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

Last change on this file since 8312 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
RevLine 
[7192]1## $Id: batching.py 8290 2012-04-26 17:01:32Z henrik $
[5321]2##
[7192]3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
[5321]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.
[7192]8##
[5321]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.
[7192]13##
[5321]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"""
[7268]20import csv
[5321]21import grok
[7271]22from zope.schema import getFields
[5321]23from zope.interface import Interface
[7268]24from zope.component import queryUtility
[8290]25from hurry.workflow.interfaces import IWorkflowState
[7268]26from zope.catalog.interfaces import ICatalog
[7811]27from waeup.kofa.interfaces import (
[8290]28    IBatchProcessor, IObjectConverter, FatalCSVError, IGNORE_MARKER,
29    IObjectHistory, IUserAccount)
30from waeup.kofa.interfaces import MessageFactory as _
[7811]31from waeup.kofa.utils.batching import BatchProcessor
32from waeup.kofa.applicants.interfaces import (
[7268]33    IApplicantsContainer, IApplicant, IApplicantUpdateByRegNo)
[8290]34from waeup.kofa.applicants.workflow import  IMPORTABLE_STATES
[5321]35
[7933]36class ApplicantsContainerProcessor(BatchProcessor):
37    """A processor for applicants containers.
[5321]38    """
[5474]39    grok.implements(IBatchProcessor)
[5321]40    grok.provides(IBatchProcessor)
41    grok.context(Interface)
[7933]42    util_name = 'applicants container processor'
[5321]43    grok.name(util_name)
44
[7933]45    name = u'Applicants Container Processor'
[5475]46    mode = u'create'
[6251]47    iface = IApplicantsContainer
[5321]48
[6251]49    location_fields = ['code',]
[6282]50    factory_name = 'waeup.ApplicantsContainer'
[5321]51
52    def parentsExist(self, row, site):
[6251]53        return 'applicants' in site.keys()
[5321]54
55    def entryExists(self, row, site):
[6251]56        return row['code'] in site['applicants'].keys()
[5321]57
58    def getParent(self, row, site):
[6251]59        return site['applicants']
[5321]60
61    def getEntry(self, row, site):
62        if not self.entryExists(row, site):
63            return None
64        parent = self.getParent(row, site)
[6251]65        return parent.get(row['code'])
[5321]66
67    def addEntry(self, obj, row, site):
68        parent = self.getParent(row, site)
[6251]69        parent[row['code']] = obj
[5321]70        return
71
72    def delEntry(self, row, site):
73        parent = self.getParent(row, site)
[6251]74        del parent[row['code']]
[5321]75        return
[7262]76
[7933]77class ApplicantProcessor(BatchProcessor):
[7262]78    """A batch processor for IApplicant objects.
79    """
80    grok.implements(IBatchProcessor)
81    grok.provides(IBatchProcessor)
82    grok.context(Interface)
[7933]83    util_name = 'applicantprocessor'
[7262]84    grok.name(util_name)
[7933]85    name = u'Applicant Processor'
[7262]86    iface = IApplicant
[8008]87    location_fields = ['container_code']
[7262]88    factory_name = 'waeup.Applicant'
89
90    mode = None
91
92    @property
[7268]93    def available_fields(self):
94        return sorted(list(set(
[8290]95            ['application_number','reg_number',
96            'container_code','state','password'] + getFields(
[7268]97                self.iface).keys())))
[7262]98
[7268]99    def checkHeaders(self, headerfields, mode='create'):
[7270]100        if not 'reg_number' in headerfields and not 'application_number' \
[7268]101            in headerfields:
102            raise FatalCSVError(
[7270]103                "Need at least columns application_number or reg_number ")
[7268]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
[7262]117
[7268]118    def getLocator(self, row):
[8236]119        if row.get('application_number', None) not in (IGNORE_MARKER, None):
[7268]120            return 'application_number'
[8236]121        elif row.get('reg_number', None) not in (IGNORE_MARKER, None):
[7270]122            return 'reg_number'
[7268]123        else:
124            return None
[7262]125
126    def getParent(self, row, site):
[7268]127        if not 'applicants' in site.keys():
128            return False
[7262]129        return site['applicants'][row['container_code']]
130
[7268]131    def parentsExist(self, row, site):
132        return self.getParent(row, site) is not None
133
[7262]134    def getEntry(self, row, site):
[7264]135        if not self.parentsExist(row, site):
136            return None
137        parent = self.getParent(row, site)
[7268]138        if self.getLocator(row) == 'application_number':
139            if row['application_number'] in parent:
140                applicant = parent[row['application_number']]
141                return applicant
[7270]142        elif self.getLocator(row) == 'reg_number':
143            reg_number = row['reg_number']
[7268]144            cat = queryUtility(ICatalog, name='applicants_catalog')
145            results = list(
[7270]146                cat.searchResults(reg_number=(reg_number, reg_number)))
[7268]147            if results:
148                return results[0]
149        return None
[7262]150
[7268]151    def entryExists(self, row, site):
152        return self.getEntry(row, site) is not None
153
[7262]154    def addEntry(self, obj, row, site):
155        parent = self.getParent(row, site)
156        parent.addApplicant(obj)
[8290]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'))
[7262]164        return
165
166    def delEntry(self, row, site):
[7268]167        applicant = self.getEntry(row, site)
168        if applicant is not None:
[7262]169            parent = self.getParent(row, site)
[7268]170            del parent[applicant.application_number]
[7262]171        pass
[7268]172
[8290]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
[7268]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',
[7270]230                'application_number', 'reg_number'] and mode == 'remove':
[7268]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']:
[7270]242            if self.getLocator(row) == 'reg_number':
[7268]243                iface = IApplicantUpdateByRegNo
244        else:
245            iface = self.iface
246        converter = IObjectConverter(iface)
247        errs, inv_errs, conv_dict =  converter.fromStringDict(
[8223]248            row, self.factory_name, mode=mode)
[8290]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
[7268]260        return errs, inv_errs, conv_dict
Note: See TracBrowser for help on using the repository browser.