source: main/waeup.kofa/trunk/src/waeup/kofa/utils/converters.py @ 8152

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

Make import converters more modular (support special converters for special zope.schema field types) and support lists and result entries as import input.

  • Property svn:keywords set to Id
File size: 9.4 KB
Line 
1## $Id: converters.py 7932 2012-03-21 10:41:15Z uli $
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"""Converters for zope.schema-based datatypes.
19"""
20import grok
21from zope.component import createObject
22from zope.formlib import form
23from zope.formlib.boolwidgets import CheckBoxWidget
24from zope.formlib.form import (
25    _widgetKey, WidgetInputError, ValidationError, InputErrors, expandPrefix)
26from zope.formlib.interfaces import IInputWidget
27from zope.interface import Interface
28from zope.publisher.browser import TestRequest
29from zope.schema.interfaces import IList
30from waeup.kofa.interfaces import (
31    IObjectConverter, IResultEntryField, IFieldConverter, SubjectSource,
32    GradeSource)
33from waeup.kofa.schoolgrades import ResultEntry
34
35class ExtendedCheckBoxWidget(CheckBoxWidget):
36    """A checkbox widget that supports more input values as True/False
37    markers.
38
39    The default bool widget expects the string 'on' as only valid
40    ``True`` value in HTML forms for bool fields.
41
42    This widget also accepts '1', 'true' and 'yes' for that. Also all
43    uppercase/lowecase combinations of these strings are accepted.
44
45    The widget still renders ``True`` to ``'on'`` when a form is
46    generated.
47    """
48    true_markers = ['1', 'true', 'on', 'yes']
49
50    def _toFieldValue(self, input):
51        """Convert from HTML presentation to Python bool."""
52        if not isinstance(input, basestring):
53            return False
54        return input.lower() in self.true_markers
55
56    def _getFormInput(self):
57        """Returns the form input used by `_toFieldValue`.
58
59        Return values:
60
61          ``'on'``  checkbox is checked
62          ``''``    checkbox is not checked
63          ``None``  form input was not provided
64
65        """
66        value = self.request.get(self.name)
67        if isinstance(value, basestring):
68            value = value.lower()
69        if value in self.true_markers:
70            return 'on'
71        elif self.name + '.used' in self.request:
72            return ''
73        else:
74            return None
75
76def getWidgetsData(widgets, form_prefix, data):
77    """Get data and validation errors from `widgets` for `data`.
78
79    Updates the dict in `data` with values from the widgets in
80    `widgets`.
81
82    Returns a list of tuples ``(<WIDGET_NAME>, <ERROR>)`` where
83    ``<WIDGET_NAME>`` is a widget name (normally the same as the
84    associated field name) and ``<ERROR>`` is the exception that
85    happened for that widget/field.
86
87    This is merely a copy from the same-named function in
88    :mod:`zope.formlib.form`. The only difference is that we also
89    store the fieldname for which a validation error happened in the
90    returned error list (what the original does not do).
91    """
92    errors = []
93    form_prefix = expandPrefix(form_prefix)
94
95    for input, widget in widgets.__iter_input_and_widget__():
96        if input and IInputWidget.providedBy(widget):
97            name = _widgetKey(widget, form_prefix)
98
99            if not widget.hasInput():
100                continue
101
102            try:
103                data[name] = widget.getInputValue()
104            except ValidationError, error:
105                # convert field ValidationError to WidgetInputError
106                error = WidgetInputError(widget.name, widget.label, error)
107                errors.append((name, error))
108            except InputErrors, error:
109                errors.append((name, error))
110
111    return errors
112
113class DefaultFieldConverter(grok.Adapter):
114    grok.context(Interface)
115    grok.provides(IFieldConverter)
116
117    def request_data(self, name, value, schema_field, prefix=''):
118        return {prefix: value}
119
120class ListFieldConverter(grok.Adapter):
121    grok.context(IList)
122    grok.provides(IFieldConverter)
123
124    def request_data(self, name, value, schema_field, prefix=''):
125        value_type = schema_field.value_type
126        try:
127            items = eval(value)
128        except:
129            return {prefix: value}
130        result = {'%s.count' % prefix: len(items)}
131        for num, item in enumerate(items):
132            sub_converter = IFieldConverter(value_type)
133            result.update(sub_converter.request_data(
134                unicode(num), unicode(item),
135                value_type, "%s.%s." % (prefix, num)))
136        return result
137
138class ResultEntryConverter(grok.Adapter):
139    grok.context(IResultEntryField)
140    grok.provides(IFieldConverter)
141
142    def request_data(self, name, value, schema_field, prefix=''):
143        """Turn CSV values into ResultEntry-compatible form data.
144
145        Expects as `value` a _string_ like ``(u'mysubject',
146        u'mygrade')`` and turns it into some dict like::
147
148          {
149            'form.grade.subject': u'9234896395...',
150            'form.grade.grade': u'7e67e9e777..'
151            }
152
153        where the values are tokens from appropriate sources.
154
155        Such dicts can be transformed into real ResultEntry objects by
156        input widgets used in converters.
157        """
158        try:
159            entry = ResultEntry.from_string(value)
160            subj, grade = entry.subject, entry.grade
161        except:
162            return {prefix: value}
163        # web forms send tokens instead of real values
164        s_token = SubjectSource().factory.getToken(subj)
165        g_token = GradeSource().factory.getToken(grade)
166        result = {
167            "%ssubject" % (prefix): s_token,
168            "%sgrade" % (prefix): g_token,
169            }
170        return result
171
172class DefaultObjectConverter(grok.Adapter):
173    """Turn string values into real values.
174
175    A converter can convert string values for objects that implement a
176    certain interface into real values based on the given interface.
177    """
178
179    grok.context(Interface)
180    grok.provides(IObjectConverter)
181
182    def __init__(self, iface):
183        self.iface = iface
184        # Omit known dictionaries since there is no widget available
185        # for dictionary schema fields
186        self.default_form_fields = form.Fields(iface).omit('description_dict')
187        return
188
189    def fromStringDict(self, data_dict, context, form_fields=None):
190        """Convert values in `data_dict`.
191
192        Converts data in `data_dict` into real values based on
193        `context` and `form_fields`.
194
195        `data_dict` is a mapping (dict) from field names to values
196        represented as strings.
197
198        The fields (keys) to convert can be given in optional
199        `form_fields`. If given, form_fields should be an instance of
200        :class:`zope.formlib.form.Fields`. Suitable instances are for
201        example created by :class:`grok.AutoFields`.
202
203        If no `form_fields` are given, a default is computed from the
204        associated interface.
205
206        The `context` can be an existing object (implementing the
207        associated interface) or a factory name. If it is a string, we
208        try to create an object using
209        :func:`zope.component.createObject`.
210
211        Returns a tuple ``(<FIELD_ERRORS>, <INVARIANT_ERRORS>,
212        <DATA_DICT>)`` where
213
214        ``<FIELD_ERRORS>``
215           is a list of tuples ``(<FIELD_NAME>, <ERROR>)`` for each
216           error that happened when validating the input data in
217           `data_dict`
218
219        ``<INVARIANT_ERRORS>``
220           is a list of invariant errors concerning several fields
221
222        ``<DATA_DICT>``
223           is a dict with the values from input dict converted.
224
225        If errors happen, i.e. the error lists are not empty, always
226        an empty ``<DATA_DICT>`` is returned.
227
228        If ``<DATA_DICT>`` is non-empty, there were no errors.
229        """
230        if form_fields is None:
231            form_fields = self.default_form_fields
232
233        request = TestRequest(form={})
234        for key, val in data_dict.items():
235            field = form_fields.get(key, None)
236            if field is not None:
237                # let adapters to the respective schema fields do the
238                # further fake-request processing
239                schema_field = field.interface[field.__name__]
240                field_converter = IFieldConverter(schema_field)
241                request.form.update(
242                    field_converter.request_data(
243                        key, val, schema_field, 'form.%s' % key)
244                    )
245            else:
246                request.form['form.%s' % key] = val
247
248        obj = context
249        if isinstance(context, basestring):
250            obj = createObject(context)
251
252        widgets = form.setUpInputWidgets(
253            form_fields, 'form', obj, request)
254
255        new_data = dict()
256        errors = getWidgetsData(widgets, 'form', new_data)
257
258        invariant_errors = form.checkInvariants(form_fields, new_data)
259
260        if errors or invariant_errors:
261            err_messages = [(key, err.args[0]) for key, err in errors]
262            invariant_errors = [err.message for err in invariant_errors]
263            return err_messages, invariant_errors, {}
264
265        return errors, invariant_errors, new_data
Note: See TracBrowser for help on using the repository browser.