From 04775b4598d1a9c5bcc0c1da431e368f28dcd13e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 23 Oct 2012 07:02:48 -0400 Subject: [PATCH] Fixed #13997 - Added an example of constructing a MultiWidget and documented the value_from_datadict method. --- docs/ref/forms/widgets.txt | 192 +++++++++++++++++++++++++------------ 1 file changed, 129 insertions(+), 63 deletions(-) diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index 3c458930fa..a0ef0731ad 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -214,38 +214,49 @@ foundation for custom widgets. The 'value' given is not guaranteed to be valid input, therefore subclass implementations should program defensively. + .. method:: value_from_datadict(self, data, files, name) + + Given a dictionary of data and this widget's name, returns the value + of this widget. Returns ``None`` if a value wasn't provided. + .. class:: MultiWidget(widgets, attrs=None) A widget that is composed of multiple widgets. :class:`~django.forms.widgets.MultiWidget` works hand in hand with the :class:`~django.forms.MultiValueField`. - .. method:: render(name, value, attrs=None) + :class:`MultiWidget` has one required argument: - Argument `value` is handled differently in this method from the - subclasses of :class:`~Widget`. + .. attribute:: MultiWidget.widgets - If `value` is a list, output of :meth:`~MultiWidget.render` will be a - concatenation of rendered child widgets. If `value` is not a list, it - will be first processed by the method :meth:`~MultiWidget.decompress()` - to create the list and then processed as above. + An iterable containing the widgets needed. - Unlike in the single value widgets, method :meth:`~MultiWidget.render` - need not be implemented in the subclasses. + And one required method: .. method:: decompress(value) - Returns a list of "decompressed" values for the given value of the - multi-value field that makes use of the widget. The input value can be - assumed as valid, but not necessarily non-empty. + This method takes a single "compressed" value from the field and + returns a list of "decompressed" values. The input value can be + assumed valid, but not necessarily non-empty. This method **must be implemented** by the subclass, and since the value may be empty, the implementation must be defensive. The rationale behind "decompression" is that it is necessary to "split" - the combined value of the form field into the values of the individual - field encapsulated within the multi-value field (e.g. when displaying - the partially or fully filled-out form). + the combined value of the form field into the values for each widget. + + An example of this is how :class:`SplitDateTimeWidget` turns a + :class:`datetime` value into a list with date and time split into two + separate values:: + + class SplitDateTimeWidget(MultiWidget): + + # ... + + def decompress(self, value): + if value: + return [value.date(), value.time().replace(microsecond=0)] + return [None, None] .. tip:: @@ -254,6 +265,109 @@ foundation for custom widgets. with the opposite responsibility - to combine cleaned values of all member fields into one. + Other methods that may be useful to override include: + + .. method:: render(name, value, attrs=None) + + Argument ``value`` is handled differently in this method from the + subclasses of :class:`~Widget` because it has to figure out how to + split a single value for display in multiple widgets. + + The ``value`` argument used when rendering can be one of two things: + + * A ``list``. + * A single value (e.g., a string) that is the "compressed" representation + of a ``list`` of values. + + If `value` is a list, output of :meth:`~MultiWidget.render` will be a + concatenation of rendered child widgets. If `value` is not a list, it + will be first processed by the method :meth:`~MultiWidget.decompress()` + to create the list and then processed as above. + + In the second case -- i.e., if the value is *not* a list -- + ``render()`` will first decompress the value into a ``list`` before + rendering it. It does so by calling the ``decompress()`` method, which + :class:`MultiWidget`'s subclasses must implement (see above). + + When ``render()`` executes its HTML rendering, each value in the list + is rendered with the corresponding widget -- the first value is + rendered in the first widget, the second value is rendered in the + second widget, etc. + + Unlike in the single value widgets, method :meth:`~MultiWidget.render` + need not be implemented in the subclasses. + + .. method:: format_output(rendered_widgets) + + Given a list of rendered widgets (as strings), returns a Unicode string + representing the HTML for the whole lot. + + This hook allows you to format the HTML design of the widgets any way + you'd like. + + Here's an example widget which subclasses :class:`MultiWidget` to display + a date with the day, month, and year in different select boxes. This widget + is intended to be used with a :class:`~django.forms.DateField` rather than + a :class:`~django.forms.MultiValueField`, thus we have implemented + :meth:`~Widget.value_from_datadict`:: + + from datetime import date + from django.forms import widgets + + class DateSelectorWidget(widgets.MultiWidget): + def __init__(self, attrs=None): + # create choices for days, months, years + # example below, the rest snipped for brevity. + years = [(year, year) for year in (2011, 2012, 2013)] + _widgets = ( + widgets.Select(attrs=attrs, choices=days), + widgets.Select(attrs=attrs, choices=months), + widgets.Select(attrs=attrs, choices=years), + ) + super(DateSelectorWidget, self).__init__(_widgets, attrs) + + def decompress(self, value): + if value: + return [value.day, value.month, value.year] + return [None, None, None] + + def format_output(self, rendered_widgets): + return u''.join(rendered_widgets) + + def value_from_datadict(self, data, files, name): + datelist = [ + widget.value_from_datadict(data, files, name + '_%s' % i) + for i, widget in enumerate(self.widgets)] + try: + D = date(day=int(datelist[0]), month=int(datelist[1]), + year=int(datelist[2])) + except ValueError: + return '' + else: + return str(D) + + The constructor creates several :class:`Select` widgets in a tuple. The + ``super`` class uses this tuple to setup the widget. + + The :meth:`~MultiWidget.format_output` method is fairly vanilla here (in + fact, it's the same as what's been implemented as the default for + ``MultiWidget``), but the idea is that you could add custom HTML between + the widgets should you wish. + + The required method :meth:`~MultiWidget.decompress` breaks up a + ``datetime.date`` value into the day, month, and year values corresponding + to each widget. Note how the method handles the case where ``value`` is + ``None``. + + The default implementation of :meth:`~Widget.value_from_datadict` returns + a list of values corresponding to each ``Widget``. This is appropriate + when using a ``MultiWidget`` with a :class:`~django.forms.MultiValueField`, + but since we want to use this widget with a :class:`~django.forms.DateField` + which takes a single value, we have overridden this method to combine the + data of all the subwidgets into a ``datetime.date``. The method extracts + data from the ``POST`` dictionary and constructs and validates the date. + If it is valid, we return the string, otherwise, we return an empty string + which will cause ``form.is_valid`` to return ``False``. .. _built-in widgets: @@ -552,54 +666,6 @@ Composite widgets :attr:`~Field.choices` attribute. If it does, it will override anything you set here when the attribute is updated on the :class:`Field`. -``MultiWidget`` -~~~~~~~~~~~~~~~ - -.. class:: MultiWidget - - Wrapper around multiple other widgets. You'll probably want to use this - class with :class:`MultiValueField`. - - Its ``render()`` method is different than other widgets', because it has to - figure out how to split a single value for display in multiple widgets. - - Subclasses may implement ``format_output``, which takes the list of - rendered widgets and returns a string of HTML that formats them any way - you'd like. - - The ``value`` argument used when rendering can be one of two things: - - * A ``list``. - * A single value (e.g., a string) that is the "compressed" representation - of a ``list`` of values. - - In the second case -- i.e., if the value is *not* a list -- ``render()`` - will first decompress the value into a ``list`` before rendering it. It - does so by calling the ``decompress()`` method, which - :class:`MultiWidget`'s subclasses must implement. This method takes a - single "compressed" value and returns a ``list``. An example of this is how - :class:`SplitDateTimeWidget` turns a :class:`datetime` value into a list - with date and time split into two seperate values:: - - class SplitDateTimeWidget(MultiWidget): - - # ... - - def decompress(self, value): - if value: - return [value.date(), value.time().replace(microsecond=0)] - return [None, None] - - When ``render()`` executes its HTML rendering, each value in the list is - rendered with the corresponding widget -- the first value is rendered in - the first widget, the second value is rendered in the second widget, etc. - - :class:`MultiWidget` has one required argument: - - .. attribute:: MultiWidget.widgets - - An iterable containing the widgets needed. - ``SplitDateTimeWidget`` ~~~~~~~~~~~~~~~~~~~~~~~