source: main/waeup.kofa/trunk/src/waeup/kofa/applicants/batching.py @ 8290

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

Enable import of password and state. When application_number is given in create mode, construct applicant_id accordingly (do not use id generator) and store applicant with this application_number in the respective container. Attention: application_number can be any string.

More tests will follow.

  • Property svn:keywords set to Id
File size: 9.6 KB
Line 
1## $Id: batching.py 8290 2012-04-26 17:01: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##
18"""Batch processing for applicants.
19"""
20import csv
21import grok
22from zope.schema import getFields
23from zope.interface import Interface
24from zope.component import queryUtility
25from hurry.workflow.interfaces import IWorkflowState
26from zope.catalog.interfaces import ICatalog
27from waeup.kofa.interfaces import (
28    IBatchProcessor, IObjectConverter, FatalCSVError, IGNORE_MARKER,
29    IObjectHistory, IUserAccount)
30from waeup.kofa.interfaces import MessageFactory as _
31from waeup.kofa.utils.batching import BatchProcessor
32from waeup.kofa.applicants.interfaces import (
33    IApplicantsContainer, IApplicant, IApplicantUpdateByRegNo)
34from waeup.kofa.applicants.workflow import  IMPORTABLE_STATES
35
36class ApplicantsContainerProcessor(BatchProcessor):
37    """A processor for applicants containers.
38    """
39    grok.implements(IBatchProcessor)
40    grok.provides(IBatchProcessor)
41    grok.context(Interface)
42    util_name = 'applicants container processor'
43    grok.name(util_name)
44
45    name = u'Applicants Container Processor'
46    mode = u'create'
47    iface = IApplicantsContainer
48
49    location_fields = ['code',]
50    factory_name = 'waeup.ApplicantsContainer'
51
52    def parentsExist(self, row, site):
53        return 'applicants' in site.keys()
54
55    def entryExists(self, row, site):
56        return row['code'] in site['applicants'].keys()
57
58    def getParent(self, row, site):
59        return site['applicants']
60
61    def getEntry(self, row, site):
62        if not self.entryExists(row, site):
63            return None
64        parent = self.getParent(row, site)
65        return parent.get(row['code'])
66
67    def addEntry(self, obj, row, site):
68        parent = self.getParent(row, site)
69        parent[row['code']] = obj
70        return
71
72    def delEntry(self, row, site):
73        parent = self.getParent(row, site)
74        del parent[row['code']]
75        return
76
77class ApplicantProcessor(BatchProcessor):
78    """A batch processor for IApplicant objects.
79    """
80    grok.implements(IBatchProcessor)
81    grok.provides(IBatchProcessor)
82    grok.context(Interface)
83    util_name = 'applicantprocessor'
84    grok.name(util_name)
85    name = u'Applicant Processor'
86    iface = IApplicant
87    location_fields = ['container_code']
88    factory_name = 'waeup.Applicant'
89
90    mode = None
91
92    @property
93    def available_fields(self):
94        return sorted(list(set(
95            ['application_number','reg_number',
96            'container_code','state','password'] + getFields(
97                self.iface).keys())))
98
99    def checkHeaders(self, headerfields, mode='create'):
100        if not 'reg_number' in headerfields and not 'application_number' \
101            in headerfields:
102            raise FatalCSVError(
103                "Need at least columns application_number or reg_number ")
104        if mode == 'create':
105            for field in self.required_fields:
106                if not field in headerfields:
107                    raise FatalCSVError(
108                        "Need at least columns %s for import!" %
109                        ', '.join(["'%s'" % x for x in self.required_fields]))
110        # Check for fields to be ignored...
111        not_ignored_fields = [x for x in headerfields
112                              if not x.startswith('--')]
113        if len(set(not_ignored_fields)) < len(not_ignored_fields):
114            raise FatalCSVError(
115                "Double headers: each column name may only appear once.")
116        return True
117
118    def getLocator(self, row):
119        if row.get('application_number', None) not in (IGNORE_MARKER, None):
120            return 'application_number'
121        elif row.get('reg_number', None) not in (IGNORE_MARKER, None):
122            return 'reg_number'
123        else:
124            return None
125
126    def getParent(self, row, site):
127        if not 'applicants' in site.keys():
128            return False
129        return site['applicants'][row['container_code']]
130
131    def parentsExist(self, row, site):
132        return self.getParent(row, site) is not None
133
134    def getEntry(self, row, site):
135        if not self.parentsExist(row, site):
136            return None
137        parent = self.getParent(row, site)
138        if self.getLocator(row) == 'application_number':
139            if row['application_number'] in parent:
140                applicant = parent[row['application_number']]
141                return applicant
142        elif self.getLocator(row) == 'reg_number':
143            reg_number = row['reg_number']
144            cat = queryUtility(ICatalog, name='applicants_catalog')
145            results = list(
146                cat.searchResults(reg_number=(reg_number, reg_number)))
147            if results:
148                return results[0]
149        return None
150
151    def entryExists(self, row, site):
152        return self.getEntry(row, site) is not None
153
154    def addEntry(self, obj, row, site):
155        parent = self.getParent(row, site)
156        parent.addApplicant(obj)
157        # We have to log this if state is provided. If not,
158        # logging is done by the event handler handle_applicant_added
159        if row.has_key('state'):
160            parent.__parent__.logger.info(
161            '%s - Application initialized' % obj.applicant_id)
162        history = IObjectHistory(obj)
163        history.addMessage(_('Application initialized'))
164        return
165
166    def delEntry(self, row, site):
167        applicant = self.getEntry(row, site)
168        if applicant is not None:
169            parent = self.getParent(row, site)
170            del parent[applicant.application_number]
171        pass
172
173    def updateEntry(self, obj, row, site):
174        """Update obj to the values given in row.
175        """
176        items_changed = ''
177
178        # Remove application_number from row if empty
179        if row.has_key('application_number') and row['application_number'] in (
180            None, IGNORE_MARKER):
181            row.pop('application_number')
182
183        # Update applicant_id fom application_number and container code
184        # if application_number is given
185        if row.has_key('application_number'):
186            obj.applicant_id = u'%s_%s' % (
187                row['container_code'], row['application_number'])
188            row.pop('application_number')
189
190        # Update password
191        passwd = row.get('password', IGNORE_MARKER)
192        if passwd not in ('', IGNORE_MARKER):
193            IUserAccount(obj).setPassword(passwd)
194            row.pop('password')
195
196        # Update registration state
197        state = row.get('state', IGNORE_MARKER)
198        if state not in (IGNORE_MARKER, ''):
199            value = row['state']
200            IWorkflowState(obj).setState(value)
201            msg = _("State '${a}' set", mapping = {'a':value})
202            history = IObjectHistory(obj)
203            history.addMessage(msg)
204            row.pop('state')
205
206        # apply other values...
207        items_changed = super(ApplicantProcessor, self).updateEntry(
208            obj, row, site)
209
210        # Log actions...
211        parent = self.getParent(row, site)
212        if hasattr(obj,'application_number'):
213            # Update mode: the applicant exists and we can get the applicant_id
214            parent.__parent__.logger.info(
215                '%s - Application record updated: %s'
216                % (obj.applicant_id, items_changed))
217        else:
218            # Create mode: the applicant does not yet exist
219            parent.logger.info('Application record imported: %s' % items_changed)
220        return items_changed
221
222    def getMapping(self, path, headerfields, mode):
223        """Get a mapping from CSV file headerfields to actually used fieldnames.
224        """
225        result = dict()
226        reader = csv.reader(open(path, 'rb'))
227        raw_header = reader.next()
228        for num, field in enumerate(headerfields):
229            if field not in ['container_code',
230                'application_number', 'reg_number'] and mode == 'remove':
231                continue
232            if field == u'--IGNORE--':
233                # Skip ignored columns in failed and finished data files.
234                continue
235            result[raw_header[num]] = field
236        return result
237
238    def checkConversion(self, row, mode='create'):
239        """Validates all values in row.
240        """
241        if mode in ['update', 'remove']:
242            if self.getLocator(row) == 'reg_number':
243                iface = IApplicantUpdateByRegNo
244        else:
245            iface = self.iface
246        converter = IObjectConverter(iface)
247        errs, inv_errs, conv_dict =  converter.fromStringDict(
248            row, self.factory_name, mode=mode)
249        if row.has_key('state') and \
250            not row['state'] in IMPORTABLE_STATES:
251            if row['state'] not in (IGNORE_MARKER, ''):
252                errs.append(('state','not allowed'))
253            else:
254                # state is an attribute of Applicant and must not
255                # be changed if empty
256                conv_dict['state'] = IGNORE_MARKER
257        application_number = row.get('application_number', IGNORE_MARKER)
258        if application_number in (IGNORE_MARKER, ''):
259                conv_dict['application_number'] = IGNORE_MARKER
260        return errs, inv_errs, conv_dict
Note: See TracBrowser for help on using the repository browser.