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

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

Extend converter and create own interface for it.

File size: 11.1 KB
Line 
1"""Converters for zope.schema-based datatypes.
2"""
3import datetime
4import grok
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
11from zope.schema.interfaces import IBool, IText, IInt, IChoice, IDate
12from waeup.sirp.interfaces import ISchemaTypeConverter
13
14# If a string has this value, it is considered as 'missing_value' or None.
15NONE_STRING_VALUE = ''
16
17class Converter(grok.Adapter):
18    """Base for string-or-none to zope.schema field converters.
19    """
20    grok.baseclass()
21    grok.provides(ISchemaTypeConverter)
22
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')
33
34    def fromString(self, string=None, strict=True):
35        """Convert ``string`` to value according to assigned field type.
36        """
37        result = None
38        if string is NONE_STRING_VALUE:
39            string = None
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
50
51    def _convertValueToString(self, value):
52        return str(value)
53
54    def toString(self, value, strict=True):
55        """Convert given `value` to string according to assigned field type.
56        """
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):
64    """A converter for zope.schema.Bool fields.
65    """
66    grok.context(IBool)
67    grok.provides(ISchemaTypeConverter)
68
69    def _convertValueFromString(self, string):
70        if string is NONE_STRING_VALUE:
71            string = None
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'
84
85class TextConverter(Converter):
86    """A converter for zope.schema.interfaces.IText fields.
87    """
88    grok.context(IText)
89    grok.provides(ISchemaTypeConverter)
90
91    def _convertValueFromString(self, string):
92        return unicode(string)
93
94class IntConverter(Converter):
95    """A converter for zope.schema.Int fields.
96    """
97    grok.context(IInt)
98    grok.provides(ISchemaTypeConverter)
99
100    def _convertValueFromString(self, string):
101        return int(string)
102
103class ChoiceConverter(Converter):
104    """A converter for zope.schema.Choice fields.
105    """
106    grok.context(IChoice)
107    grok.provides(ISchemaTypeConverter)
108
109    tokens = None
110    values = None
111
112    def __init__(self, context):
113        self.context = context
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
120
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.
127
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
134    def _convertValueFromString(self, string):
135        if self.tokens is not None:
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
150        if self.terms is not None:
151            return self.terms.getValue(string)
152        result = self.context.source.getTermByToken(string).value
153        return result
154
155    def _convertValueToString(self, value):
156        if self.values is not None:
157            return self.values[value]
158        if self.terms is not None:
159            return self.terms.getTerm(value).token
160        return str(value)
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)
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``
194
195    * ``DD/MM/YYYY``
196
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']
215
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')
240
241
242from zope import schema
243from zope.component import createObject
244from zope.interface import Interface
245from zope.formlib import form
246from zope.formlib.form import (
247    _widgetKey, WidgetInputError, ValidationError, InputErrors, expandPrefix)
248from zope.formlib.interfaces import IInputWidget
249from zope.publisher.browser import TestRequest
250
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
274class IObjectConverter(Interface):
275    def __init__(iface):
276        """Create an converter.
277
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
311    grok.context(Interface)
312    grok.provides(IObjectConverter)
313
314    def __init__(self, iface):
315        self.iface = iface
316        self.form_fields = form.Fields(iface)
317        return
318
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)
326        request = TestRequest(form={})
327        for key, val in data_dict.items():
328            request.form['form.%s' % key] = val
329        widgets = form.setUpWidgets(
330            form_fields, 'form', obj, request)
331        errors = getWidgetsData(widgets, 'form', data_dict)
332        err_messages = []
333        if errors:
334            for key, error in errors:
335                message = error.args[0]
336                err_messages.append((key, message))
337        invariant_errors = form.checkInvariants(form_fields, data_dict)
338        invariant_errors = [err.message for err in invariant_errors]
339        if not errors and not invariant_errors:
340            changed = form.applyChanges(
341                obj, form_fields, data_dict)
342        return err_messages, invariant_errors, obj
Note: See TracBrowser for help on using the repository browser.