source: main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py @ 8201

Last change on this file since 8201 was 8176, checked in by uli, 13 years ago

Use PhoneNumber?.

  • Property svn:keywords set to Id
File size: 26.8 KB
RevLine 
[7191]1## $Id: batching.py 8176 2012-04-16 09:16:34Z uli $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
[7433]18"""Batch processing components for student objects.
[6821]19
20Batch processors eat CSV files to add, update or remove large numbers
21of certain kinds of objects at once.
22
[7261]23Here we define the processors for students specific objects like
24students, studycourses, payment tickets and accommodation tickets.
[6821]25"""
26import grok
[6849]27import csv
[6821]28from zope.interface import Interface
[6825]29from zope.schema import getFields
[7548]30from zope.component import queryUtility, getUtility
[7429]31from zope.event import notify
[6825]32from zope.catalog.interfaces import ICatalog
[7951]33from hurry.workflow.interfaces import IWorkflowState, IWorkflowInfo
[7811]34from waeup.kofa.interfaces import (
[7522]35    IBatchProcessor, FatalCSVError, IObjectConverter, IUserAccount,
[7951]36    IObjectHistory, VALIDATED)
[7959]37from waeup.kofa.interfaces import MessageFactory as _
[7811]38from waeup.kofa.students.interfaces import (
[7532]39    IStudent, IStudentStudyCourse,
[7536]40    IStudentUpdateByRegNo, IStudentUpdateByMatricNo,
[7623]41    IStudentStudyLevel, ICourseTicket,
[8174]42    IStudentOnlinePayment, IStudentVerdictUpdate)
[7811]43from waeup.kofa.students.workflow import  IMPORTABLE_STATES
44from waeup.kofa.utils.batching import BatchProcessor
[6821]45
46class StudentProcessor(BatchProcessor):
47    """A batch processor for IStudent objects.
48    """
49    grok.implements(IBatchProcessor)
50    grok.provides(IBatchProcessor)
51    grok.context(Interface)
[7933]52    util_name = 'studentprocessor'
[6821]53    grok.name(util_name)
54
[7933]55    name = u'Student Processor'
[6821]56    iface = IStudent
57
[6849]58    location_fields = []
[6821]59    factory_name = 'waeup.Student'
60
[6841]61    mode = None
62
[6821]63    @property
[6849]64    def available_fields(self):
[8176]65        fields = getFields(self.iface)
[6849]66        return sorted(list(set(
[7513]67            ['student_id','reg_number','matric_number',
68            'password', 'reg_state'] + getFields(
[6849]69                self.iface).keys())))
[6821]70
[6849]71    def checkHeaders(self, headerfields, mode='create'):
[6854]72        if not 'reg_number' in headerfields and not 'student_id' \
73            in headerfields and not 'matric_number' in headerfields:
[6849]74            raise FatalCSVError(
[6854]75                "Need at least columns student_id or reg_number " +
76                "or matric_number for import!")
[6849]77        if mode == 'create':
78            for field in self.required_fields:
79                if not field in headerfields:
80                    raise FatalCSVError(
81                        "Need at least columns %s for import!" %
82                        ', '.join(["'%s'" % x for x in self.required_fields]))
83        # Check for fields to be ignored...
84        not_ignored_fields = [x for x in headerfields
85                              if not x.startswith('--')]
86        if len(set(not_ignored_fields)) < len(not_ignored_fields):
87            raise FatalCSVError(
88                "Double headers: each column name may only appear once.")
89        return True
90
[6821]91    def parentsExist(self, row, site):
92        return 'students' in site.keys()
93
[6849]94    def getLocator(self, row):
[7269]95        if row.get('student_id',None):
[6849]96            return 'student_id'
[7269]97        elif row.get('reg_number',None):
[6849]98            return 'reg_number'
[7269]99        elif row.get('matric_number',None):
[6849]100            return 'matric_number'
101        else:
102            return None
103
[6821]104    # The entry never exists in create mode.
105    def entryExists(self, row, site):
[7267]106        return self.getEntry(row, site) is not None
107
108    def getParent(self, row, site):
109        return site['students']
110
111    def getEntry(self, row, site):
[6846]112        if not 'students' in site.keys():
[6849]113            return None
114        if self.getLocator(row) == 'student_id':
[6846]115            if row['student_id'] in site['students']:
116                student = site['students'][row['student_id']]
117                return student
[6849]118        elif self.getLocator(row) == 'reg_number':
[6846]119            reg_number = row['reg_number']
120            cat = queryUtility(ICatalog, name='students_catalog')
121            results = list(
122                cat.searchResults(reg_number=(reg_number, reg_number)))
123            if results:
124                return results[0]
[6849]125        elif self.getLocator(row) == 'matric_number':
[6846]126            matric_number = row['matric_number']
127            cat = queryUtility(ICatalog, name='students_catalog')
128            results = list(
129                cat.searchResults(matric_number=(matric_number, matric_number)))
130            if results:
131                return results[0]
[6849]132        return None
[6821]133
134    def addEntry(self, obj, row, site):
135        parent = self.getParent(row, site)
136        parent.addStudent(obj)
[7959]137        # We have to log this if reg_state is provided. If not,
138        # logging is done by the event handler handle_student_added
139        if row.has_key('reg_state'):
140            parent.logger.info('%s - Student record created' % obj.student_id)
[7656]141        history = IObjectHistory(obj)
[7959]142        history.addMessage(_('Student record created'))
[6821]143        return
144
145    def delEntry(self, row, site):
[7267]146        student = self.getEntry(row, site)
[7263]147        if student is not None:
[6846]148            parent = self.getParent(row, site)
[7656]149            parent.logger.info('%s - Student removed' % student.student_id)
[6846]150            del parent[student.student_id]
[6821]151        pass
[6825]152
[7497]153    def updateEntry(self, obj, row, site):
154        """Update obj to the values given in row.
155        """
[7643]156        # Remove student_id from row if empty
157        if row.has_key('student_id') and row['student_id'] is None:
158            row.pop('student_id')
[7656]159        items_changed = ''
[7497]160        for key, value in row.items():
161            # Set student password and all fields declared in interface.
[7522]162            if key == 'password' and value != '':
[7497]163                IUserAccount(obj).setPassword(value)
[7513]164            elif key == 'reg_state':
165                IWorkflowState(obj).setState(value)
[7959]166                msg = _("State '${a}' set", mapping = {'a':value})
[7522]167                history = IObjectHistory(obj)
168                history.addMessage(msg)
[7497]169            elif hasattr(obj, key):
170                setattr(obj, key, value)
[7656]171            items_changed += '%s=%s, ' % (key,value)
172        parent = self.getParent(row, site)
173        if hasattr(obj,'student_id'):
174            # Update mode: the student exists and we can get the student_id
175            parent.logger.info(
176                '%s - Student record updated: %s'
177                % (obj.student_id, items_changed))
178        else:
179            # Create mode: the student does not yet exist
180            parent.logger.info('Student data imported: %s' % items_changed)
[7497]181        return
182
[6849]183    def getMapping(self, path, headerfields, mode):
184        """Get a mapping from CSV file headerfields to actually used fieldnames.
185        """
186        result = dict()
187        reader = csv.reader(open(path, 'rb'))
188        raw_header = reader.next()
189        for num, field in enumerate(headerfields):
[6854]190            if field not in [
191                'student_id', 'reg_number', 'matric_number'] and mode == 'remove':
[6849]192                continue
193            if field == u'--IGNORE--':
194                # Skip ignored columns in failed and finished data files.
195                continue
196            result[raw_header[num]] = field
197        return result
198
199    def checkConversion(self, row, mode='create'):
200        """Validates all values in row.
201        """
[7643]202        iface = self.iface
[6849]203        if mode in ['update', 'remove']:
204            if self.getLocator(row) == 'reg_number':
205                iface = IStudentUpdateByRegNo
206            elif self.getLocator(row) == 'matric_number':
207                iface = IStudentUpdateByMatricNo
208        converter = IObjectConverter(iface)
209        errs, inv_errs, conv_dict =  converter.fromStringDict(
210            row, self.factory_name)
[7513]211        if row.has_key('reg_state') and \
212            not row['reg_state'] in IMPORTABLE_STATES:
[7522]213            if row['reg_state'] != '':
214                errs.append(('reg_state','not allowed'))
215            else:
216                errs.append(('reg_state','no value provided'))
[6849]217        return errs, inv_errs, conv_dict
218
[6825]219class StudentStudyCourseProcessor(BatchProcessor):
220    """A batch processor for IStudentStudyCourse objects.
221    """
222    grok.implements(IBatchProcessor)
223    grok.provides(IBatchProcessor)
224    grok.context(Interface)
[6837]225    util_name = 'studycourseupdater'
[6825]226    grok.name(util_name)
227
[7933]228    name = u'StudentStudyCourse Processor (update only)'
[7532]229    iface = IStudentStudyCourse
[6825]230    factory_name = 'waeup.StudentStudyCourse'
231
[6849]232    location_fields = []
233
[6841]234    mode = None
235
[6825]236    @property
237    def available_fields(self):
238        return sorted(list(set(
[6843]239            ['student_id','reg_number','matric_number'] + getFields(
240                self.iface).keys())))
[6825]241
[6837]242    def checkHeaders(self, headerfields, mode='ignore'):
[6854]243        if not 'reg_number' in headerfields and not 'student_id' \
244            in headerfields and not 'matric_number' in headerfields:
[6825]245            raise FatalCSVError(
[6854]246                "Need at least columns student_id " +
247                "or reg_number or matric_number for import!")
[6834]248        # Check for fields to be ignored...
[6825]249        not_ignored_fields = [x for x in headerfields
250                              if not x.startswith('--')]
251        if len(set(not_ignored_fields)) < len(not_ignored_fields):
252            raise FatalCSVError(
253                "Double headers: each column name may only appear once.")
254        return True
255
[7267]256    def getParent(self, row, site):
[6846]257        if not 'students' in site.keys():
[6849]258            return None
[6846]259        if 'student_id' in row.keys() and row['student_id']:
[6825]260            if row['student_id'] in site['students']:
261                student = site['students'][row['student_id']]
262                return student
[6843]263        elif 'reg_number' in row.keys() and row['reg_number']:
[6825]264            reg_number = row['reg_number']
265            cat = queryUtility(ICatalog, name='students_catalog')
266            results = list(
267                cat.searchResults(reg_number=(reg_number, reg_number)))
268            if results:
269                return results[0]
[6843]270        elif 'matric_number' in row.keys() and row['matric_number']:
271            matric_number = row['matric_number']
272            cat = queryUtility(ICatalog, name='students_catalog')
273            results = list(
274                cat.searchResults(matric_number=(matric_number, matric_number)))
275            if results:
276                return results[0]
[6849]277        return None
[6825]278
[7267]279    def parentsExist(self, row, site):
280        return self.getParent(row, site) is not None
281
[6825]282    def entryExists(self, row, site):
[7534]283        return self.getEntry(row, site) is not None
[6825]284
285    def getEntry(self, row, site):
[7534]286        student = self.getParent(row, site)
[7536]287        if student is None:
[6825]288            return None
289        return student.get('studycourse')
[7429]290
291    def updateEntry(self, obj, row, site):
292        """Update obj to the values given in row.
293        """
[7656]294        items_changed = ''
[7429]295        for key, value in row.items():
296            # Skip fields not declared in interface.
297            if hasattr(obj, key):
298                setattr(obj, key, value)
[7656]299                if key == 'certificate':
300                    value = value.code
301            items_changed += '%s=%s, ' % (key,value)
302        parent = self.getParent(row, site)
303        parent.__parent__.logger.info(
304            '%s - Study course updated: %s'
305            % (parent.student_id, items_changed))
[7429]306        # Update the students_catalog
307        notify(grok.ObjectModifiedEvent(obj.__parent__))
308        return
309
[7532]310    def checkConversion(self, row, mode='ignore'):
311        """Validates all values in row.
312        """
313        converter = IObjectConverter(self.iface)
314        errs, inv_errs, conv_dict =  converter.fromStringDict(
315            row, self.factory_name)
316        # We have to check if current_level is in range of certificate.
[7548]317        # This is not done by the converter. This kind of conversion
318        # checking does only work if a combination of certificate and
319        # current_level is provided.
[7534]320        if conv_dict.has_key('certificate'):
321          certificate = conv_dict['certificate']
322          start_level = certificate.start_level
323          end_level = certificate.end_level
324          if conv_dict['current_level'] < start_level or \
[7612]325              conv_dict['current_level'] > end_level+120:
[7534]326              errs.append(('current_level','not in range'))
[7532]327        return errs, inv_errs, conv_dict
328
[7536]329class StudentStudyLevelProcessor(BatchProcessor):
330    """A batch processor for IStudentStudyLevel objects.
331    """
332    grok.implements(IBatchProcessor)
333    grok.provides(IBatchProcessor)
334    grok.context(Interface)
[7933]335    util_name = 'studylevelprocessor'
[7536]336    grok.name(util_name)
337
[7933]338    name = u'StudentStudyLevel Processor'
[7536]339    iface = IStudentStudyLevel
340    factory_name = 'waeup.StudentStudyLevel'
341
342    location_fields = []
343
344    mode = None
345
346    @property
347    def available_fields(self):
348        return sorted(list(set(
349            ['student_id','reg_number','matric_number','level'] + getFields(
350                self.iface).keys())))
351
352    def checkHeaders(self, headerfields, mode='ignore'):
353        if not 'reg_number' in headerfields and not 'student_id' \
354            in headerfields and not 'matric_number' in headerfields:
355            raise FatalCSVError(
356                "Need at least columns student_id " +
357                "or reg_number or matric_number for import!")
358        if not 'level' in headerfields:
359            raise FatalCSVError(
360                "Need level for import!")
361        # Check for fields to be ignored...
362        not_ignored_fields = [x for x in headerfields
363                              if not x.startswith('--')]
364        if len(set(not_ignored_fields)) < len(not_ignored_fields):
365            raise FatalCSVError(
366                "Double headers: each column name may only appear once.")
367        return True
368
369    def getParent(self, row, site):
370        if not 'students' in site.keys():
371            return None
372        if 'student_id' in row.keys() and row['student_id']:
373            if row['student_id'] in site['students']:
374                student = site['students'][row['student_id']]
375                return student['studycourse']
376        elif 'reg_number' in row.keys() and row['reg_number']:
377            reg_number = row['reg_number']
378            cat = queryUtility(ICatalog, name='students_catalog')
379            results = list(
380                cat.searchResults(reg_number=(reg_number, reg_number)))
381            if results:
382                return results[0]['studycourse']
383        elif 'matric_number' in row.keys() and row['matric_number']:
384            matric_number = row['matric_number']
385            cat = queryUtility(ICatalog, name='students_catalog')
386            results = list(
387                cat.searchResults(matric_number=(matric_number, matric_number)))
388            if results:
389                return results[0]['studycourse']
390        return None
391
392    def parentsExist(self, row, site):
393        return self.getParent(row, site) is not None
394
395    def entryExists(self, row, site):
396        return self.getEntry(row, site) is not None
397
398    def getEntry(self, row, site):
399        studycourse = self.getParent(row, site)
400        if studycourse is None:
401            return None
402        return studycourse.get(row['level'])
403
404    def addEntry(self, obj, row, site):
405        parent = self.getParent(row, site)
406        obj.level = int(row['level'])
407        parent[row['level']] = obj
408        return
409
410    def checkConversion(self, row, mode='ignore'):
411        """Validates all values in row.
412        """
413        converter = IObjectConverter(self.iface)
414        errs, inv_errs, conv_dict =  converter.fromStringDict(
415            row, self.factory_name)
416        # We have to check if level is a valid integer.
[7548]417        # This is not done by the converter.
[7536]418        try:
419            level = int(row['level'])
[7612]420            if level not in range(0,700,10):
[7536]421                errs.append(('level','no valid integer'))
422        except ValueError:
423            errs.append(('level','no integer'))
424        return errs, inv_errs, conv_dict
[7548]425
426class CourseTicketProcessor(BatchProcessor):
427    """A batch processor for ICourseTicket objects.
428    """
429    grok.implements(IBatchProcessor)
430    grok.provides(IBatchProcessor)
431    grok.context(Interface)
[7933]432    util_name = 'courseticketprocessor'
[7548]433    grok.name(util_name)
434
[7933]435    name = u'CourseTicket Processor'
[7548]436    iface = ICourseTicket
437    factory_name = 'waeup.CourseTicket'
438
439    location_fields = []
440
441    mode = None
442
443    @property
444    def available_fields(self):
445        return sorted(list(set(
446            ['student_id','reg_number','matric_number','level','code'] + getFields(
447                self.iface).keys())))
448
449    def checkHeaders(self, headerfields, mode='ignore'):
450        if not 'reg_number' in headerfields and not 'student_id' \
451            in headerfields and not 'matric_number' in headerfields:
452            raise FatalCSVError(
453                "Need at least columns student_id " +
454                "or reg_number or matric_number for import!")
455        if not 'level' in headerfields:
456            raise FatalCSVError(
457                "Need level for import!")
458        if not 'code' in headerfields:
459            raise FatalCSVError(
460                "Need code for import!")
461        # Check for fields to be ignored...
462        not_ignored_fields = [x for x in headerfields
463                              if not x.startswith('--')]
464        if len(set(not_ignored_fields)) < len(not_ignored_fields):
465            raise FatalCSVError(
466                "Double headers: each column name may only appear once.")
467        return True
468
469    def getParent(self, row, site):
470        if not 'students' in site.keys():
471            return None
472        if 'student_id' in row.keys() and row['student_id']:
473            if row['student_id'] in site['students']:
474                student = site['students'][row['student_id']]
475                return student['studycourse'].get(row['level'])
476        elif 'reg_number' in row.keys() and row['reg_number']:
477            reg_number = row['reg_number']
478            cat = queryUtility(ICatalog, name='students_catalog')
479            results = list(
480                cat.searchResults(reg_number=(reg_number, reg_number)))
481            if results:
482                return results[0]['studycourse'].get(row['level'])
483        elif 'matric_number' in row.keys() and row['matric_number']:
484            matric_number = row['matric_number']
485            cat = queryUtility(ICatalog, name='students_catalog')
486            results = list(
487                cat.searchResults(matric_number=(matric_number, matric_number)))
488            if results:
489                return results[0]['studycourse'].get(row['level'])
490        return None
491
492    def parentsExist(self, row, site):
493        return self.getParent(row, site) is not None
494
495    def entryExists(self, row, site):
496        return self.getEntry(row, site) is not None
497
498    def getEntry(self, row, site):
499        level = self.getParent(row, site)
500        if level is None:
501            return None
502        return level.get(row['code'])
503
504    def addEntry(self, obj, row, site):
505        parent = self.getParent(row, site)
506        catalog = getUtility(ICatalog, name='courses_catalog')
507        entries = list(catalog.searchResults(code=(row['code'],row['code'])))
508        obj.fcode = entries[0].__parent__.__parent__.__parent__.code
509        obj.dcode = entries[0].__parent__.__parent__.code
510        obj.title = entries[0].title
511        obj.credits = entries[0].credits
512        obj.passmark = entries[0].passmark
513        obj.semester = entries[0].semester
514        parent[row['code']] = obj
515        return
516
517    def checkConversion(self, row, mode='ignore'):
518        """Validates all values in row.
519        """
520        converter = IObjectConverter(self.iface)
521        errs, inv_errs, conv_dict =  converter.fromStringDict(
522            row, self.factory_name)
523        # We have to check if course really exists.
524        # This is not done by the converter.
525        catalog = getUtility(ICatalog, name='courses_catalog')
526        entries = catalog.searchResults(code=(row['code'],row['code']))
527        if len(entries) == 0:
528            errs.append(('code','non-existent'))
529            return errs, inv_errs, conv_dict
[7623]530        return errs, inv_errs, conv_dict
531
532class StudentOnlinePaymentProcessor(BatchProcessor):
533    """A batch processor for IStudentOnlinePayment objects.
534    """
535    grok.implements(IBatchProcessor)
536    grok.provides(IBatchProcessor)
537    grok.context(Interface)
[7933]538    util_name = 'paymentprocessor'
[7623]539    grok.name(util_name)
540
[7933]541    name = u'Payment Processor'
[8174]542    iface = IStudentOnlinePayment
[7623]543    factory_name = 'waeup.StudentOnlinePayment'
544
545    location_fields = []
546
547    mode = None
548
549    @property
550    def available_fields(self):
551        return sorted(list(set(
552            ['student_id','reg_number','matric_number','p_id'] + getFields(
553                self.iface).keys())))
554
555    def checkHeaders(self, headerfields, mode='ignore'):
556        if not 'reg_number' in headerfields and not 'student_id' \
557            in headerfields and not 'matric_number' in headerfields:
558            raise FatalCSVError(
559                "Need at least columns student_id " +
560                "or reg_number or matric_number for import!")
561        if not 'p_id' in headerfields:
562            raise FatalCSVError(
563                "Need p_id for import!")
564        # Check for fields to be ignored...
565        not_ignored_fields = [x for x in headerfields
566                              if not x.startswith('--')]
567        if len(set(not_ignored_fields)) < len(not_ignored_fields):
568            raise FatalCSVError(
569                "Double headers: each column name may only appear once.")
570        return True
571
572    def getParent(self, row, site):
573        if not 'students' in site.keys():
574            return None
575        if 'student_id' in row.keys() and row['student_id']:
576            if row['student_id'] in site['students']:
577                student = site['students'][row['student_id']]
578                return student['payments']
579        elif 'reg_number' in row.keys() and row['reg_number']:
580            reg_number = row['reg_number']
581            cat = queryUtility(ICatalog, name='students_catalog')
582            results = list(
583                cat.searchResults(reg_number=(reg_number, reg_number)))
584            if results:
585                return results[0]['payments']
586        elif 'matric_number' in row.keys() and row['matric_number']:
587            matric_number = row['matric_number']
588            cat = queryUtility(ICatalog, name='students_catalog')
589            results = list(
590                cat.searchResults(matric_number=(matric_number, matric_number)))
591            if results:
592                return results[0]['payments']
593        return None
594
595    def parentsExist(self, row, site):
596        return self.getParent(row, site) is not None
597
598    def entryExists(self, row, site):
599        return self.getEntry(row, site) is not None
600
601    def getEntry(self, row, site):
602        payments = self.getParent(row, site)
603        if payments is None:
604            return None
[7626]605        # We can use the hash symbol at the end of p_id in import files
606        # to avoid annoying automatic number transformation
607        # by Excel or Calc
608        p_id = row['p_id'].strip('#')
609        if p_id.startswith('p'):
610            entry = payments.get(p_id)
[7623]611        else:
612            # For data migration from old SRP
[7626]613            entry = payments.get('p' + p_id[6:])
[7623]614        return entry
615
616    def addEntry(self, obj, row, site):
617        parent = self.getParent(row, site)
[7626]618        p_id = row['p_id'].strip('#')
619        if not p_id.startswith('p'):
[7623]620            # For data migration from old SRP
[7626]621            obj.p_id = 'p' + p_id[6:]
[7623]622            parent[obj.p_id] = obj
623        else:
[7626]624            parent[p_id] = obj
[7623]625        return
626
627    def checkConversion(self, row, mode='ignore'):
628        """Validates all values in row.
629        """
630        converter = IObjectConverter(self.iface)
631        errs, inv_errs, conv_dict =  converter.fromStringDict(
632            row, self.factory_name)
633        # We have to check p_id.
[7626]634        p_id = row['p_id'].strip('#')
635        if p_id.startswith('p'):
636            if not len(p_id) == 14:
[7623]637                errs.append(('p_id','invalid length'))
638                return errs, inv_errs, conv_dict
639        else:
[7626]640            if not len(p_id) == 19:
[7623]641                errs.append(('p_id','invalid length'))
642                return errs, inv_errs, conv_dict
643        return errs, inv_errs, conv_dict
[7951]644
645class StudentVerdictProcessor(StudentStudyCourseProcessor):
646    """A batch processor for verdicts.
647
648    Import verdicts and perform workflow transitions.
649    """
650
651    util_name = 'verdictupdater'
652    grok.name(util_name)
653
654    name = u'Verdict Processor (update only)'
655    iface = IStudentVerdictUpdate
656    factory_name = 'waeup.StudentStudyCourse'
657
658    @property
659    def available_fields(self):
660        return sorted(list(set(
661            ['student_id','reg_number','matric_number',
662                'current_session', 'current_level'] + getFields(
663                self.iface).keys())))
664
665    def checkUpdateRequirements(self, obj, row, site):
666        """Checks requirements the studycourse and the student must fulfill
667        before being updated.
668        """
669        # Check if current_levels correspond
670        if obj.current_level != row['current_level']:
671            return 'Current level does not correspond.'
672        # Check if current_sessions correspond
673        if obj.current_session != row['current_session']:
674            return 'Current session does not correspond.'
675        # Check if student is in state REGISTERED
676        if obj.getStudent().state != VALIDATED:
677            return 'Student in wrong state.'
678        return None
679
680    def updateEntry(self, obj, row, site):
681        """Update obj to the values given in row.
682        """
683        items_changed = ''
684        for key, value in row.items():
685            # Skip fields not declared in interface plus
[7952]686            # current_session and current_level
[7951]687            if hasattr(obj, key) and not key in [
[7952]688                'current_session','current_level']:
[7951]689                setattr(obj, key, value)
690            items_changed += '%s=%s, ' % (key,value)
691        parent = self.getParent(row, site)
692        parent.__parent__.logger.info(
693            '%s - Verdict updated: %s'
694            % (parent.student_id, items_changed))
695        # Fire transition
696        IWorkflowInfo(obj.__parent__).fireTransition('return')
697        # Update the students_catalog
698        notify(grok.ObjectModifiedEvent(obj.__parent__))
699        return
700
701    def checkConversion(self, row, mode='ignore'):
702        """Validates all values in row.
703        """
704        converter = IObjectConverter(self.iface)
705        errs, inv_errs, conv_dict =  converter.fromStringDict(
706            row, self.factory_name)
[8176]707        return errs, inv_errs, conv_dict
Note: See TracBrowser for help on using the repository browser.