source: main/waeup.sirp/trunk/src/waeup/sirp/students/batching.py @ 7518

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

Enable import of student registration states which are in IMPORTABLE_STATES.

Set workflow state directly rather than firing transitions.

  • Property svn:keywords set to Id
File size: 10.1 KB
Line 
1## $Id: batching.py 7513 2012-01-25 17:47: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##
18"""Batch processing components for student 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 students specific objects like
24students, studycourses, payment tickets and accommodation tickets.
25"""
26import grok
27import csv
28from zope.interface import Interface
29from zope.schema import getFields
30from zope.component import queryUtility
31from zope.event import notify
32from zope.catalog.interfaces import ICatalog
33from hurry.workflow.interfaces import IWorkflowState
34from waeup.sirp.interfaces import (
35    IBatchProcessor, FatalCSVError, IObjectConverter, IUserAccount)
36from waeup.sirp.students.interfaces import (
37    IStudent, IStudentStudyCourseImport,
38    IStudentUpdateByRegNo, IStudentUpdateByMatricNo)
39from waeup.sirp.students.workflow import  IMPORTABLE_STATES
40from waeup.sirp.utils.batching import BatchProcessor
41
42class StudentProcessor(BatchProcessor):
43    """A batch processor for IStudent objects.
44    """
45    grok.implements(IBatchProcessor)
46    grok.provides(IBatchProcessor)
47    grok.context(Interface)
48    util_name = 'studentimporter'
49    grok.name(util_name)
50
51    name = u'Student Importer'
52    iface = IStudent
53
54    location_fields = []
55    factory_name = 'waeup.Student'
56
57    mode = None
58
59    @property
60    def available_fields(self):
61        return sorted(list(set(
62            ['student_id','reg_number','matric_number',
63            'password', 'reg_state'] + getFields(
64                self.iface).keys())))
65
66    def checkHeaders(self, headerfields, mode='create'):
67        if not 'reg_number' in headerfields and not 'student_id' \
68            in headerfields and not 'matric_number' in headerfields:
69            raise FatalCSVError(
70                "Need at least columns student_id or reg_number " +
71                "or matric_number for import!")
72        if mode == 'create':
73            for field in self.required_fields:
74                if not field in headerfields:
75                    raise FatalCSVError(
76                        "Need at least columns %s for import!" %
77                        ', '.join(["'%s'" % x for x in self.required_fields]))
78        # Check for fields to be ignored...
79        not_ignored_fields = [x for x in headerfields
80                              if not x.startswith('--')]
81        if len(set(not_ignored_fields)) < len(not_ignored_fields):
82            raise FatalCSVError(
83                "Double headers: each column name may only appear once.")
84        return True
85
86    def parentsExist(self, row, site):
87        return 'students' in site.keys()
88
89    def getLocator(self, row):
90        if row.get('student_id',None):
91            return 'student_id'
92        elif row.get('reg_number',None):
93            return 'reg_number'
94        elif row.get('matric_number',None):
95            return 'matric_number'
96        else:
97            return None
98
99    # The entry never exists in create mode.
100    def entryExists(self, row, site):
101        return self.getEntry(row, site) is not None
102
103    def getParent(self, row, site):
104        return site['students']
105
106    def getEntry(self, row, site):
107        if not 'students' in site.keys():
108            return None
109        if self.getLocator(row) == 'student_id':
110            if row['student_id'] in site['students']:
111                student = site['students'][row['student_id']]
112                return student
113        elif self.getLocator(row) == 'reg_number':
114            reg_number = row['reg_number']
115            cat = queryUtility(ICatalog, name='students_catalog')
116            results = list(
117                cat.searchResults(reg_number=(reg_number, reg_number)))
118            if results:
119                return results[0]
120        elif self.getLocator(row) == 'matric_number':
121            matric_number = row['matric_number']
122            cat = queryUtility(ICatalog, name='students_catalog')
123            results = list(
124                cat.searchResults(matric_number=(matric_number, matric_number)))
125            if results:
126                return results[0]
127        return None
128
129       
130    def addEntry(self, obj, row, site):
131        parent = self.getParent(row, site)
132        parent.addStudent(obj)
133        return
134
135    def delEntry(self, row, site):
136        student = self.getEntry(row, site)
137        if student is not None:
138            parent = self.getParent(row, site)
139            del parent[student.student_id]
140        pass
141
142    def updateEntry(self, obj, row, site):
143        """Update obj to the values given in row.
144        """
145        for key, value in row.items():
146            # Set student password and all fields declared in interface.
147            if key == 'password':
148                IUserAccount(obj).setPassword(value)
149            elif key == 'reg_state':
150                IWorkflowState(obj).setState(value)
151            elif hasattr(obj, key):
152                setattr(obj, key, value)
153        return
154
155    def getMapping(self, path, headerfields, mode):
156        """Get a mapping from CSV file headerfields to actually used fieldnames.
157        """
158        result = dict()
159        reader = csv.reader(open(path, 'rb'))
160        raw_header = reader.next()
161        for num, field in enumerate(headerfields):
162            if field not in [
163                'student_id', 'reg_number', 'matric_number'] and mode == 'remove':
164                continue
165            if field == u'--IGNORE--':
166                # Skip ignored columns in failed and finished data files.
167                continue
168            result[raw_header[num]] = field
169        return result
170
171    def checkConversion(self, row, mode='create'):
172        """Validates all values in row.
173        """
174        if mode in ['update', 'remove']:
175            if self.getLocator(row) == 'reg_number':
176                iface = IStudentUpdateByRegNo
177            elif self.getLocator(row) == 'matric_number':
178                iface = IStudentUpdateByMatricNo
179        else:
180            iface = self.iface
181        converter = IObjectConverter(iface)
182        errs, inv_errs, conv_dict =  converter.fromStringDict(
183            row, self.factory_name)
184        if row.has_key('reg_state') and \
185            row['reg_state'] is not None and \
186            not row['reg_state'] in IMPORTABLE_STATES:
187            errs.append(('reg_state','not allowed'))
188        return errs, inv_errs, conv_dict
189
190class StudentStudyCourseProcessor(BatchProcessor):
191    """A batch processor for IStudentStudyCourse objects.
192    """
193    grok.implements(IBatchProcessor)
194    grok.provides(IBatchProcessor)
195    grok.context(Interface)
196    util_name = 'studycourseupdater'
197    grok.name(util_name)
198
199    name = u'StudentStudyCourse Importer (update only)'
200    iface = IStudentStudyCourseImport
201    factory_name = 'waeup.StudentStudyCourse'
202
203    location_fields = []
204
205    mode = None
206
207    @property
208    def available_fields(self):
209        return sorted(list(set(
210            ['student_id','reg_number','matric_number'] + getFields(
211                self.iface).keys())))
212
213    def checkHeaders(self, headerfields, mode='ignore'):
214        if not 'reg_number' in headerfields and not 'student_id' \
215            in headerfields and not 'matric_number' in headerfields:
216            raise FatalCSVError(
217                "Need at least columns student_id " +
218                "or reg_number or matric_number for import!")
219        # Check for fields to be ignored...
220        not_ignored_fields = [x for x in headerfields
221                              if not x.startswith('--')]
222        if len(set(not_ignored_fields)) < len(not_ignored_fields):
223            raise FatalCSVError(
224                "Double headers: each column name may only appear once.")
225        return True
226
227    def getParent(self, row, site):
228        if not 'students' in site.keys():
229            return None
230        if 'student_id' in row.keys() and row['student_id']:
231            if row['student_id'] in site['students']:
232                student = site['students'][row['student_id']]
233                return student
234        elif 'reg_number' in row.keys() and row['reg_number']:
235            reg_number = row['reg_number']
236            #import pdb; pdb.set_trace()
237            cat = queryUtility(ICatalog, name='students_catalog')
238            results = list(
239                cat.searchResults(reg_number=(reg_number, reg_number)))
240            if results:
241                return results[0]
242        elif 'matric_number' in row.keys() and row['matric_number']:
243            matric_number = row['matric_number']
244            cat = queryUtility(ICatalog, name='students_catalog')
245            results = list(
246                cat.searchResults(matric_number=(matric_number, matric_number)))
247            if results:
248                return results[0]
249        return None
250
251    def parentsExist(self, row, site):
252        return self.getParent(row, site) is not None
253
254    def entryExists(self, row, site):
255        student = self.getParent(row, site)
256        if not student:
257            return None
258        if 'studycourse' in student:
259            return student
260        return None
261
262    def getEntry(self, row, site):
263        student = self.entryExists(row, site)
264        if not student:
265            return None
266        return student.get('studycourse')
267
268    def updateEntry(self, obj, row, site):
269        """Update obj to the values given in row.
270        """
271        for key, value in row.items():
272            # Skip fields not declared in interface.
273            if hasattr(obj, key):
274                setattr(obj, key, value)
275        # Update the students_catalog
276        notify(grok.ObjectModifiedEvent(obj.__parent__))
277        return
278
Note: See TracBrowser for help on using the repository browser.