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

Last change on this file since 11891 was 11891, checked in by Henrik Bettermann, 10 years ago

Enable localization of batch processing modules.

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