source: main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/batching.py @ 17787

Last change on this file since 17787 was 17787, checked in by Henrik Bettermann, 6 months ago

Add SessionConfigurationProcessor.
Add ConfigurationContainerProcessor.
Add ConfigurationContainerExporter.

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