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

Last change on this file since 8045 was 7959, checked in by Henrik Bettermann, 13 years ago

Translate all history messages.

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