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

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

Add test_certificate_removed.

Use phone widget also in ApplicantEditFormPage?.

  • Property svn:keywords set to Id
File size: 9.3 KB
RevLine 
[7191]1## $Id: batching.py 7433 2011-12-22 07:05:32Z 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 (
34    IBatchProcessor, FatalCSVError, IObjectConverter)
[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(
60            ['student_id','reg_number','matric_number'] + getFields(
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
[6849]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):
[6854]146            if field not in [
147                'student_id', 'reg_number', 'matric_number'] and mode == 'remove':
[6849]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
[6825]170class StudentStudyCourseProcessor(BatchProcessor):
171    """A batch processor for IStudentStudyCourse objects.
172    """
173    grok.implements(IBatchProcessor)
174    grok.provides(IBatchProcessor)
175    grok.context(Interface)
[6837]176    util_name = 'studycourseupdater'
[6825]177    grok.name(util_name)
178
[6837]179    name = u'StudentStudyCourse Importer (update only)'
[6825]180    iface = IStudentStudyCourseImport
181    factory_name = 'waeup.StudentStudyCourse'
182
[6849]183    location_fields = []
184
[6841]185    mode = None
186
[6825]187    @property
188    def available_fields(self):
189        return sorted(list(set(
[6843]190            ['student_id','reg_number','matric_number'] + getFields(
191                self.iface).keys())))
[6825]192
[6837]193    def checkHeaders(self, headerfields, mode='ignore'):
[6854]194        if not 'reg_number' in headerfields and not 'student_id' \
195            in headerfields and not 'matric_number' in headerfields:
[6825]196            raise FatalCSVError(
[6854]197                "Need at least columns student_id " +
198                "or reg_number or matric_number for import!")
[6834]199        # Check for fields to be ignored...
[6825]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
[7267]207    def getParent(self, row, site):
[6846]208        if not 'students' in site.keys():
[6849]209            return None
[6846]210        if 'student_id' in row.keys() and row['student_id']:
[6825]211            if row['student_id'] in site['students']:
212                student = site['students'][row['student_id']]
213                return student
[6843]214        elif 'reg_number' in row.keys() and row['reg_number']:
[6825]215            reg_number = row['reg_number']
[6849]216            #import pdb; pdb.set_trace()
[6825]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]
[6843]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]
[6849]229        return None
[6825]230
[7267]231    def parentsExist(self, row, site):
232        return self.getParent(row, site) is not None
233
[6825]234    def entryExists(self, row, site):
[7267]235        student = self.getParent(row, site)
[6825]236        if not student:
[6849]237            return None
[6825]238        if 'studycourse' in student:
239            return student
[6849]240        return None
[6825]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')
[7429]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.