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

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

Fix test and simplify if statements in batching.py.

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