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

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

Update local roles by import. Test if ignore and deltion marker are doing what they are supposed to do.

  • Property svn:keywords set to Id
File size: 14.5 KB
RevLine 
[7195]1## $Id: batching.py 9001 2012-07-13 16:17:44Z 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...
125        grok.getSite().logger.info('%s - %s - Record updated: %s'
126            % (self.name, self.location_fields[0], items_changed))
127        return items_changed
128
[8994]129    def checkConversion(self, row, mode='create'):
130        """Validates all values in row.
131        """
132        errs, inv_errs, conv_dict =  super(
133            FacultyProcessor, self).checkConversion(row, mode=mode)
134        if row.has_key('local_roles'):
[9001]135            if row['local_roles'] in (None, DELETION_MARKER, IGNORE_MARKER):
136                return errs, inv_errs, conv_dict
[8994]137            try:
138                local_roles = eval(row['local_roles'])
139            except:
140                errs.append(('local_roles','Error'))
141                return errs, inv_errs, conv_dict
142            if not isinstance(local_roles, list):
143                errs.append(('local_roles','no list'))
144                return errs, inv_errs, conv_dict
145            for rolemap in local_roles:
146                if not isinstance(rolemap, dict):
147                    errs.append(('local_roles','no dicts'))
148                    return errs, inv_errs, conv_dict
149                if not 'user_name' in rolemap.keys() or not \
150                    'local_role' in rolemap.keys():
151                    errs.append(('local_roles','user_name or local_role missing'))
152                    return errs, inv_errs, conv_dict
153                local_role = rolemap['local_role']
154                if not local_role in self.allowed_roles:
155                    errs.append(('local_roles','%s not allowed' % local_role))
156                    return errs, inv_errs, conv_dict
157                user = rolemap['user_name']
158                users = grok.getSite()['users']
159                if not user in users.keys():
160                    errs.append(('local_roles','%s does not exist' % user))
161                    return errs, inv_errs, conv_dict
162        return errs, inv_errs, conv_dict
163
[8996]164class DepartmentProcessor(FacultyProcessor):
[5009]165    """A batch processor for IDepartment objects.
166    """
[6628]167    grok.implements(IBatchProcessor)
[5009]168    grok.provides(IBatchProcessor)
169    grok.context(Interface)
[7933]170    util_name = 'departmentprocessor'
[5009]171    grok.name(util_name)
172
[7933]173    name = u'Department Processor'
[5009]174    iface = IDepartment
[8996]175    allowed_roles = Department.local_roles
[5009]176
177    location_fields = ['code', 'faculty_code']
178    factory_name = 'waeup.Department'
179
[6628]180    mode = None
181
[5009]182    def parentsExist(self, row, site):
183        if not 'faculties' in site.keys():
184            return False
185        return row['faculty_code'] in site['faculties']
186
187    def entryExists(self, row, site):
188        if not self.parentsExist(row, site):
189            return False
190        parent = self.getParent(row, site)
191        return row['code'] in parent.keys()
192
193    def getParent(self, row, site):
194        return site['faculties'][row['faculty_code']]
195
196    def getEntry(self, row, site):
197        if not self.entryExists(row, site):
198            return None
199        parent = self.getParent(row, site)
200        return parent.get(row['code'])
[6628]201
[5009]202    def addEntry(self, obj, row, site):
203        parent = self.getParent(row, site)
204        parent.addDepartment(obj)
205        return
206
207    def delEntry(self, row, site):
208        parent = self.getParent(row, site)
209        del parent[row['code']]
210        return
211
[8996]212class CourseProcessor(FacultyProcessor):
[5009]213    """A batch processor for ICourse objects.
214    """
[6628]215    grok.implements(IBatchProcessor)
[5009]216    grok.provides(IBatchProcessor)
217    grok.context(Interface)
[7933]218    util_name = 'courseprocessor'
[5009]219    grok.name(util_name)
220
[7933]221    name = u'Course Processor'
[5009]222    iface = ICourse
[8996]223    allowed_roles = Course.local_roles
[5009]224
225    location_fields = ['code', 'faculty_code', 'department_code']
226    factory_name = 'waeup.Course'
227
[6628]228    mode = None
229
[5009]230    def parentsExist(self, row, site):
231        if not 'faculties' in site.keys():
232            return False
233        if not row['faculty_code'] in site['faculties'].keys():
234            return False
235        faculty = site['faculties'][row['faculty_code']]
236        return row['department_code'] in faculty.keys()
237
238    def entryExists(self, row, site):
239        if not self.parentsExist(row, site):
240            return False
241        parent = self.getParent(row, site)
242        return row['code'] in parent.keys()
243
244    def getParent(self, row, site):
245        dept = site['faculties'][row['faculty_code']][row['department_code']]
246        return dept.courses
247
248    def getEntry(self, row, site):
249        if not self.entryExists(row, site):
250            return None
251        parent = self.getParent(row, site)
252        return parent.get(row['code'])
[6628]253
[5009]254    def addEntry(self, obj, row, site):
255        parent = self.getParent(row, site)
256        parent.addCourse(obj)
257        return
258
259    def delEntry(self, row, site):
260        parent = self.getParent(row, site)
261        del parent[row['code']]
262        return
263
[8996]264class CertificateProcessor(FacultyProcessor):
[5009]265    """A batch processor for ICertificate objects.
266    """
[6628]267    grok.implements(IBatchProcessor)
[5009]268    grok.provides(IBatchProcessor)
269    grok.context(Interface)
[7933]270    util_name = 'certificateprocessor'
[5009]271    grok.name(util_name)
272
[7933]273    name = u'Certificate Processor'
[5009]274    iface = ICertificate
[8996]275    allowed_roles = Certificate.local_roles
[5009]276
[8302]277    location_fields = ['code']
[5009]278    factory_name = 'waeup.Certificate'
279
[6628]280    mode = None
281
[8302]282    @property
283    def available_fields(self):
284        fields = getFields(self.iface)
285        return sorted(list(set(
[8996]286            ['faculty_code','department_code'] + fields.keys()
287            + ['local_roles'])))
[8302]288
289    def checkHeaders(self, headerfields, mode='create'):
290        req = self.req[mode]
291        # Check for required fields...
292        for field in req:
293            if not field in headerfields:
294                raise FatalCSVError(
295                    "Need at least columns %s for import!" %
296                    ', '.join(["'%s'" % x for x in req]))
297        # Check for double fields. Cannot happen because this error is
298        # already catched in views
299        not_ignored_fields = [x for x in headerfields
300                              if not x.startswith('--')]
301        if len(set(not_ignored_fields)) < len(not_ignored_fields):
302            raise FatalCSVError(
303                "Double headers: each column name may only appear once.")
304        if mode == 'create':
305            if not 'faculty_code' in headerfields \
306                and not 'department_code' in headerfields :
307                raise FatalCSVError(
308                    "Need at least columns faculty_code and department_code")
309        return True
310
[5009]311    def parentsExist(self, row, site):
[8302]312        return self.getParent(row,site) is not None
[5009]313
314    def entryExists(self, row, site):
315        parent = self.getParent(row, site)
[8302]316        if parent is not None:
317            return row['code'] in parent.keys()
318        return False
[5009]319
320    def getParent(self, row, site):
[8302]321        if not 'faculties' in site.keys():
322            return None
323        # If both faculty and department codes are provided, use
324        # these to get parent.
325        if row.get('faculty_code',None) not in (None, IGNORE_MARKER) and \
326            row.get('department_code',None) not in (None, IGNORE_MARKER):
327            if not row['faculty_code'] in site['faculties'].keys():
328                return None
329            faculty = site['faculties'][row['faculty_code']]
330            if not row['department_code'] in faculty.keys():
331                return None
332            dept = faculty[row['department_code']]
333            return dept.certificates
334        # If department code or faculty code is missing,
335        # use catalog to get parent. Makes only sense in update mode but
336        # does also work in create mode.
337        cat = queryUtility(ICatalog, name='certificates_catalog')
338        results = list(
339            cat.searchResults(code=(row['code'], row['code'])))
340        if results:
341            return results[0].__parent__
342        return None
[5009]343
344    def getEntry(self, row, site):
345        parent = self.getParent(row, site)
[8302]346        if parent is not None:
347            return parent.get(row['code'])
348        return None
[5009]349
350    def addEntry(self, obj, row, site):
351        parent = self.getParent(row, site)
[6243]352        parent.addCertificate(obj)
353        return
[5009]354
355    def delEntry(self, row, site):
356        parent = self.getParent(row, site)
357        del parent[row['code']]
358        return
359
[8996]360class CertificateCourseProcessor(FacultyProcessor):
[5009]361    """A batch processor for ICertificateCourse objects.
362    """
[6628]363    grok.implements(IBatchProcessor)
[5009]364    grok.provides(IBatchProcessor)
365    grok.context(Interface)
[7933]366    util_name = 'certificatecourseprocessor'
[5009]367    grok.name(util_name)
368
[7933]369    name = u'CertificateCourse Processor'
[5009]370    iface = ICertificateCourse
371
372    location_fields = ['course', 'level', 'faculty_code', 'department_code',
373                       'certificate_code',]
374    factory_name = 'waeup.CertificateCourse'
375
[6628]376    mode = None
377
[5009]378    def parentsExist(self, row, site):
379        if not 'faculties' in site.keys():
380            return False
381        if not row['faculty_code'] in site['faculties'].keys():
382            return False
383        faculty = site['faculties'][row['faculty_code']]
384        if not row['department_code'] in faculty.keys():
385            return False
386        dept = faculty[row['department_code']]
387        return row['certificate_code'] in dept.certificates.keys()
388
389    def entryExists(self, row, site):
390        if not self.parentsExist(row, site):
391            return False
392        parent = self.getParent(row, site)
393        code = "%s_%s" % (row['course'].code, row['level'])
394        return code in parent.keys()
395
396    def getParent(self, row, site):
397        dept = site['faculties'][row['faculty_code']][row['department_code']]
398        return dept.certificates[row['certificate_code']]
399
400    def getEntry(self, row, site):
401        if not self.entryExists(row, site):
402            return None
403        parent = self.getParent(row, site)
[6628]404        return parent.get("%s_%s" % (row['course'].code, row['level']))
[5009]405
406    def addEntry(self, obj, row, site):
407        parent = self.getParent(row, site)
[8920]408        parent.addCertCourse(row['course'],
[7665]409                            row['level'], row['mandatory'])
[5009]410        return
411
412    def delEntry(self, row, site):
413        parent = self.getParent(row, site)
[8920]414        parent.delCertCourse(row['course'].code, row['level'])
[5009]415        return
Note: See TracBrowser for help on using the repository browser.