## ## dictwidget.py ## Login : ## Started on Sun Jul 18 03:05:27 2010 Uli Fouquet ## $Id$ ## ## Copyright (C) 2010 Uli Fouquet ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## import grok from zope.browserpage import ViewPageTemplateFile from zope.component import getMultiAdapter from zope.formlib.i18n import _ from zope.formlib.interfaces import ( ISimpleInputWidget, IInputWidget, IDisplayWidget) from zope.formlib.sequencewidget import SequenceWidget from zope.formlib.widget import DisplayWidget, renderElement from zope.i18n import translate from zope.publisher.interfaces.browser import IBrowserRequest from zope.schema.interfaces import IDict, IField class DictWidget(SequenceWidget): _type = dict #grok.adapts(IDict, IField, IBrowserRequest) #grok.adapts(IDict, IBrowserRequest) #grok.provides(ISimpleInputWidget) template = ViewPageTemplateFile('dictwidget.pt') def __init__(self, context, request, subwidget=None): super(DictWidget, self).__init__(context, None, request) self.subwidget = subwidget # The subwidgets are cached in this dict if preserve_widgets is True. self._widgets = {} self.preserve_widgets = False def _getWidget(self, i): """Return a widget for the i-th number of the sequence. Normally this method creates a new widget each time, but when the ``preserve_widgets`` attribute is True, it starts caching widgets. We need it so that the errors on the subwidgets would appear only if ``getInputValue`` was called. ``getInputValue`` on the subwidgets gets called on each request that has data. """ if i not in self._widgets: key_field = self.context.key_type if self.subwidget is not None: key_widget = self.subwidget(key_field, self.request) else: key_widget = getMultiAdapter( (key_field, self.request), IInputWidget) #key_widget.setPrefix('%s.key.%d.' % (self.name, i)) key_widget.setPrefix('%s.key.%d.' % (self.name, i)) value_field = self.context.value_type if self.subwidget is not None: value_widget = self.subwidget(value_field, self.request) else: value_widget = getMultiAdapter( (value_field, self.request), IInputWidget) #value_widget.setPrefix('%s.value.%d.' % (self.name, i)) value_widget.setPrefix('%s.value.%d.' % (self.name, i)) #import pdb; pdb.set_trace() if not self.preserve_widgets: #return super(DictWidget, self)._getWidget(i) return (key_widget, value_widget) self._widgets[i] = (key_widget, value_widget) #return super(DictWidget, self)._getWidget(i) return self._widgets[i] if i not in self._widgets: field = self.context.value_type if self.subwidget is not None: widget = self.subwidget(field, self.request) else: widget = getMultiAdapter( (field, self.request), IInputWidget) widget.setPrefix('%s.%d.' % (self.name, i)) if not self.preserve_widgets: return widget self._widgets[i] = widget return self._widgets[i] def widgets(self): """Return a list of widgets to display""" sequence = self._getRenderedValue() result = [] for i, value in enumerate(sequence.items()): widget = self._getWidget(i) widget[0].setRenderedValue(value[0]) widget[1].setRenderedValue(value[1]) result.append(widget) return result def _generateSequence(self): """Extract the values of the subwidgets from the request. Returns a list of key-value tuples. This can only be called if self.hasInput() returns true. """ if self.context.value_type is None: # Why would this ever happen? return [] if self.context.key_type is None: return [] # the marker field tells how many individual items were # included in the input; we check for exactly that many input # widgets try: count = int(self.request.form[self.name + ".count"]) except ValueError: # could not convert to int; the input was not generated # from the widget as implemented here raise WidgetInputError(self.context.__name__, self.context.title) # pre-populate sequence = [(None, None)] * count #sequence = [None] * count #sequence = dict() # now look through the request for interesting values # in reverse so that we can remove items as we go removing = self.name + ".remove" in self.request.form for i in reversed(range(count)): #widget = self._getWidget(i) key_widget, value_widget = self._getWidget(i) key = None value = None if key_widget.hasValidInput(): # catch and set sequence widget errors to ``_error`` attribute try: key = key_widget.getInputValue() #sequence[i] = widget.getInputValue() except WidgetInputError, error: self._error = error raise self._error if value_widget.hasValidInput(): # catch and set sequence widget errors to ``_error`` attribute try: value = value_widget.getInputValue() #sequence[i] = widget.getInputValue() except WidgetInputError, error: self._error = error raise self._error sequence[i] = (key, value) #if widget.hasValidInput(): # # catch and set sequence widget errors to ``_error`` attribute # try: # sequence[i] = widget.getInputValue() # except WidgetInputError, error: # self._error = error # raise self._error remove_key = "%s.remove_%d" % (self.name, i) if remove_key in self.request.form and removing: del sequence[i] # add an entry to the list if the add button has been pressed if self.name + ".add" in self.request.form: # Should this be using self.context.value_type.missing_value # instead of None? sequence.append((None, None)) return sequence def getInputValue(self): """Return converted and validated widget data. If there is no user input and the field is required, then a ``MissingInputError`` will be raised. If there is no user input and the field is not required, then the field default value will be returned. A ``WidgetInputError`` is raised in the case of one or more errors encountered, inputting, converting, or validating the data. """ sequence = super(DictWidget, self).getInputValue() return sequence return self._type(sequence) def _getRenderedValue(self): """Returns a dict from the request or _data""" if self._renderedValueSet(): if self._data is self.context.missing_value: #sequence = [] sequence = dict() else: #sequence = list(self._data) sequence = dict(self._data) elif self.hasInput(): #sequence = self._generateSequence() sequence = dict(self._generateSequence()) elif self.context.default is not None: #sequence = self.context.default sequence = self.context.default else: #sequence = [] seuence = dict() # ensure minimum number of items in the form #while len(sequence) < self.context.min_length: # # Shouldn't this use self.field.value_type.missing_value, # # instead of None? # #sequence.append(None) # pass return sequence # TODO: applyChanges isn't reporting "change" correctly (we're # re-generating the sequence with every edit, and need to be smarter) def applyChanges(self, content): field = self.context value = self.getInputValue() change = field.query(content, self) != value if change: print "SET: ", field, content, value field.set(content, value) else: print "NO SET: ", field, content, value return change # Basic display widget class DictDisplayWidget(DisplayWidget): _missingValueMessage = _("sequence-value-not-provided", u"(no value available)") _emptySequenceMessage = _("sequence-value-is-empty", u"(no values)") tag = "ol" itemTag = "li" cssClass = "sequenceWidget" extra = "" def __init__(self, context, request, subwidget=None): # def __init__(self, context, field, request, subwidget=None): super(DictDisplayWidget, self).__init__(context, request) self.subwidget = subwidget def __call__(self): # get the data to display: #import pdb; pdb.set_trace() if self._renderedValueSet(): data = self._data else: data = self.context.get(self.context.context) # deal with special cases: if data == self.context.missing_value: return translate(self._missingValueMessage, self.request) #data = list(data) data = dict(data) if not data: return translate(self._emptySequenceMessage, self.request) parts = [] # for i, item in enumerate(data): for i, item in enumerate(data.items()): #widget = self._getWidget(i) key_widget, value_widget = self._getWidget(i) key_widget.setRenderedValue(item[0]) value_widget.setRenderedValue(item[1]) #widget.setRenderedValue(item) #s = widget() s = "%s: %s" % (key_widget(), value_widget()) if self.itemTag: s = "<%s>%s" % (self.itemTag, s, self.itemTag) parts.append(s) contents = "\n".join(parts) if self.tag: contents = "\n%s\n" % contents contents = renderElement(self.tag, cssClass=self.cssClass, extra=self.extra, contents=contents) return contents def _getWidget(self, i): """Get a tuple of key-value widgets for the i-th 'row' of dict. """ key_field = self.context.key_type value_field = self.context.value_type if self.subwidget is not None: key_widget = self.subwidget(key_field, self.request) value_widget = self.subwidget(value_field, self.request) else: key_widget = getMultiAdapter( (key_field, self.request), IDisplayWidget) value_widget = getMultiAdapter( (value_field, self.request), IDisplayWidget) key_widget.setPrefix('%s.key.%d.' % (self.name, i)) value_widget.setPrefix('%s.value.%d.' % (self.name, i)) return (key_widget, value_widget)