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

Last change on this file since 7502 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
RevLine 
[7191]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##
[7433]18"""Batch processing components for student objects.
[6821]19
20Batch processors eat CSV files to add, update or remove large numbers
21of certain kinds of objects at once.
22
[7261]23Here we define the processors for students specific objects like
24students, studycourses, payment tickets and accommodation tickets.
[6821]25"""
26import grok
[6849]27import csv
[6821]28from zope.interface import Interface
[6825]29from zope.schema import getFields
30from zope.component import queryUtility
[7429]31from zope.event import notify
[6825]32from zope.catalog.interfaces import ICatalog
[6849]33from waeup.sirp.interfaces import (
[7497]34    IBatchProcessor, FatalCSVError, IObjectConverter, IUserAccount)
[6825]35from waeup.sirp.students.interfaces import (
[7256]36    IStudent, IStudentStudyCourseImport,
[6849]37    IStudentUpdateByRegNo, IStudentUpdateByMatricNo)
[6821]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
[6849]52    location_fields = []
[6821]53    factory_name = 'waeup.Student'
54
[6841]55    mode = None
56
[6821]57    @property
[6849]58    def available_fields(self):
59        return sorted(list(set(
[7497]60            ['student_id','reg_number','matric_number','password'] + getFields(
[6849]61                self.iface).keys())))
[6821]62
[6849]63    def checkHeaders(self, headerfields, mode='create'):
[6854]64        if not 'reg_number' in headerfields and not 'student_id' \
65            in headerfields and not 'matric_number' in headerfields:
[6849]66            raise FatalCSVError(
[6854]67                "Need at least columns student_id or reg_number " +
68                "or matric_number for import!")
[6849]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
[6821]83    def parentsExist(self, row, site):
84        return 'students' in site.keys()
85
[6849]86    def getLocator(self, row):
[7269]87        if row.get('student_id',None):
[6849]88            return 'student_id'
[7269]89        elif row.get('reg_number',None):
[6849]90            return 'reg_number'
[7269]91        elif row.get('matric_number',None):
[6849]92            return 'matric_number'
93        else:
94            return None
95
[6821]96    # The entry never exists in create mode.
97    def entryExists(self, row, site):
[7267]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):
[6846]104        if not 'students' in site.keys():
[6849]105            return None
106        if self.getLocator(row) == 'student_id':
[6846]107            if row['student_id'] in site['students']:
108                student = site['students'][row['student_id']]
109                return student
[6849]110        elif self.getLocator(row) == 'reg_number':
[6846]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]
[6849]117        elif self.getLocator(row) == 'matric_number':
[6846]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]
[6849]124        return None
[6821]125
[7267]126       
[6821]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):
[7267]133        student = self.getEntry(row, site)
[7263]134        if student is not None:
[6846]135            parent = self.getParent(row, site)
136            del parent[student.student_id]
[6821]137        pass
[6825]138
[7497]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
[6849]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):
[6854]157            if field not in [
158                'student_id', 'reg_number', 'matric_number'] and mode == 'remove':
[6849]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
[6825]181class StudentStudyCourseProcessor(BatchProcessor):
182    """A batch processor for IStudentStudyCourse objects.
183    """
184    grok.implements(IBatchProcessor)
185    grok.provides(IBatchProcessor)
186    grok.context(Interface)
[6837]187    util_name = 'studycourseupdater'
[6825]188    grok.name(util_name)
189
[6837]190    name = u'StudentStudyCourse Importer (update only)'
[6825]191    iface = IStudentStudyCourseImport
192    factory_name = 'waeup.StudentStudyCourse'
193
[6849]194    location_fields = []
195
[6841]196    mode = None
197
[6825]198    @property
199    def available_fields(self):
200        return sorted(list(set(
[6843]201            ['student_id','reg_number','matric_number'] + getFields(
202                self.iface).keys())))
[6825]203
[6837]204    def checkHeaders(self, headerfields, mode='ignore'):
[6854]205        if not 'reg_number' in headerfields and not 'student_id' \
206            in headerfields and not 'matric_number' in headerfields:
[6825]207            raise FatalCSVError(
[6854]208                "Need at least columns student_id " +
209                "or reg_number or matric_number for import!")
[6834]210        # Check for fields to be ignored...
[6825]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
[7267]218    def getParent(self, row, site):
[6846]219        if not 'students' in site.keys():
[6849]220            return None
[6846]221        if 'student_id' in row.keys() and row['student_id']:
[6825]222            if row['student_id'] in site['students']:
223                student = site['students'][row['student_id']]
224                return student
[6843]225        elif 'reg_number' in row.keys() and row['reg_number']:
[6825]226            reg_number = row['reg_number']
[6849]227            #import pdb; pdb.set_trace()
[6825]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]
[6843]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]
[6849]240        return None
[6825]241
[7267]242    def parentsExist(self, row, site):
243        return self.getParent(row, site) is not None
244
[6825]245    def entryExists(self, row, site):
[7267]246        student = self.getParent(row, site)
[6825]247        if not student:
[6849]248            return None
[6825]249        if 'studycourse' in student:
250            return student
[6849]251        return None
[6825]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')
[7429]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.