source: main/waeup.kofa/trunk/src/waeup/kofa/university/batching.py @ 9106

Last change on this file since 9106 was 9087, checked in by Henrik Bettermann, 12 years ago

Improve logging.

  • Property svn:keywords set to Id
File size: 14.5 KB
RevLine 
[7195]1## $Id: batching.py 9087 2012-08-07 06:17:31Z 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##
[5009]18"""Batch processing components for academics objects.
19
20Batch processors eat CSV files to add, update or remove large numbers
21of certain kinds of objects at once.
22
23Here we define the processors for academics specific objects like
24faculties, departments and the like.
25"""
26import grok
27from zope.interface import Interface
[8302]28from zope.component import queryUtility
29from zope.schema import getFields
30from zope.catalog.interfaces import ICatalog
[8995]31from zope.event import notify
[9001]32from zope.securitypolicy.interfaces import (
33    IPrincipalRoleManager, IPrincipalRoleMap)
[8995]34from waeup.kofa.authentication import LocalRoleSetEvent
[9001]35from waeup.kofa.interfaces import (
36    IBatchProcessor, IGNORE_MARKER, DELETION_MARKER, FatalCSVError)
[7811]37from waeup.kofa.university.interfaces import (
[7333]38    IFacultiesContainer, IFaculty, ICourse, IDepartment, ICertificate,
[5009]39    ICertificateCourse)
[8996]40from waeup.kofa.university import (
41    Faculty, Department, Course, Certificate)
[7811]42from waeup.kofa.utils.batching import BatchProcessor
[5009]43
44class FacultyProcessor(BatchProcessor):
45    """A batch processor for IFaculty objects.
46    """
[6628]47    grok.implements(IBatchProcessor)
[5009]48    grok.provides(IBatchProcessor)
49    grok.context(Interface)
[7933]50    util_name = 'facultyprocessor'
[5009]51    grok.name(util_name)
52
[7933]53    name = u'Faculty Processor'
[5009]54    iface = IFaculty
[8994]55    allowed_roles = Faculty.local_roles
[5009]56
57    location_fields = ['code',]
58    factory_name = 'waeup.Faculty'
59
[6628]60    mode = None
61
[5009]62    def parentsExist(self, row, site):
63        return 'faculties' in site.keys()
64
[8994]65    @property
66    def available_fields(self):
67        fields = getFields(self.iface)
68        return sorted(list(set(
[8996]69            self.location_fields + fields.keys() + ['local_roles']
70            )))
[8994]71
[5009]72    def entryExists(self, row, site):
73        return row['code'] in site['faculties'].keys()
74
75    def getParent(self, row, site):
76        return site['faculties']
77
78    def getEntry(self, row, site):
79        if not self.entryExists(row, site):
80            return None
81        parent = self.getParent(row, site)
82        return parent.get(row['code'])
[6628]83
[5009]84    def addEntry(self, obj, row, site):
85        parent = self.getParent(row, site)
86        parent.addFaculty(obj)
87        return
88
89    def delEntry(self, row, site):
90        parent = self.getParent(row, site)
91        del parent[row['code']]
92        pass
93
[8995]94    def updateEntry(self, obj, row, site):
95        """Update obj to the values given in row.
96        """
97        items_changed = ''
98
[9001]99        if row.has_key('local_roles') and row['local_roles'] not in (
100            None, IGNORE_MARKER):
101            role_manager = IPrincipalRoleManager(obj)
102            role_map = IPrincipalRoleMap(obj)
103            # Remove all existing local roles.
104            for local_role, user_name, setting in role_map.getPrincipalsAndRoles():
105                role_manager.unsetRoleForPrincipal(local_role, user_name)
106                notify(LocalRoleSetEvent(
107                        obj, local_role, user_name, granted=False))
108            # Add new local roles.
109            if row['local_roles'] != DELETION_MARKER:
110                local_roles = eval(row['local_roles'])
111                for rolemap in local_roles:
112                    user = rolemap['user_name']
113                    local_role = rolemap['local_role']
114                    role_manager.assignRoleToPrincipal(local_role, user)
115                    notify(LocalRoleSetEvent(obj, local_role, user, granted=True))
116                    items_changed += (
117                        '%s=%s, ' % ('local_roles', '%s|%s' % (user,local_role)))
[8995]118            row.pop('local_roles')
119
120        # apply other values...
121        items_changed += super(FacultyProcessor, self).updateEntry(
122            obj, row, site)
123
124        # Log actions...
[9087]125        location_field = self.location_fields[0]
[8995]126        grok.getSite().logger.info('%s - %s - Record updated: %s'
[9087]127            % (self.name, row[location_field], items_changed))
[8995]128        return items_changed
129
[8994]130    def checkConversion(self, row, mode='create'):
131        """Validates all values in row.
132        """
133        errs, inv_errs, conv_dict =  super(
134            FacultyProcessor, self).checkConversion(row, mode=mode)
135        if row.has_key('local_roles'):
[9001]136            if row['local_roles'] in (None, DELETION_MARKER, IGNORE_MARKER):
137                return errs, inv_errs, conv_dict
[8994]138            try:
139                local_roles = eval(row['local_roles'])
140            except:
141                errs.append(('local_roles','Error'))
142                return errs, inv_errs, conv_dict
143            if not isinstance(local_roles, list):
144                errs.append(('local_roles','no list'))
145                return errs, inv_errs, conv_dict
146            for rolemap in local_roles:
147                if not isinstance(rolemap, dict):
148                    errs.append(('local_roles','no dicts'))
149                    return errs, inv_errs, conv_dict
150                if not 'user_name' in rolemap.keys() or not \
151                    'local_role' in rolemap.keys():
152                    errs.append(('local_roles','user_name or local_role missing'))
153                    return errs, inv_errs, conv_dict
154                local_role = rolemap['local_role']
155                if not local_role in self.allowed_roles:
156                    errs.append(('local_roles','%s not allowed' % local_role))
157                    return errs, inv_errs, conv_dict
158                user = rolemap['user_name']
159                users = grok.getSite()['users']
160                if not user in users.keys():
161                    errs.append(('local_roles','%s does not exist' % user))
162                    return errs, inv_errs, conv_dict
163        return errs, inv_errs, conv_dict
164
[8996]165class DepartmentProcessor(FacultyProcessor):
[5009]166    """A batch processor for IDepartment objects.
167    """
[6628]168    grok.implements(IBatchProcessor)
[5009]169    grok.provides(IBatchProcessor)
170    grok.context(Interface)
[7933]171    util_name = 'departmentprocessor'
[5009]172    grok.name(util_name)
173
[7933]174    name = u'Department Processor'
[5009]175    iface = IDepartment
[8996]176    allowed_roles = Department.local_roles
[5009]177
178    location_fields = ['code', 'faculty_code']
179    factory_name = 'waeup.Department'
180
[6628]181    mode = None
182
[5009]183    def parentsExist(self, row, site):
184        if not 'faculties' in site.keys():
185            return False
186        return row['faculty_code'] in site['faculties']
187
188    def entryExists(self, row, site):
189        if not self.parentsExist(row, site):
190            return False
191        parent = self.getParent(row, site)
192        return row['code'] in parent.keys()
193
194    def getParent(self, row, site):
195        return site['faculties'][row['faculty_code']]
196
197    def getEntry(self, row, site):
198        if not self.entryExists(row, site):
199            return None
200        parent = self.getParent(row, site)
201        return parent.get(row['code'])
[6628]202
[5009]203    def addEntry(self, obj, row, site):
204        parent = self.getParent(row, site)
205        parent.addDepartment(obj)
206        return
207
208    def delEntry(self, row, site):
209        parent = self.getParent(row, site)
210        del parent[row['code']]
211        return
212
[8996]213class CourseProcessor(FacultyProcessor):
[5009]214    """A batch processor for ICourse objects.
215    """
[6628]216    grok.implements(IBatchProcessor)
[5009]217    grok.provides(IBatchProcessor)
218    grok.context(Interface)
[7933]219    util_name = 'courseprocessor'
[5009]220    grok.name(util_name)
221
[7933]222    name = u'Course Processor'
[5009]223    iface = ICourse
[8996]224    allowed_roles = Course.local_roles
[5009]225
226    location_fields = ['code', 'faculty_code', 'department_code']
227    factory_name = 'waeup.Course'
228
[6628]229    mode = None
230
[5009]231    def parentsExist(self, row, site):
232        if not 'faculties' in site.keys():
233            return False
234        if not row['faculty_code'] in site['faculties'].keys():
235            return False
236        faculty = site['faculties'][row['faculty_code']]
237        return row['department_code'] in faculty.keys()
238
239    def entryExists(self, row, site):
240        if not self.parentsExist(row, site):
241            return False
242        parent = self.getParent(row, site)
243        return row['code'] in parent.keys()
244
245    def getParent(self, row, site):
246        dept = site['faculties'][row['faculty_code']][row['department_code']]
247        return dept.courses
248
249    def getEntry(self, row, site):
250        if not self.entryExists(row, site):
251            return None
252        parent = self.getParent(row, site)
253        return parent.get(row['code'])
[6628]254
[5009]255    def addEntry(self, obj, row, site):
256        parent = self.getParent(row, site)
257        parent.addCourse(obj)
258        return
259
260    def delEntry(self, row, site):
261        parent = self.getParent(row, site)
262        del parent[row['code']]
263        return
264
[8996]265class CertificateProcessor(FacultyProcessor):
[5009]266    """A batch processor for ICertificate objects.
267    """
[6628]268    grok.implements(IBatchProcessor)
[5009]269    grok.provides(IBatchProcessor)
270    grok.context(Interface)
[7933]271    util_name = 'certificateprocessor'
[5009]272    grok.name(util_name)
273
[7933]274    name = u'Certificate Processor'
[5009]275    iface = ICertificate
[8996]276    allowed_roles = Certificate.local_roles
[5009]277
[8302]278    location_fields = ['code']
[5009]279    factory_name = 'waeup.Certificate'
280
[6628]281    mode = None
282
[8302]283    @property
284    def available_fields(self):
285        fields = getFields(self.iface)
286        return sorted(list(set(
[8996]287            ['faculty_code','department_code'] + fields.keys()
288            + ['local_roles'])))
[8302]289
290    def checkHeaders(self, headerfields, mode='create'):
291        req = self.req[mode]
292        # Check for required fields...
293        for field in req:
294            if not field in headerfields:
295                raise FatalCSVError(
296                    "Need at least columns %s for import!" %
297                    ', '.join(["'%s'" % x for x in req]))
298        # Check for double fields. Cannot happen because this error is
299        # already catched in views
300        not_ignored_fields = [x for x in headerfields
301                              if not x.startswith('--')]
302        if len(set(not_ignored_fields)) < len(not_ignored_fields):
303            raise FatalCSVError(
304                "Double headers: each column name may only appear once.")
305        if mode == 'create':
306            if not 'faculty_code' in headerfields \
307                and not 'department_code' in headerfields :
308                raise FatalCSVError(
309                    "Need at least columns faculty_code and department_code")
310        return True
311
[5009]312    def parentsExist(self, row, site):
[8302]313        return self.getParent(row,site) is not None
[5009]314
315    def entryExists(self, row, site):
316        parent = self.getParent(row, site)
[8302]317        if parent is not None:
318            return row['code'] in parent.keys()
319        return False
[5009]320
321    def getParent(self, row, site):
[8302]322        if not 'faculties' in site.keys():
323            return None
324        # If both faculty and department codes are provided, use
325        # these to get parent.
326        if row.get('faculty_code',None) not in (None, IGNORE_MARKER) and \
327            row.get('department_code',None) not in (None, IGNORE_MARKER):
328            if not row['faculty_code'] in site['faculties'].keys():
329                return None
330            faculty = site['faculties'][row['faculty_code']]
331            if not row['department_code'] in faculty.keys():
332                return None
333            dept = faculty[row['department_code']]
334            return dept.certificates
335        # If department code or faculty code is missing,
336        # use catalog to get parent. Makes only sense in update mode but
337        # does also work in create mode.
338        cat = queryUtility(ICatalog, name='certificates_catalog')
339        results = list(
340            cat.searchResults(code=(row['code'], row['code'])))
341        if results:
342            return results[0].__parent__
343        return None
[5009]344
345    def getEntry(self, row, site):
346        parent = self.getParent(row, site)
[8302]347        if parent is not None:
348            return parent.get(row['code'])
349        return None
[5009]350
351    def addEntry(self, obj, row, site):
352        parent = self.getParent(row, site)
[6243]353        parent.addCertificate(obj)
354        return
[5009]355
356    def delEntry(self, row, site):
357        parent = self.getParent(row, site)
358        del parent[row['code']]
359        return
360
[8996]361class CertificateCourseProcessor(FacultyProcessor):
[5009]362    """A batch processor for ICertificateCourse objects.
363    """
[6628]364    grok.implements(IBatchProcessor)
[5009]365    grok.provides(IBatchProcessor)
366    grok.context(Interface)
[7933]367    util_name = 'certificatecourseprocessor'
[5009]368    grok.name(util_name)
369
[7933]370    name = u'CertificateCourse Processor'
[5009]371    iface = ICertificateCourse
372
373    location_fields = ['course', 'level', 'faculty_code', 'department_code',
374                       'certificate_code',]
375    factory_name = 'waeup.CertificateCourse'
376
[6628]377    mode = None
378
[5009]379    def parentsExist(self, row, site):
380        if not 'faculties' in site.keys():
381            return False
382        if not row['faculty_code'] in site['faculties'].keys():
383            return False
384        faculty = site['faculties'][row['faculty_code']]
385        if not row['department_code'] in faculty.keys():
386            return False
387        dept = faculty[row['department_code']]
388        return row['certificate_code'] in dept.certificates.keys()
389
390    def entryExists(self, row, site):
391        if not self.parentsExist(row, site):
392            return False
393        parent = self.getParent(row, site)
394        code = "%s_%s" % (row['course'].code, row['level'])
395        return code in parent.keys()
396
397    def getParent(self, row, site):
398        dept = site['faculties'][row['faculty_code']][row['department_code']]
399        return dept.certificates[row['certificate_code']]
400
401    def getEntry(self, row, site):
402        if not self.entryExists(row, site):
403            return None
404        parent = self.getParent(row, site)
[6628]405        return parent.get("%s_%s" % (row['course'].code, row['level']))
[5009]406
407    def addEntry(self, obj, row, site):
408        parent = self.getParent(row, site)
[8920]409        parent.addCertCourse(row['course'],
[7665]410                            row['level'], row['mandatory'])
[5009]411        return
412
413    def delEntry(self, row, site):
414        parent = self.getParent(row, site)
[8920]415        parent.delCertCourse(row['course'].code, row['level'])
[5009]416        return
Note: See TracBrowser for help on using the repository browser.