I am having a difficult time finding documentation on how to write a custom widget.
My questions are:
- If I build a custom widget, can it be used equivalently for the admin interface or for normal forms?
- If I want to allow the user to edit a list of items, what widget should I subclass? What methods of the widget do I need to override/implement?
- What widget method is responsible for going from the user's input back to the data model?
Thanks.
You're right in that Django doesn't supply documentation on this specific topic. I advise you to look at the builtin widgets in django.forms.widgets
(I'll reference classes from that module below).
If I build a custom widget, can it be used equivalently for the admin interface or for normal forms?
Admin overrides some widgets (see django.contrib.admin.options.FORMFIELD_FOR_DBFIELD_DEFAULTS
). You can probably subclass ModelAdmin
and change the formfield_overrides
attribute, but I have never done anything with ModelAdmin
so I can't help here...
If I want to allow the user to edit a list of items, what widget should I subclass? What methods of the widget do I need to override/implement?
Your widget probably doesn't have anything in common with the default widgets (with Select
if any?!). Subclass from Widget
and if you find any common pattern with builtins, you can still change it later.
Implement the following methods:
render(self, name, value, attrs=None, renderer=None)
Check out
Input.render
for a simple example. It also supports user-defined attributes that are included in the HTML. You may also want to add "id" attributes, seeMultipleHiddenInput.render
on how to do that. Don't forget to usemark_safe
when outputting HTML directly. If you have a rather complex widget you can use template rendering (example)._has_changed(self, initial, data)
Optional. Used in admin to log messages about what was changed.
What widget method is responsible for going from the user's input back to the data model?
That has nothing to do with the widget - Django can't know what widget was used in an earlier request. It can only use the form (POST) data sent from the form. Therefore, the field method Field.to_python
is used to convert input to the Python data type (may raise ValidationError
if the input is invalid).
Django <1.11
Additionally to the other answers, this is a small code sample of a custom widget:
widgets.py
:
from django.forms.widgets import Widget
from django.template import loader
from django.utils.safestring import mark_safe
class MyWidget(Widget):
template_name = 'myapp/my_widget.html'
def get_context(self, name, value, attrs=None):
return {'widget': {
'name': name,
'value': value,
}}
def render(self, name, value, attrs=None):
context = self.get_context(name, value, attrs)
template = loader.get_template(self.template_name).render(context)
return mark_safe(template)
my_widget.html
:
<textarea id="mywidget-{{ widget.name }}" name="{{ widget.name }}">
{% if widget.value %}{{ widget.value }}{% endif %}</textarea>
Django 1.11
Widgets are now rendered using the form rendering API.
NOTE: There are three questions here. For the first two questions, see the fuller answer by AndiDog. I'm only answering the third question here:
Q. What widget method is responsible for going from the user's input back to the data model?
A. The value_from_datadict
method -- it's sort of the inverse of a widget's render
method. This method is presumably what the Django docs on widgets are referring to when it says "The widget handles the rendering of the HTML, and the extraction of data from a GET/POST dictionary that corresponds to the widget." There's nothing further on this point in the docs, but you can see how it works from the code for the built-in widgets.
Usually I start by inheriting from one of the existing widgets, add a new desired property and then modify a render method. Here's an example for a filterable select widget I implemented. The filtering is done via jquery mobile.
class FilterableSelectWidget(forms.Select):
def __init__(self, attrs=None, choices=()):
super(FilterableSelectWidget, self).__init__(attrs, choices)
# choices can be any iterable, but we may need to render this widget
# multiple times. Thus, collapse it into a list so it can be consumed
# more than once.
self._data_filter = {}
@property
def data_filter(self):
return self._data_filter
@data_filter.setter
def data_filter(self, attr_dict):
self._data_filter.update(attr_dict)
def render_option(self, selected_choices, option_value, option_label):
option_value = force_text(option_value)
if option_value in selected_choices:
selected_html = mark_safe(' selected="selected"')
if not self.allow_multiple_selected:
# Only allow for a single selection.
selected_choices.remove(option_value)
else:
selected_html = ''
# use self.data_filter
filtertext = self.data_filter.get(option_value)
data_filtertext = 'data-filtertext="{filtertext}"'.\
format(filtertext=filtertext) if filtertext else ''
return format_html('<option value="{0}"{1} {3}>{2}</option>',
option_value,
selected_html,
force_text(option_label),
mark_safe(data_filtertext))
Then in the views where I create a form, I'll set the data_filter for the field.
some_form.fields["some_field"] = \
forms.ChoiceField(choices=choices,
widget=FilterableSelectWidget)
some_form.fields["some_field"].widget.data_filter = \
data_filter
The documentation on Django's site doesn't help with this at all. It's suggestions on customisation of widgets, here, break the use of form.as_p()
which then jeopardises the value of forms as presented in Django, i.e.,: an assemblage of widgets.
The solutions I liked best is floppyforms. It facilitates the definition of widgets using templates and is a (almost) transparent replacement for Django's own forms module. It has excellent documentation and is easy to pick up.
精彩评论