Python Django:如何构建自定义表单小部件?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4707192/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Django: How to build a custom form widget?
提问by Nick Heiner
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.
谢谢。
回答by AndiDog
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).
你说得对,Django 没有提供关于这个特定主题的文档。我建议您查看中的内置小部件django.forms.widgets(我将在下面引用该模块中的类)。
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 ModelAdminand change the formfield_overridesattribute, but I have never done anything with ModelAdminso I can't help here...
管理员覆盖了一些小部件(请参阅 参考资料django.contrib.admin.options.FORMFIELD_FOR_DBFIELD_DEFAULTS)。您可能可以子类化ModelAdmin并更改formfield_overrides属性,但我从未做过任何事情,ModelAdmin所以我无能为力...
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 Selectif any?!). Subclass from Widgetand if you find any common pattern with builtins, you can still change it later.
您的小部件可能与默认小部件没有任何共同之处(Select如果有的话?!)。从子类Widget,如果你发现有任何建宏常见的模式,仍然可以进行更改。
Implement the following methods:
实现以下方法:
render(self, name, value, attrs=None, renderer=None)Check out
Input.renderfor a simple example. It also supports user-defined attributes that are included in the HTML. You may also want to add "id" attributes, seeMultipleHiddenInput.renderon how to do that. Don't forget to usemark_safewhen 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.
render(self, name, value, attrs=None, renderer=None)查看
Input.render一个简单的例子。它还支持包含在 HTML 中的用户定义属性。您可能还想添加“id”属性,请参阅MultipleHiddenInput.render如何执行此操作。mark_safe直接输出HTML时不要忘记使用。如果您有一个相当复杂的小部件,您可以使用模板渲染(示例)。_has_changed(self, initial, data)可选的。在管理员中用于记录有关更改内容的消息。
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_pythonis used to convert input to the Python data type (may raise ValidationErrorif the input is invalid).
这与小部件无关 - Django 不知道在早期请求中使用了什么小部件。它只能使用从表单发送的表单(POST)数据。因此,field 方法Field.to_python用于将输入转换为 Python 数据类型(ValidationError如果输入无效,可能会引发)。
回答by Ghopper21
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:
注意:这里有三个问题。对于前两个问题,请参阅 AndiDog 的更完整答案。我在这里只回答第三个问题:
Q. What widget method is responsible for going from the user's input back to the data model?
问:什么小部件方法负责将用户的输入返回到数据模型?
A. The value_from_datadictmethod -- it's sort of the inverse of a widget's rendermethod. 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.
A.value_from_datadict方法——它有点像小部件的render方法。这个方法大概就是 Django 文档关于小部件的意思,当它说“小部件处理 HTML 的呈现,以及从对应于小部件的 GET/POST 字典中提取数据”时。文档中没有关于这一点的更多内容,但您可以从内置小部件的代码中了解它是如何工作的。
回答by Al Conrad
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.
通常我首先从现有小部件之一继承,添加一个新的所需属性,然后修改渲染方法。这是我实现的可过滤选择小部件的示例。过滤是通过 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.
然后在创建表单的视图中,我将为该字段设置 data_filter。
some_form.fields["some_field"] = \
forms.ChoiceField(choices=choices,
widget=FilterableSelectWidget)
some_form.fields["some_field"].widget.data_filter = \
data_filter
回答by Peter Shannon
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.
Django 站点上的文档对此根本没有帮助。这是关于小部件定制的建议,在这里,打破使用form.as_p()然后危及 Django 中呈现的表单的价值,即:小部件的组合。
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.
我最喜欢的解决方案是floppyforms。它有助于使用模板定义小部件,并且是 Django 自己的表单模块的(几乎)透明替代品。它有很好的文档,很容易上手。
回答by Wtower
Django <1.11
姜戈 <1.11
Additionally to the other answers, this is a small code sample of a custom widget:
除了其他答案之外,这是一个自定义小部件的小代码示例:
widgets.py:
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:
my_widget.html:
<textarea id="mywidget-{{ widget.name }}" name="{{ widget.name }}">
{% if widget.value %}{{ widget.value }}{% endif %}</textarea>
Django 1.11
Django 1.11
Widgets are now rendered using the form rendering API.
现在使用表单呈现 API呈现小部件。

