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

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

Add extended bools widget. This one accepts also '1', 'true', 'yes' as True values, which is handy when importing CSV
files. We have to register this new widget overriding the default. Maybe a bit overkill?

File size: 5.9 KB
Line 
1"""Converters for zope.schema-based datatypes.
2"""
3import grok
4from zope.component import createObject
5from zope.formlib import form
6from zope.formlib.boolwidgets import CheckBoxWidget
7from zope.formlib.form import (
8    _widgetKey, WidgetInputError, ValidationError, InputErrors, expandPrefix)
9from zope.formlib.interfaces import IInputWidget, ISimpleInputWidget
10from zope.interface import Interface
11from zope.publisher.browser import TestRequest
12from waeup.sirp.interfaces import IObjectConverter
13
14class ExtendedCheckBoxWidget(CheckBoxWidget):
15    """A checkbox widget that supports more input values as True/False
16       markers.
17
18    The default bool widget expects the string 'on' as only valid
19    ``True`` value in HTML forms for bool fields.
20
21    This widget also accepts '1', 'true' and 'yes' for that. Also all
22    uppercase/lowecase combinations of these strings are accepted.
23
24    The widget still renders ``True`` to ``'on'`` when a form is
25    generated.
26    """
27    true_markers = ['1', 'true', 'on', 'yes']
28
29    def _toFieldValue(self, input):
30        """Convert from HTML presentation to Python bool."""
31        if not isinstance(input, basestring):
32            return False
33        return input.lower() in self.true_markers
34
35    def _getFormInput(self):
36        """Returns the form input used by `_toFieldValue`.
37
38        Return values:
39
40          ``'on'``  checkbox is checked
41          ``''``    checkbox is not checked
42          ``None``  form input was not provided
43
44        """
45        value = self.request.get(self.name)
46        if isinstance(value, basestring):
47            value = value.lower()
48        if value in self.true_markers:
49            return 'on'
50        elif self.name + '.used' in self.request:
51            return ''
52        else:
53            return None
54
55def getWidgetsData(widgets, form_prefix, data):
56    """Get data and validation errors from `widgets` for `data`.
57
58    Updates the dict in `data` with values from the widgets in
59    `widgets`.
60
61    Returns a list of tuples ``(<WIDGET_NAME>, <ERROR>)`` where
62    ``<WIDGET_NAME>`` is a widget name (normally the same as the
63    associated field name) and ``<ERROR>`` is the exception that
64    happened for that widget/field.
65
66    This is merely a copy from the same-named function in
67    :mod:`zope.formlib.form`. The only difference is that we also
68    store the fieldname for which a validation error happened in the
69    returned error list (what the original does not do).
70
71    """
72    errors = []
73    form_prefix = expandPrefix(form_prefix)
74
75    for input, widget in widgets.__iter_input_and_widget__():
76        if input and IInputWidget.providedBy(widget):
77            name = _widgetKey(widget, form_prefix)
78
79            if not widget.hasInput():
80                continue
81
82            try:
83                data[name] = widget.getInputValue()
84            except ValidationError, error:
85                # convert field ValidationError to WidgetInputError
86                error = WidgetInputError(widget.name, widget.label, error)
87                errors.append((name, error))
88            except InputErrors, error:
89                errors.append((name, error))
90
91    return errors
92
93
94class DefaultObjectConverter(grok.Adapter):
95    """Turn string values into real values.
96
97    A converter can convert string values for objects that implement a
98    certain interface into real values based on the given interface.
99    """
100
101    grok.context(Interface)
102    grok.provides(IObjectConverter)
103
104    def __init__(self, iface):
105        self.iface = iface
106        self.default_form_fields = form.Fields(iface)
107        return
108
109    def fromStringDict(self, data_dict, context, form_fields=None):
110        """Convert values in `data_dict`.
111
112        Converts data in `data_dict` into real values based on
113        `context` and `form_fields`.
114
115        `data_dict` is a mapping (dict) from field names to values
116        represented as strings.
117
118        The fields (keys) to convert can be given in optional
119        `form_fields`. If given, form_fields should be an instance of
120        :class:`zope.formlib.form.Fields`. Suitable instances are for
121        example created by :class:`grok.AutoFields`.
122
123        If no `form_fields` are given, a default is computed from the
124        associated interface.
125
126        The `context` can be an existing object (implementing the
127        associated interface) or a factory name. If it is a string, we
128        try to create an object using
129        :func:`zope.component.createObject`.
130
131        Returns a tuple ``(<FIELD_ERRORS>, <INVARIANT_ERRORS>,
132        <DATA_DICT>)`` where
133
134        ``<FIELD_ERRORS>``
135           is a list of tuples ``(<FIELD_NAME>, <ERROR>)`` for each
136           error that happened when validating the input data in
137           `data_dict`
138
139        ``<INVARIANT_ERRORS>``
140           is a list of invariant errors concerning several fields
141
142        ``<DATA_DICT>``
143           is a dict with the values from input dict converted.
144
145        If errors happen, i.e. the error lists are not empty, always
146        an empty ``<DATA_DICT>`` is returned.
147
148        If ``<DATA_DICT>` is non-empty, there were no errors.
149        """
150        if form_fields is None:
151            form_fields = self.default_form_fields
152
153        request = TestRequest(form={})
154        for key, val in data_dict.items():
155            request.form['form.%s' % key] = val
156
157        obj = context
158        if isinstance(context, basestring):
159            obj = createObject(context)
160
161        widgets = form.setUpInputWidgets(
162            form_fields, 'form', obj, request)
163
164        new_data = dict()
165        errors = getWidgetsData(widgets, 'form', new_data)
166
167        invariant_errors = form.checkInvariants(form_fields, new_data)
168        if errors or invariant_errors:
169            err_messages = [(key, err.args[0]) for key, err in errors]
170            invariant_errors = [err.message for err in invariant_errors]
171            return err_messages, invariant_errors, {}
172
173        return errors, invariant_errors, new_data
Note: See TracBrowser for help on using the repository browser.