source: main/waeup.sirp/branches/ulif-fasttables/src/waeup/sirp/jambtables/dictwidget.py @ 5274

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

Add a dict widget. This is not in a very usable state, as it does not preserve order of fields and can't, because dicts have no order.

File size: 12.0 KB
Line 
1##
2## dictwidget.py
3## Login : <uli@pu.smp.net>
4## Started on  Sun Jul 18 03:05:27 2010 Uli Fouquet
5## $Id$
6##
7## Copyright (C) 2010 Uli Fouquet
8## This program is free software; you can redistribute it and/or modify
9## it under the terms of the GNU General Public License as published by
10## the Free Software Foundation; either version 2 of the License, or
11## (at your option) any later version.
12##
13## This program is distributed in the hope that it will be useful,
14## but WITHOUT ANY WARRANTY; without even the implied warranty of
15## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16## GNU General Public License for more details.
17##
18## You should have received a copy of the GNU General Public License
19## along with this program; if not, write to the Free Software
20## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21##
22import grok
23from zope.browserpage import ViewPageTemplateFile
24from zope.component import getMultiAdapter
25from zope.formlib.i18n import _
26from zope.formlib.interfaces import (
27    ISimpleInputWidget, IInputWidget, IDisplayWidget)
28from zope.formlib.sequencewidget import SequenceWidget
29from zope.formlib.widget import DisplayWidget, renderElement
30from zope.i18n import translate
31from zope.publisher.interfaces.browser import IBrowserRequest
32from zope.schema.interfaces import IDict, IField
33
34class DictWidget(SequenceWidget):
35    _type = dict
36    #grok.adapts(IDict, IField, IBrowserRequest)
37    #grok.adapts(IDict, IBrowserRequest)
38    #grok.provides(ISimpleInputWidget)
39    template = ViewPageTemplateFile('dictwidget.pt')
40
41   
42    def __init__(self, context, request, subwidget=None):
43        super(DictWidget, self).__init__(context, None, request)
44        self.subwidget = subwidget
45
46        # The subwidgets are cached in this dict if preserve_widgets is True.
47        self._widgets = {}
48        self.preserve_widgets = False
49
50    def _getWidget(self, i):
51        """Return a widget for the i-th number of the sequence.
52
53        Normally this method creates a new widget each time, but when
54        the ``preserve_widgets`` attribute is True, it starts caching
55        widgets.  We need it so that the errors on the subwidgets
56        would appear only if ``getInputValue`` was called.
57
58        ``getInputValue`` on the subwidgets gets called on each
59        request that has data.
60        """
61        if i not in self._widgets:
62            key_field = self.context.key_type
63            if self.subwidget is not None:
64                key_widget = self.subwidget(key_field, self.request)
65            else:
66                key_widget = getMultiAdapter(
67                    (key_field, self.request), IInputWidget)
68            #key_widget.setPrefix('%s.key.%d.' % (self.name, i))
69            key_widget.setPrefix('%s.key.%d.' % (self.name, i))
70           
71            value_field = self.context.value_type
72            if self.subwidget is not None:
73                value_widget = self.subwidget(value_field, self.request)
74            else:
75                value_widget = getMultiAdapter(
76                    (value_field, self.request), IInputWidget)
77            #value_widget.setPrefix('%s.value.%d.' % (self.name, i))
78            value_widget.setPrefix('%s.value.%d.' % (self.name, i))
79            #import pdb; pdb.set_trace()
80            if not self.preserve_widgets:
81                #return super(DictWidget, self)._getWidget(i)
82                return (key_widget, value_widget)
83            self._widgets[i] = (key_widget, value_widget)
84        #return super(DictWidget, self)._getWidget(i)
85        return self._widgets[i]
86           
87        if i not in self._widgets:
88            field = self.context.value_type
89            if self.subwidget is not None:
90                widget = self.subwidget(field, self.request)
91            else:
92                widget = getMultiAdapter(
93                    (field, self.request), IInputWidget)
94            widget.setPrefix('%s.%d.' % (self.name, i))
95            if not self.preserve_widgets:
96                return widget
97            self._widgets[i] = widget
98        return self._widgets[i]
99
100    def widgets(self):
101        """Return a list of widgets to display"""
102        sequence = self._getRenderedValue()
103        result = []
104        for i, value in enumerate(sequence.items()):
105            widget = self._getWidget(i)
106            widget[0].setRenderedValue(value[0])
107            widget[1].setRenderedValue(value[1])
108            result.append(widget)
109        return result
110
111    def _generateSequence(self):
112        """Extract the values of the subwidgets from the request.
113
114        Returns a list of key-value tuples.
115
116        This can only be called if self.hasInput() returns true.
117        """
118        if self.context.value_type is None:
119            # Why would this ever happen?
120            return []
121        if self.context.key_type is None:
122            return []
123        # the marker field tells how many individual items were
124        # included in the input; we check for exactly that many input
125        # widgets
126        try:
127            count = int(self.request.form[self.name + ".count"])
128        except ValueError:
129            # could not convert to int; the input was not generated
130            # from the widget as implemented here
131            raise WidgetInputError(self.context.__name__, self.context.title)
132
133        # pre-populate
134        sequence = [(None, None)] * count
135        #sequence = [None] * count
136        #sequence = dict()
137
138        # now look through the request for interesting values
139        # in reverse so that we can remove items as we go
140        removing = self.name + ".remove" in self.request.form
141        for i in reversed(range(count)):
142            #widget = self._getWidget(i)
143            key_widget, value_widget = self._getWidget(i)
144            key = None
145            value = None
146            if key_widget.hasValidInput():
147                # catch and set sequence widget errors to ``_error`` attribute
148                try:
149                    key = key_widget.getInputValue()
150                    #sequence[i] = widget.getInputValue()
151                except WidgetInputError, error:
152                    self._error = error
153                    raise self._error
154            if value_widget.hasValidInput():
155                # catch and set sequence widget errors to ``_error`` attribute
156                try:
157                    value = value_widget.getInputValue()
158                    #sequence[i] = widget.getInputValue()
159                except WidgetInputError, error:
160                    self._error = error
161                    raise self._error
162            sequence[i] = (key, value)
163            #if widget.hasValidInput():
164            #    # catch and set sequence widget errors to ``_error`` attribute
165            #    try:
166            #        sequence[i] = widget.getInputValue()
167            #    except WidgetInputError, error:
168            #        self._error = error
169            #        raise self._error
170
171            remove_key = "%s.remove_%d" % (self.name, i)
172            if remove_key in self.request.form and removing:
173                del sequence[i]
174
175        # add an entry to the list if the add button has been pressed
176        if self.name + ".add" in self.request.form:
177            # Should this be using self.context.value_type.missing_value
178            # instead of None?
179            sequence.append((None, None))
180
181        return sequence
182
183   
184    def getInputValue(self):
185        """Return converted and validated widget data.
186
187        If there is no user input and the field is required, then a
188        ``MissingInputError`` will be raised.
189
190        If there is no user input and the field is not required, then
191        the field default value will be returned.
192
193        A ``WidgetInputError`` is raised in the case of one or more
194        errors encountered, inputting, converting, or validating the data.
195        """
196        sequence = super(DictWidget, self).getInputValue()
197        return sequence
198        return self._type(sequence)
199
200    def _getRenderedValue(self):
201        """Returns a dict from the request or _data"""
202        if self._renderedValueSet():
203            if self._data is self.context.missing_value:
204                #sequence = []
205                sequence = dict()
206            else:
207                #sequence = list(self._data)
208                sequence = dict(self._data)
209        elif self.hasInput():
210            #sequence = self._generateSequence()
211            sequence = dict(self._generateSequence())
212        elif self.context.default is not None:
213            #sequence = self.context.default
214            sequence = self.context.default
215        else:
216            #sequence = []
217            seuence = dict()
218        # ensure minimum number of items in the form
219        #while len(sequence) < self.context.min_length:
220        #    # Shouldn't this use self.field.value_type.missing_value,
221        #    # instead of None?
222        #    #sequence.append(None)
223        #    pass
224        return sequence
225
226    # TODO: applyChanges isn't reporting "change" correctly (we're
227    # re-generating the sequence with every edit, and need to be smarter)
228    def applyChanges(self, content):
229        field = self.context
230        value = self.getInputValue()
231        change = field.query(content, self) != value
232        if change:
233            print "SET: ", field, content, value
234            field.set(content, value)
235        else:
236            print "NO SET: ", field, content, value
237        return change
238
239   
240# Basic display widget
241
242class DictDisplayWidget(DisplayWidget):
243
244    _missingValueMessage = _("sequence-value-not-provided",
245                             u"(no value available)")
246
247    _emptySequenceMessage = _("sequence-value-is-empty",
248                              u"(no values)")
249
250    tag = "ol"
251    itemTag = "li"
252    cssClass = "sequenceWidget"
253    extra = ""
254
255    def __init__(self, context, request, subwidget=None):
256        # def __init__(self, context, field, request, subwidget=None):
257        super(DictDisplayWidget, self).__init__(context, request)
258        self.subwidget = subwidget
259
260    def __call__(self):
261        # get the data to display:
262        #import pdb; pdb.set_trace()
263        if self._renderedValueSet():
264            data = self._data
265        else:
266            data = self.context.get(self.context.context)
267
268        # deal with special cases:
269        if data == self.context.missing_value:
270            return translate(self._missingValueMessage, self.request)
271        #data = list(data)
272        data = dict(data)
273        if not data:
274            return translate(self._emptySequenceMessage, self.request)
275
276        parts = []
277        # for i, item in enumerate(data):
278        for i, item in enumerate(data.items()):
279            #widget = self._getWidget(i)
280            key_widget, value_widget = self._getWidget(i)
281            key_widget.setRenderedValue(item[0])
282            value_widget.setRenderedValue(item[1])
283            #widget.setRenderedValue(item)
284            #s = widget()
285            s = "%s: %s" % (key_widget(), value_widget())
286            if self.itemTag:
287                s = "<%s>%s</%s>" % (self.itemTag, s, self.itemTag)
288            parts.append(s)
289        contents = "\n".join(parts)
290        if self.tag:
291            contents = "\n%s\n" % contents
292            contents = renderElement(self.tag,
293                                     cssClass=self.cssClass,
294                                     extra=self.extra,
295                                     contents=contents)
296        return contents
297
298    def _getWidget(self, i):
299        """Get a tuple of key-value widgets for the i-th 'row' of dict.
300        """
301        key_field = self.context.key_type
302        value_field = self.context.value_type
303        if self.subwidget is not None:
304            key_widget = self.subwidget(key_field, self.request)
305            value_widget = self.subwidget(value_field, self.request)
306        else:
307            key_widget = getMultiAdapter(
308                (key_field, self.request), IDisplayWidget)
309            value_widget = getMultiAdapter(
310                (value_field, self.request), IDisplayWidget)
311        key_widget.setPrefix('%s.key.%d.' % (self.name, i))
312        value_widget.setPrefix('%s.value.%d.' % (self.name, i))
313        return (key_widget, value_widget)
314
Note: See TracBrowser for help on using the repository browser.