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

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

Import student password as well. This is crucial for upcoming portal migration.

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