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

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

Add a zope.formlib-based converter. Just playing around with
possibilities to apply string dicts to objects (in order to import CSV
data respecting invariants and all other constraints defined in some
interface).

File size: 8.6 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.interface import Interface
243from zope.formlib import form
244from zope.formlib.interfaces import IInputWidget
245from zope.publisher.browser import TestRequest
246
247class DefaultConverter(Converter):
248
249    grok.context(Interface)
250    grok.provides(ISchemaTypeConverter)
251
252    def __init__(self, iface):
253        self.iface = iface
254
255    def applyRowData(self, data_dict, context):
256        request = TestRequest(form={})
257        form_fields = form.Fields(self.iface)
258        for key, val in data_dict.items():
259            request.form['form.%s' % key] = val
260        widgets = form.setUpWidgets(
261            form_fields, 'form', context, request)
262        errors = form.getWidgetsData(widgets, 'form', data_dict)
263        invariant_errors = form.checkInvariants(form_fields, data_dict)
264        if not errors and not invariant_errors:
265            changed = form.applyChanges(
266                context, form_fields, data_dict)
267        return errors, invariant_errors, data_dict
Note: See TracBrowser for help on using the repository browser.