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

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

Update students_catalog after importing a studycourse.

  • Property svn:keywords set to Id
File size: 9.3 KB
Line 
1## $Id: batching.py 7429 2011-12-21 15:06:34Z 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 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)
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'] + 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 getMapping(self, path, headerfields, mode):
140        """Get a mapping from CSV file headerfields to actually used fieldnames.
141        """
142        result = dict()
143        reader = csv.reader(open(path, 'rb'))
144        raw_header = reader.next()
145        for num, field in enumerate(headerfields):
146            if field not in [
147                'student_id', 'reg_number', 'matric_number'] and mode == 'remove':
148                continue
149            if field == u'--IGNORE--':
150                # Skip ignored columns in failed and finished data files.
151                continue
152            result[raw_header[num]] = field
153        return result
154
155    def checkConversion(self, row, mode='create'):
156        """Validates all values in row.
157        """
158        if mode in ['update', 'remove']:
159            if self.getLocator(row) == 'reg_number':
160                iface = IStudentUpdateByRegNo
161            elif self.getLocator(row) == 'matric_number':
162                iface = IStudentUpdateByMatricNo
163        else:
164            iface = self.iface
165        converter = IObjectConverter(iface)
166        errs, inv_errs, conv_dict =  converter.fromStringDict(
167            row, self.factory_name)
168        return errs, inv_errs, conv_dict
169
170class StudentStudyCourseProcessor(BatchProcessor):
171    """A batch processor for IStudentStudyCourse objects.
172    """
173    grok.implements(IBatchProcessor)
174    grok.provides(IBatchProcessor)
175    grok.context(Interface)
176    util_name = 'studycourseupdater'
177    grok.name(util_name)
178
179    name = u'StudentStudyCourse Importer (update only)'
180    iface = IStudentStudyCourseImport
181    factory_name = 'waeup.StudentStudyCourse'
182
183    location_fields = []
184
185    mode = None
186
187    @property
188    def available_fields(self):
189        return sorted(list(set(
190            ['student_id','reg_number','matric_number'] + getFields(
191                self.iface).keys())))
192
193    def checkHeaders(self, headerfields, mode='ignore'):
194        if not 'reg_number' in headerfields and not 'student_id' \
195            in headerfields and not 'matric_number' in headerfields:
196            raise FatalCSVError(
197                "Need at least columns student_id " +
198                "or reg_number or matric_number for import!")
199        # Check for fields to be ignored...
200        not_ignored_fields = [x for x in headerfields
201                              if not x.startswith('--')]
202        if len(set(not_ignored_fields)) < len(not_ignored_fields):
203            raise FatalCSVError(
204                "Double headers: each column name may only appear once.")
205        return True
206
207    def getParent(self, row, site):
208        if not 'students' in site.keys():
209            return None
210        if 'student_id' in row.keys() and row['student_id']:
211            if row['student_id'] in site['students']:
212                student = site['students'][row['student_id']]
213                return student
214        elif 'reg_number' in row.keys() and row['reg_number']:
215            reg_number = row['reg_number']
216            #import pdb; pdb.set_trace()
217            cat = queryUtility(ICatalog, name='students_catalog')
218            results = list(
219                cat.searchResults(reg_number=(reg_number, reg_number)))
220            if results:
221                return results[0]
222        elif 'matric_number' in row.keys() and row['matric_number']:
223            matric_number = row['matric_number']
224            cat = queryUtility(ICatalog, name='students_catalog')
225            results = list(
226                cat.searchResults(matric_number=(matric_number, matric_number)))
227            if results:
228                return results[0]
229        return None
230
231    def parentsExist(self, row, site):
232        return self.getParent(row, site) is not None
233
234    def entryExists(self, row, site):
235        student = self.getParent(row, site)
236        if not student:
237            return None
238        if 'studycourse' in student:
239            return student
240        return None
241
242    def getEntry(self, row, site):
243        student = self.entryExists(row, site)
244        if not student:
245            return None
246        return student.get('studycourse')
247
248    def updateEntry(self, obj, row, site):
249        """Update obj to the values given in row.
250        """
251        for key, value in row.items():
252            # Skip fields not declared in interface.
253            if hasattr(obj, key):
254                setattr(obj, key, value)
255        # Update the students_catalog
256        notify(grok.ObjectModifiedEvent(obj.__parent__))
257        return
258
Note: See TracBrowser for help on using the repository browser.