From a73838fde33374573b8e765dfcb0225287d396c0 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 12 Sep 2012 06:59:19 -0400 Subject: [PATCH] Fixed #11185 - Expanded docs on customizing widgets; thanks fadeev for the draft patch. --- docs/ref/forms/fields.txt | 41 +++++-- docs/ref/forms/widgets.txt | 227 +++++++++++++++++++++++++++--------- docs/topics/forms/media.txt | 24 ++-- 3 files changed, 214 insertions(+), 78 deletions(-) diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 2a8f449799..7c06bf97ee 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -852,7 +852,7 @@ Slightly complex built-in ``Field`` classes ``MultiValueField`` ~~~~~~~~~~~~~~~~~~~ -.. class:: MultiValueField(**kwargs) +.. class:: MultiValueField(fields=(), **kwargs) * Default widget: ``TextInput`` * Empty value: ``''`` (an empty string) @@ -861,22 +861,39 @@ Slightly complex built-in ``Field`` classes as an argument to the ``MultiValueField``. * Error message keys: ``required``, ``invalid`` - This abstract field (must be subclassed) aggregates the logic of multiple - fields. Subclasses should not have to implement clean(). Instead, they must - implement compress(), which takes a list of valid values and returns a - "compressed" version of those values -- a single value. For example, - :class:`SplitDateTimeField` is a subclass which combines a time field and - a date field into a datetime object. + Aggregates the logic of multiple fields that together produce a single + value. + + This field is abstract and must be subclassed. In contrast with the + single-value fields, subclasses of :class:`MultiValueField` must not + implement :meth:`~django.forms.Field.clean` but instead - implement + :meth:`~MultiValueField.compress`. Takes one extra required argument: .. attribute:: fields - A list of fields which are cleaned into a single field. Each value in - ``clean`` is cleaned by the corresponding field in ``fields`` -- the first - value is cleaned by the first field, the second value is cleaned by - the second field, etc. Once all fields are cleaned, the list of clean - values is "compressed" into a single value. + A tuple of fields whose values are cleaned and subsequently combined + into a single value. Each value of the field is cleaned by the + corresponding field in ``fields`` -- the first value is cleaned by the + first field, the second value is cleaned by the second field, etc. + Once all fields are cleaned, the list of clean values is combined into + a single value by :meth:`~MultiValueField.compress`. + + .. attribute:: MultiValueField.widget + + Must be a subclass of :class:`django.forms.MultiWidget`. + Default value is :class:`~django.forms.widgets.TextInput`, which + probably is not very useful in this case. + + .. method:: compress(data_list) + + Takes a list of valid values and returns a "compressed" version of + those values -- in a single value. For example, + :class:`SplitDateTimeField` is a subclass which combines a time field + and a date field into a ``datetime`` object. + + This method must be implemented in the subclasses. ``SplitDateTimeField`` ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index 1935cb23bc..4724cbdec2 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -11,6 +11,16 @@ A widget is Django's representation of a HTML input element. The widget handles the rendering of the HTML, and the extraction of data from a GET/POST dictionary that corresponds to the widget. +.. tip:: + + Widgets should not be confused with the :doc:`form fields `. + Form fields deal with the logic of input validation and are used directly + in templates. Widgets deal with rendering of HTML form input elements on + the web page and extraction of raw submitted data. However, widgets do + need to be :ref:`assigned ` to form fields. + +.. _widget-to-field: + Specifying widgets ------------------ @@ -95,15 +105,23 @@ choices are inherent to the model and not just the representational widget. Customizing widget instances ---------------------------- -When Django renders a widget as HTML, it only renders the bare minimum -HTML - Django doesn't add a class definition, or any other widget-specific -attributes. This means that all :class:`TextInput` widgets will appear the same -on your Web page. +When Django renders a widget as HTML, it only renders very minimal markup - +Django doesn't add class names, or any other widget-specific attributes. This +means, for example, that all :class:`TextInput` widgets will appear the same +on your Web pages. -If you want to make one widget look different to another, you need to -specify additional attributes for each widget. When you specify a -widget, you can provide a list of attributes that will be added to the -rendered HTML for the widget. +There are two ways to customize widgets: :ref:`per widget instance +` and :ref:`per widget class `. + +.. _styling-widget-instances: + +Styling widget instances +^^^^^^^^^^^^^^^^^^^^^^^^ + +If you want to make one widget instance look different from another, you will +need to specify additional attributes at the time when the widget object is +instantiated and assigned to a form field (and perhaps add some rules to your +CSS files). For example, take the following simple form:: @@ -128,9 +146,7 @@ On a real Web page, you probably don't want every widget to look the same. You might want a larger input element for the comment, and you might want the 'name' widget to have some special CSS class. It is also possible to specify the 'type' attribute to take advantage of the new HTML5 input types. To do -this, you use the :attr:`Widget.attrs` argument when creating the widget: - -For example:: +this, you use the :attr:`Widget.attrs` argument when creating the widget:: class CommentForm(forms.Form): name = forms.CharField( @@ -147,24 +163,41 @@ Django will then include the extra attributes in the rendered output: Url: Comment: -.. _built-in widgets: +.. _styling-widget-classes: -Built-in widgets ----------------- +Styling widget classes +^^^^^^^^^^^^^^^^^^^^^^ -Django provides a representation of all the basic HTML widgets, plus some -commonly used groups of widgets: +With widgets, it is possible to add media (``css`` and ``javascript``) +and more deeply customize their appearance and behavior. -``Widget`` -~~~~~~~~~~ +In a nutshell, you will need to subclass the widget and either +:ref:`define a class "Media" ` as a member of the +subclass, or :ref:`create a property "media" `, returning an +instance of that class. -.. class:: Widget +These methods involve somewhat advanced Python programming and are described in +detail in the :doc:`Form Media ` topic guide. - This abstract class cannot be rendered, but provides the basic attribute :attr:`~Widget.attrs`. +.. _base-widget-classes: + +Base Widget classes +------------------- + +Base widget classes :class:`Widget` and :class:`MultiWidget` are subclassed by +all the :ref:`built-in widgets ` and may serve as a +foundation for custom widgets. + +.. class:: Widget(attrs=None) + + This abstract class cannot be rendered, but provides the basic attribute + :attr:`~Widget.attrs`. You may also implement or override the + :meth:`~Widget.render()` method on custom widgets. .. attribute:: Widget.attrs - A dictionary containing HTML attributes to be set on the rendered widget. + A dictionary containing HTML attributes to be set on the rendered + widget. .. code-block:: python @@ -172,6 +205,74 @@ commonly used groups of widgets: >>> name.render('name', 'A name') u'' + .. method:: render(name, value, attrs=None) + + Returns HTML for the widget, as a Unicode string. This method must be + implemented by the subclass, otherwise ``NotImplementedError`` will be + raised. + + The 'value' given is not guaranteed to be valid input, therefore + subclass implementations should program defensively. + +.. 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) + + Argument `value` is handled differently in this method from the + subclasses of :class:`~Widget`. + + 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. + + Unlike in the single value widgets, method :meth:`~MultiWidget.render` + need not be implemented in the subclasses. + + .. 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 **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). + + .. tip:: + + Note that :class:`~django.forms.MultiValueField` has a + complementary method :meth:`~django.forms.MultiValueField.compress` + with the opposite responsibility - to combine cleaned values of + all member fields into one. + + +.. _built-in widgets: + +Built-in widgets +---------------- + +Django provides a representation of all the basic HTML widgets, plus some +commonly used groups of widgets in the ``django.forms.widgets`` module, +including :ref:`the input of text `, :ref:`various checkboxes +and selectors `, :ref:`uploading files `, +and :ref:`handling of multi-valued input `. + +.. _text-widgets: + +Widgets handling input of text +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +These widgets make use of the HTML elements ``input`` and ``textarea``. + ``TextInput`` ~~~~~~~~~~~~~ @@ -205,39 +306,8 @@ commonly used groups of widgets: Hidden input: ```` -``MultipleHiddenInput`` -~~~~~~~~~~~~~~~~~~~~~~~ - -.. class:: MultipleHiddenInput - - Multiple ```` widgets. - - A widget that handles multiple hidden widgets for fields that have a list - of values. - - .. attribute:: MultipleHiddenInput.choices - - This attribute is optional when the field does not have a - :attr:`~Field.choices` attribute. If it does, it will override anything - you set here when the attribute is updated on the :class:`Field`. - -``FileInput`` -~~~~~~~~~~~~~ - -.. class:: FileInput - - File upload input: ```` - -``ClearableFileInput`` -~~~~~~~~~~~~~~~~~~~~~~ - -.. class:: ClearableFileInput - - .. versionadded:: 1.3 - - File upload input: ````, with an additional checkbox - input to clear the field's value, if the field is not required and has - initial data. + Note that there also is a :class:`MultipleHiddenInput` widget that + encapsulates a set of hidden input elements. ``DateInput`` ~~~~~~~~~~~~~ @@ -297,6 +367,11 @@ commonly used groups of widgets: Text area: ```` +.. _selector-widgets: + +Selector and checkbox widgets +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ``CheckboxInput`` ~~~~~~~~~~~~~~~~~ @@ -440,6 +515,50 @@ commonly used groups of widgets: ... +.. _file-upload-widgets: + +File upload widgets +^^^^^^^^^^^^^^^^^^^ + +``FileInput`` +~~~~~~~~~~~~~ + +.. class:: FileInput + + File upload input: ```` + +``ClearableFileInput`` +~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: ClearableFileInput + + .. versionadded:: 1.3 + + File upload input: ````, with an additional checkbox + input to clear the field's value, if the field is not required and has + initial data. + +.. _composite-widgets: + +Composite widgets +^^^^^^^^^^^^^^^^^ + +``MultipleHiddenInput`` +~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: MultipleHiddenInput + + Multiple ```` widgets. + + A widget that handles multiple hidden widgets for fields that have a list + of values. + + .. attribute:: MultipleHiddenInput.choices + + This attribute is optional when the field does not have a + :attr:`~Field.choices` attribute. If it does, it will override anything + you set here when the attribute is updated on the :class:`Field`. + ``MultiWidget`` ~~~~~~~~~~~~~~~ diff --git a/docs/topics/forms/media.txt b/docs/topics/forms/media.txt index 615dd7193c..29a7829799 100644 --- a/docs/topics/forms/media.txt +++ b/docs/topics/forms/media.txt @@ -38,6 +38,8 @@ in a form suitable for easy inclusion on your Web page. whichever toolkit suits your requirements. Django is able to integrate with any JavaScript toolkit. +.. _media-as-a-static-definition: + Media as a static definition ---------------------------- @@ -78,10 +80,8 @@ A dictionary describing the CSS files required for various forms of output media. The values in the dictionary should be a tuple/list of file names. See -`the section on media paths`_ for details of how to specify paths to media -files. - -.. _the section on media paths: `Paths in media definitions`_ +:ref:`the section on media paths ` for details of how to +specify paths to media files. The keys in the dictionary are the output media types. These are the same types accepted by CSS files in media declarations: 'all', 'aural', 'braille', @@ -117,8 +117,8 @@ If this last CSS definition were to be rendered, it would become the following H ``js`` ~~~~~~ -A tuple describing the required JavaScript files. See -`the section on media paths`_ for details of how to specify paths to media +A tuple describing the required JavaScript files. See :ref:`the section on +media paths ` for details of how to specify paths to media files. ``extend`` @@ -164,10 +164,10 @@ declaration to the media declaration:: If you require even more control over media inheritance, define your media -using a `dynamic property`_. Dynamic properties give you complete control over -which media files are inherited, and which are not. +using a :ref:`dynamic property `. Dynamic properties give +you complete control over which media files are inherited, and which are not. -.. _dynamic property: `Media as a dynamic property`_ +.. _dynamic-property: Media as a dynamic property --------------------------- @@ -198,9 +198,9 @@ Paths in media definitions .. versionchanged:: 1.3 Paths used to specify media can be either relative or absolute. If a path -starts with '/', 'http://' or 'https://', it will be interpreted as an absolute -path, and left as-is. All other paths will be prepended with the value of -the appropriate prefix. +starts with ``/``, ``http://`` or ``https://``, it will be interpreted as an +absolute path, and left as-is. All other paths will be prepended with the value +of the appropriate prefix. As part of the introduction of the :doc:`staticfiles app ` two new settings were added