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

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

Reorganize course batch processing. Use courses_catalog for getting entry in update mode.

  • Property svn:keywords set to Id
File size: 14.0 KB
Line 
1## $Id: batching.py 9333 2012-10-14 04:54:11Z 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##
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
28from zope.component import queryUtility
29from zope.schema import getFields
30from zope.catalog.interfaces import ICatalog
31from zope.event import notify
32from zope.securitypolicy.interfaces import (
33    IPrincipalRoleManager, IPrincipalRoleMap)
34from waeup.kofa.authentication import LocalRoleSetEvent
35from waeup.kofa.interfaces import (
36    IBatchProcessor, IGNORE_MARKER, DELETION_MARKER, FatalCSVError)
37from waeup.kofa.university.interfaces import (
38    IFacultiesContainer, IFaculty, ICourse, IDepartment, ICertificate,
39    ICertificateCourse)
40from waeup.kofa.university import (
41    Faculty, Department, Course, Certificate)
42from waeup.kofa.utils.batching import BatchProcessor
43
44class FacultyProcessor(BatchProcessor):
45    """A batch processor for IFaculty objects.
46    """
47    grok.implements(IBatchProcessor)
48    grok.provides(IBatchProcessor)
49    grok.context(Interface)
50    util_name = 'facultyprocessor'
51    grok.name(util_name)
52
53    name = u'Faculty Processor'
54    iface = IFaculty
55    allowed_roles = Faculty.local_roles
56
57    location_fields = ['code',]
58    factory_name = 'waeup.Faculty'
59
60    mode = None
61
62    def parentsExist(self, row, site):
63        return 'faculties' in site.keys()
64
65    @property
66    def available_fields(self):
67        fields = getFields(self.iface)
68        return sorted(list(set(
69            self.location_fields + fields.keys() + ['local_roles']
70            )))
71
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'])
83
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
94    def updateEntry(self, obj, row, site):
95        """Update obj to the values given in row.
96        """
97        items_changed = ''
98
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)))
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        location_field = self.location_fields[0]
126        grok.getSite().logger.info('%s - %s - Record updated: %s'
127            % (self.name, row[location_field], items_changed))
128        return items_changed
129
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'):
136            if row['local_roles'] in (None, DELETION_MARKER, IGNORE_MARKER):
137                return errs, inv_errs, conv_dict
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
165class DepartmentProcessor(FacultyProcessor):
166    """A batch processor for IDepartment objects.
167    """
168    grok.implements(IBatchProcessor)
169    grok.provides(IBatchProcessor)
170    grok.context(Interface)
171    util_name = 'departmentprocessor'
172    grok.name(util_name)
173
174    name = u'Department Processor'
175    iface = IDepartment
176    allowed_roles = Department.local_roles
177
178    location_fields = ['code', 'faculty_code']
179    factory_name = 'waeup.Department'
180
181    mode = None
182
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'])
202
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
213class CertificateProcessor(FacultyProcessor):
214    """A batch processor for ICertificate objects.
215    """
216    grok.implements(IBatchProcessor)
217    grok.provides(IBatchProcessor)
218    grok.context(Interface)
219    util_name = 'certificateprocessor'
220    grok.name(util_name)
221
222    name = u'Certificate Processor'
223    iface = ICertificate
224    allowed_roles = Certificate.local_roles
225
226    location_fields = ['code']
227    factory_name = 'waeup.Certificate'
228
229    mode = None
230
231    @property
232    def available_fields(self):
233        fields = getFields(self.iface)
234        return sorted(list(set(
235            ['faculty_code','department_code'] + fields.keys()
236            + ['local_roles'])))
237
238    def checkHeaders(self, headerfields, mode='create'):
239        super(CertificateProcessor, self).checkHeaders(headerfields, mode)
240        if mode == 'create':
241            if not 'faculty_code' in headerfields \
242                and not 'department_code' in headerfields :
243                raise FatalCSVError(
244                    "Need at least columns faculty_code and department_code")
245        return True
246
247    def parentsExist(self, row, site):
248        return self.getParent(row,site) is not None
249
250    def entryExists(self, row, site):
251        parent = self.getParent(row, site)
252        if parent is not None:
253            return row['code'] in parent.keys()
254        return False
255
256    def getParent(self, row, site):
257        if not 'faculties' in site.keys():
258            return None
259        # If both faculty and department codes are provided, use
260        # these to get parent.
261        if row.get('faculty_code',None) not in (None, IGNORE_MARKER) and \
262            row.get('department_code',None) not in (None, IGNORE_MARKER):
263            if not row['faculty_code'] in site['faculties'].keys():
264                return None
265            faculty = site['faculties'][row['faculty_code']]
266            if not row['department_code'] in faculty.keys():
267                return None
268            dept = faculty[row['department_code']]
269            return dept.certificates
270        # If department code or faculty code is missing,
271        # use catalog to get parent. Makes only sense in update mode but
272        # does also work in create mode.
273        cat = queryUtility(ICatalog, name='certificates_catalog')
274        results = list(
275            cat.searchResults(code=(row['code'], row['code'])))
276        if results:
277            return results[0].__parent__
278        return None
279
280    def getEntry(self, row, site):
281        parent = self.getParent(row, site)
282        if parent is not None:
283            return parent.get(row['code'])
284        return None
285
286    def addEntry(self, obj, row, site):
287        parent = self.getParent(row, site)
288        parent.addCertificate(obj)
289        return
290
291    def delEntry(self, row, site):
292        parent = self.getParent(row, site)
293        del parent[row['code']]
294        return
295
296class CourseProcessor(CertificateProcessor):
297    """A batch processor for ICourse objects.
298    """
299    grok.implements(IBatchProcessor)
300    grok.provides(IBatchProcessor)
301    grok.context(Interface)
302    util_name = 'courseprocessor'
303    grok.name(util_name)
304
305    name = u'Course Processor'
306    iface = ICourse
307    allowed_roles = Course.local_roles
308
309    location_fields = ['code']
310    factory_name = 'waeup.Course'
311
312    mode = None
313
314    def getParent(self, row, site):
315        if not 'faculties' in site.keys():
316            return None
317        # If both faculty and department codes are provided, use
318        # these to get parent.
319        if row.get('faculty_code',None) not in (None, IGNORE_MARKER) and \
320            row.get('department_code',None) not in (None, IGNORE_MARKER):
321            if not row['faculty_code'] in site['faculties'].keys():
322                return None
323            faculty = site['faculties'][row['faculty_code']]
324            if not row['department_code'] in faculty.keys():
325                return None
326            dept = faculty[row['department_code']]
327            return dept.courses
328        # If department code or faculty code is missing,
329        # use catalog to get parent. Makes only sense in update mode but
330        # does also work in create mode.
331        cat = queryUtility(ICatalog, name='courses_catalog')
332        results = list(
333            cat.searchResults(code=(row['code'], row['code'])))
334        if results:
335            return results[0].__parent__
336        return None
337
338    def addEntry(self, obj, row, site):
339        parent = self.getParent(row, site)
340        parent.addCourse(obj)
341        return
342
343class CertificateCourseProcessor(FacultyProcessor):
344    """A batch processor for ICertificateCourse objects.
345    """
346    grok.implements(IBatchProcessor)
347    grok.provides(IBatchProcessor)
348    grok.context(Interface)
349    util_name = 'certificatecourseprocessor'
350    grok.name(util_name)
351
352    name = u'CertificateCourse Processor'
353    iface = ICertificateCourse
354
355    location_fields = ['course', 'level', 'faculty_code', 'department_code',
356                       'certificate_code',]
357    factory_name = 'waeup.CertificateCourse'
358
359    mode = None
360
361    def parentsExist(self, row, site):
362        if not 'faculties' in site.keys():
363            return False
364        if not row['faculty_code'] in site['faculties'].keys():
365            return False
366        faculty = site['faculties'][row['faculty_code']]
367        if not row['department_code'] in faculty.keys():
368            return False
369        dept = faculty[row['department_code']]
370        return row['certificate_code'] in dept.certificates.keys()
371
372    def entryExists(self, row, site):
373        if not self.parentsExist(row, site):
374            return False
375        parent = self.getParent(row, site)
376        code = "%s_%s" % (row['course'].code, row['level'])
377        return code in parent.keys()
378
379    def getParent(self, row, site):
380        dept = site['faculties'][row['faculty_code']][row['department_code']]
381        return dept.certificates[row['certificate_code']]
382
383    def getEntry(self, row, site):
384        if not self.entryExists(row, site):
385            return None
386        parent = self.getParent(row, site)
387        return parent.get("%s_%s" % (row['course'].code, row['level']))
388
389    def addEntry(self, obj, row, site):
390        parent = self.getParent(row, site)
391        parent.addCertCourse(row['course'],
392                            row['level'], row['mandatory'])
393        return
394
395    def delEntry(self, row, site):
396        parent = self.getParent(row, site)
397        parent.delCertCourse(row['course'].code, row['level'])
398        return
Note: See TracBrowser for help on using the repository browser.