source: main/waeup.sirp/trunk/src/waeup/sirp/utils/converters.py @ 6272

Last change on this file since 6272 was 6263, checked in by uli, 14 years ago

Extend converter and create own interface for it.

File size: 11.1 KB
RevLine 
[4805]1"""Converters for zope.schema-based datatypes.
2"""
[5328]3import datetime
[4805]4import grok
[4810]5from zope.component import getMultiAdapter
6from zope.publisher.browser import TestRequest
7try:
8    from zope.app.form.browser.interfaces import ITerms
9except ImportError:
10    from zope.browser.interfaces import ITerms
[5328]11from zope.schema.interfaces import IBool, IText, IInt, IChoice, IDate
[4920]12from waeup.sirp.interfaces import ISchemaTypeConverter
[4805]13
[4834]14# If a string has this value, it is considered as 'missing_value' or None.
15NONE_STRING_VALUE = ''
16
[4810]17class Converter(grok.Adapter):
18    """Base for string-or-none to zope.schema field converters.
19    """
20    grok.baseclass()
21    grok.provides(ISchemaTypeConverter)
[6258]22
[4810]23    def __init__(self, context):
24        """Create a converter with context, a zope.schema.Field, as context.
25        """
26        self.context = context
27
28    def _convertValueFromString(self, string):
29        """You must at least override this method to build a working
30           converter.
31        """
32        raise NotImplementedError('method not implemented')
[6258]33
[4810]34    def fromString(self, string=None, strict=True):
35        """Convert ``string`` to value according to assigned field type.
36        """
37        result = None
[4834]38        if string is NONE_STRING_VALUE:
39            string = None
[4810]40        if string is None:
41            if self.context.required is True:
42                result = self.context.default
43            else:
44                result = self.context.missing_value
45        else:
46            result = self._convertValueFromString(string)
47        if strict:
48            self.context.validate(result)
49        return result
[4814]50
51    def _convertValueToString(self, value):
52        return str(value)
53
54    def toString(self, value, strict=True):
[5328]55        """Convert given `value` to string according to assigned field type.
56        """
[4814]57        if strict:
58            self.context.validate(value)
59        if value == self.context.missing_value:
60            return None
61        return self._convertValueToString(value)
62
63class BoolConverter(Converter):
[4834]64    """A converter for zope.schema.Bool fields.
65    """
[4814]66    grok.context(IBool)
67    grok.provides(ISchemaTypeConverter)
68
69    def _convertValueFromString(self, string):
[4834]70        if string is NONE_STRING_VALUE:
71            string = None
[4814]72        if string is None:
73            return None
74        if string.lower() in ['1', 'true', 'yes']:
75            return True
76        return False
77
78    def _convertValueToString(self, value):
79        if value is None:
80            return None
81        if value:
82            return '1'
83        return '0'
[6258]84
[4810]85class TextConverter(Converter):
[4834]86    """A converter for zope.schema.interfaces.IText fields.
87    """
[4805]88    grok.context(IText)
89    grok.provides(ISchemaTypeConverter)
90
[4810]91    def _convertValueFromString(self, string):
92        return unicode(string)
93
94class IntConverter(Converter):
[4834]95    """A converter for zope.schema.Int fields.
96    """
[4810]97    grok.context(IInt)
98    grok.provides(ISchemaTypeConverter)
99
100    def _convertValueFromString(self, string):
101        return int(string)
102
103class ChoiceConverter(Converter):
[4834]104    """A converter for zope.schema.Choice fields.
105    """
[4810]106    grok.context(IChoice)
107    grok.provides(ISchemaTypeConverter)
108
[4834]109    tokens = None
110    values = None
[6258]111
[4805]112    def __init__(self, context):
113        self.context = context
[4834]114        if not hasattr(self.context.source, 'factory'):
115            try:
116                self.terms = getMultiAdapter(
117                    (self.context.source, TestRequest()), ITerms)
118            except:
119                self.terms = None
[4810]120
[4834]121            return
122        if not hasattr(self.context.source.factory, 'getToken'):
123            return
124        # For expensive token/key lookups we create a 'cache'
125        # here. This speeds up mass operations with many conversions
126        # by factor 10 or more.
[6258]127
[4834]128        # Mapping token -> value
129        self.tokens = dict([(self.context.source.factory.getToken(x), x)
130                            for x in self.context.source.factory.getValues()])
131        # Mapping value -> token
132        self.values = dict([(y,x) for x,y in self.tokens.items()])
133
[4810]134    def _convertValueFromString(self, string):
[4834]135        if self.tokens is not None:
[5328]136            result = None
137            try:
138                result = self.tokens[string]
139            except KeyError:
140                # Be gentle...
141                try:
142                    result = self.tokens[string.lower()]
143                    return result
144                except KeyError:
145                    tokenlist = (','.join(self.tokens[:2]))
146                    raise ValueError(
147                        'The token %s is not valid. Use one of %s, ...' % (
148                            string, tokenlist))
149            return result
[4814]150        if self.terms is not None:
[4810]151            return self.terms.getValue(string)
[5328]152        result = self.context.source.getTermByToken(string).value
153        return result
[4810]154
[4814]155    def _convertValueToString(self, value):
[4834]156        if self.values is not None:
157            return self.values[value]
[4814]158        if self.terms is not None:
159            return self.terms.getTerm(value).token
160        return str(value)
[4834]161
162    def fromString(self, string=None, strict=False):
163        """Convert ``string`` to value according to assigned field type.
164
165        We change the default for ``strict``: this disables extra
166        validation checks for Choice fields and saves lots of time. If
167        a string/value is out of allowed range we get a value or key
168        error anyway.
169        """
170        return super(ChoiceConverter, self).fromString(string=string,
171                                                       strict=strict)
172
173    def toString(self, value, strict=False):
174        """Convert ``value`` to string according to assigned field type.
175
176        We change the default for ``strict``: this disables extra
177        validation checks for Choice fields and saves lots of time. If
178        a string/value is out of allowed range we get a value or key
179        error anyway.
180        """
181        return super(ChoiceConverter, self).toString(value=value,
182                                                     strict=strict)
[5328]183
184class DateConverter(Converter):
185    """A converter for zope.schema.IDate fields.
186
187    Converts date to string and vice versa. Stringified dates are
188    expected in format ``YYYY-MM-DD``.
189
190    To support at least some other formats, we accept for conversion
191    from string also the following formats:
192
193    * ``YYYY/MM/DD``
[6258]194
[5328]195    * ``DD/MM/YYYY``
[6258]196
[5328]197    * ``D/M/YYYY``
198
199    * ``DD.MM.YYYY``
200
201    * ``D.M.YYYY``
202
203    When converting to strings, always 'YYYY-MM-DD' is returned.
204
205    For convenience, when converting from strings also string data
206    separated by space is stripped before processing. Therefore
207    strings like '1990-04-01 12:12:01 GMT +1' will also work.
208    """
209    grok.context(IDate)
210    grok.provides(ISchemaTypeConverter)
211
212    #: List of supported date formats in `strftime()` notation.
213    formats = ['%Y-%m-%d', '%Y/%m/%d' , '%d/%m/%Y', '%D/%M/%Y',
214               '%d.%m.%Y', '%D.%M.%Y']
[6258]215
[5328]216    def _convertValueFromString(self, string):
217        orig_string = string
218        if string is NONE_STRING_VALUE:
219            string = None
220        if string is None:
221            return None
222        value = None
223        if ' ' in string:
224            string = string.split(' ')[0]
225        for format in self.formats:
226            try:
227                value = datetime.datetime.strptime(string, format)
228                break
229            except ValueError:
230                pass
231        if value is None:
232            raise ValueError(
233                'Cannot convert to date: %s. Use YYYY-MM-DD.' %
234                orig_string)
235        value = value.date()
236        return value
237
238    def _convertValueToString(self, value):
239        return datetime.date.strftime(value, '%Y-%m-%d')
[6258]240
241
[6263]242from zope import schema
243from zope.component import createObject
[6258]244from zope.interface import Interface
245from zope.formlib import form
[6260]246from zope.formlib.form import (
247    _widgetKey, WidgetInputError, ValidationError, InputErrors, expandPrefix)
[6258]248from zope.formlib.interfaces import IInputWidget
249from zope.publisher.browser import TestRequest
250
[6260]251def getWidgetsData(widgets, form_prefix, data):
252    errors = []
253    form_prefix = expandPrefix(form_prefix)
254
255    for input, widget in widgets.__iter_input_and_widget__():
256        if input and IInputWidget.providedBy(widget):
257            name = _widgetKey(widget, form_prefix)
258
259            if not widget.hasInput():
260                continue
261
262            try:
263                data[name] = widget.getInputValue()
264            except ValidationError, error:
265                # convert field ValidationError to WidgetInputError
266                error = WidgetInputError(widget.name, widget.label, error)
267                errors.append((name, error))
268            except InputErrors, error:
269                errors.append((name, error))
270
271    return errors
272
273
[6263]274class IObjectConverter(Interface):
275    def __init__(iface):
276        """Create an converter.
[6258]277
[6263]278        `iface` denotes the interface to which we want to turn any
279        passed object.
280
281        """
282
283    def applyRowData(data_dict, context, form_fields=None):
284        """Apply data in `data_dict` to `context`.
285
286        `data_dict` is a dict containing field names as keys and an
287        object or string as `context`.
288
289        If `context` is a string, this is understood as a factory name
290        and we will try to create a proper object calling
291        ``createObject()``.
292
293        `form_fields` are by default (``None``) buildt from the given
294        `iface` but can also be passed in to override the
295        default. This might be handy if you want to omit or select
296        certains fields from the interface.
297
298        Returns a tuple ``(<ERROR_LIST, INV_ERR_LIST, OBJ>)`` where
299        ``ERROR_DICT`` is a dict of errors for single fields (if
300        happened), ``INV_ERR_LIST`` is a list of invariant errors
301        happened (errors that apply to several fields), and ``OBJ`` is
302        the created/updated object.
303        """
304
305class DefaultObjectConverter(grok.Adapter):
306    """An object converter can apply CSV data to objects.
307
308    Thus, in a way, it can turn CSV data into real objects.
309    """
310
[6258]311    grok.context(Interface)
[6263]312    grok.provides(IObjectConverter)
[6258]313
[6263]314    def __init__(self, iface):
[6258]315        self.iface = iface
[6263]316        self.form_fields = form.Fields(iface)
[6260]317        return
[6258]318
[6263]319    def applyRowData(self, data_dict, context, form_fields=None):
320        if form_fields is None:
321            form_fields = self.form_fields
322
323        obj = context
324        if isinstance(context, basestring):
325            obj = createObject(context)
[6258]326        request = TestRequest(form={})
327        for key, val in data_dict.items():
328            request.form['form.%s' % key] = val
329        widgets = form.setUpWidgets(
[6263]330            form_fields, 'form', obj, request)
[6260]331        errors = getWidgetsData(widgets, 'form', data_dict)
[6263]332        err_messages = []
[6260]333        if errors:
334            for key, error in errors:
[6263]335                message = error.args[0]
336                err_messages.append((key, message))
[6258]337        invariant_errors = form.checkInvariants(form_fields, data_dict)
[6263]338        invariant_errors = [err.message for err in invariant_errors]
[6258]339        if not errors and not invariant_errors:
340            changed = form.applyChanges(
[6263]341                obj, form_fields, data_dict)
342        return err_messages, invariant_errors, obj
Note: See TracBrowser for help on using the repository browser.