source: main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/accesscodes/batching.py @ 17650

Last change on this file since 17650 was 12883, checked in by Henrik Bettermann, 10 years ago

More docs.

  • Property svn:keywords set to Id
File size: 8.6 KB
Line 
1## $Id: batching.py 12883 2015-04-25 06:13:18Z 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 accesscodes.
19
20"""
21import grok
22from hurry.workflow.interfaces import IWorkflowState, IWorkflowInfo
23from zope.interface import Interface
24from waeup.kofa.interfaces import (
25    IBatchProcessor, IGNORE_MARKER, IObjectHistory, IObjectConverter)
26from waeup.kofa.utils.batching import BatchProcessor
27from waeup.kofa.accesscodes.interfaces import IAccessCodeBatch, IAccessCode
28from waeup.kofa.accesscodes.workflow import INITIALIZED, USED, DISABLED
29
30IMPORTABLE_TRANSITIONS = (
31    'init', 'use', 'disable_used', 'disable_unused', 'reeanble')
32
33IMPORTABLE_STATES = (INITIALIZED, USED, DISABLED)
34
35class AccessCodeBatchProcessor(BatchProcessor):
36    """The Access Code Batch Processor imports containers for access codes.
37    It does not import their content. There is nothing special about this
38    processor.
39    """
40    grok.implements(IBatchProcessor)
41    grok.provides(IBatchProcessor)
42    grok.context(Interface)
43    util_name = 'accesscodebatchprocessor'
44    grok.name(util_name)
45
46    name = u'AccessCodeBatch Processor'
47    iface = IAccessCodeBatch
48
49    location_fields = ['batch_id',]
50    factory_name = 'waeup.AccessCodeBatch'
51
52    mode = None
53
54    def parentsExist(self, row, site):
55        return 'accesscodes' in site.keys()
56
57    def entryExists(self, row, site):
58        return row['batch_id'] in site['accesscodes'].keys()
59
60    def getParent(self, row, site):
61        return site['accesscodes']
62
63    def getEntry(self, row, site):
64        if not self.entryExists(row, site):
65            return None
66        parent = self.getParent(row, site)
67        return parent.get(row['batch_id'])
68
69    def addEntry(self, obj, row, site):
70        parent = self.getParent(row, site)
71        parent.addBatchByImport(obj, row['batch_id'])
72        return
73
74    def updateEntry(self, obj, row, site, filename):
75        """Update obj to the values given in row.
76
77        Returns a string describing the fields changed.
78        """
79        items_changed = super(AccessCodeBatchProcessor, self).updateEntry(
80            obj, row, site, filename)
81        # Log actions...
82        location_field = self.location_fields[0]
83        grok.getSite()['accesscodes'].logger.info(
84            '%s - %s - %s - updated: %s'
85            % (self.name, filename, row[location_field], items_changed))
86        return
87
88class AccessCodeProcessor(BatchProcessor):
89    """The Access Code Processor imports access codes (ac) into their
90    batches. Localization requires the `representation` of the ac
91    (object id) as well as  `batch_prefix` and `batch_num` to find
92    the parent container (the batch).
93
94    Access codes follow a workflow. The `checkConversion` method validates
95    the workflow `state` and `transition` in row. `checkUpdateRequirements`
96    checks if the transition is allowed. This depends on the context.
97    """
98    grok.implements(IBatchProcessor)
99    grok.provides(IBatchProcessor)
100    grok.context(Interface)
101    util_name = 'accesscodeprocessor'
102    grok.name(util_name)
103
104    name = u'AccessCode Processor'
105    iface = IAccessCode
106
107    location_fields = ['representation', 'batch_prefix', 'batch_num']
108    factory_name = 'waeup.AccessCode'
109
110    mode = None
111
112    @property
113    def available_fields(self):
114        return sorted(list(set(
115                    self.location_fields + [
116                        'random_num', 'owner', 'cost',
117                        'state', 'transition', 'batch_serial',]
118                    )))
119
120    @property
121    def required_fields(self):
122        return ['random_num', 'batch_serial', 'history',]
123
124    def parentsExist(self, row, site):
125        return self.getParent(row,site) is not None
126
127    def entryExists(self, row, site):
128        parent = self.getParent(row, site)
129        if parent is None:
130            return False
131        return row['representation'] in parent.keys()
132
133    def getParent(self, row, site):
134        if not 'accesscodes' in site.keys():
135            return None
136        batch_id = '%s-%s' % (row['batch_prefix'], row['batch_num'])
137        return site['accesscodes'].get(batch_id, None)
138
139    def getEntry(self, row, site):
140        if not self.entryExists(row, site):
141            return None
142        parent = self.getParent(row, site)
143        return parent.get(row['representation'], None)
144
145    def addEntry(self, obj, row, site):
146        parent = self.getParent(row, site)
147        parent[row['representation']] = obj
148        return
149
150    def checkConversion(self, row, mode='create'):
151        """Validates all values in row.
152        """
153        converter = IObjectConverter(self.iface)
154        errs, inv_errs, conv_dict =  converter.fromStringDict(
155            row, self.factory_name, mode=mode)
156        if 'transition' in row and 'state' in row:
157            if row['transition'] not in (IGNORE_MARKER, '') and \
158                row['state'] not in (IGNORE_MARKER, ''):
159                errs.append(('workflow','not allowed'))
160                return errs, inv_errs, conv_dict
161        if 'transition' in row:
162            if row['transition'] not in IMPORTABLE_TRANSITIONS:
163                if row['transition'] not in (IGNORE_MARKER, ''):
164                    errs.append(('transition','not allowed'))
165        if 'state' in row:
166            if row['state'] not in IMPORTABLE_STATES:
167                if row['state'] not in (IGNORE_MARKER, ''):
168                    errs.append(('state','not allowed'))
169                else:
170                    # State is an attribute of AccessCode and must not
171                    # be changed if empty.
172                    conv_dict['state'] = IGNORE_MARKER
173        return errs, inv_errs, conv_dict
174
175    def checkUpdateRequirements(self, obj, row, site):
176        """Checks requirements the object must fulfill when being updated.
177
178        This method is not used in case of deleting or adding objects.
179
180        Returns error messages as strings in case of requirement
181        problems.
182        """
183        transition = row.get('transition', IGNORE_MARKER)
184        if transition not in (IGNORE_MARKER, ''):
185            allowed_transitions = IWorkflowInfo(obj).getManualTransitionIds()
186            if transition not in allowed_transitions:
187                return 'Transition not allowed.'
188        return None
189
190    def updateEntry(self, obj, row, site, filename):
191        """Update obj to the values given in row.
192
193        Returns a string describing the fields changed.
194        """
195        items_changed = ''
196        # Update state
197        # Attention: When importing states the counters remain unchanged.
198        if 'state' in row:
199            state = row.get('state', IGNORE_MARKER)
200            if state not in (IGNORE_MARKER, ''):
201                value = row['state']
202                IWorkflowState(obj).setState(value)
203                items_changed += ('%s=%s, ' % ('state', state))
204                msg = "state '%s' set" % state
205                IObjectHistory(obj).addMessage(msg)
206            row.pop('state')
207        # Trigger transition. Counters are properly changed.
208        if 'transition' in row:
209            transition = row.get('transition', IGNORE_MARKER)
210            if transition not in (IGNORE_MARKER, ''):
211                value = row['transition']
212                IWorkflowInfo(obj).fireTransition(value)
213                items_changed += ('%s=%s, ' % ('transition', transition))
214            row.pop('transition')
215
216        # In import files we can use the hash symbol at the end of a
217        # random_num string to avoid annoying automatic number transformation
218        # by Excel or Calc
219        if 'random_num' in row:
220            random_num = row.get('random_num', IGNORE_MARKER)
221            if random_num not in (IGNORE_MARKER, ''):
222                row['random_num'] = random_num.strip('#')
223
224        items_changed += super(AccessCodeProcessor, self).updateEntry(
225            obj, row, site, filename)
226
227        # Log actions...
228        grok.getSite()['accesscodes'].logger.info(
229            '%s - %s - %s - updated: %s'
230            % (self.name, filename, row['representation'], items_changed))
231        return
Note: See TracBrowser for help on using the repository browser.