【发布时间】:2011-06-10 01:58:07
【问题描述】:
我很难找到有关如何编写自定义小部件的文档。
我的问题是:
- 如果我构建了一个自定义小部件,它是否可以等效地用于管理界面或普通表单?
- 如果我想允许用户编辑项目列表,我应该对哪个小部件进行子类化?我需要覆盖/实现小部件的哪些方法?
- 什么小部件方法负责从用户输入返回数据模型?
谢谢。
【问题讨论】:
我很难找到有关如何编写自定义小部件的文档。
我的问题是:
谢谢。
【问题讨论】:
你说得对,Django 没有提供关于这个特定主题的文档。我建议您查看django.forms.widgets 中的内置小部件(我将在下面引用该模块中的类)。
如果我构建了一个自定义小部件,它是否可以等效地用于管理界面或普通表单?
管理员会覆盖一些小部件(请参阅django.contrib.admin.options.FORMFIELD_FOR_DBFIELD_DEFAULTS)。您可能可以继承 ModelAdmin 并更改 formfield_overrides 属性,但我从未对 ModelAdmin 做过任何事情,所以我在这里帮不上忙...
如果我想允许用户编辑项目列表,我应该对哪个小部件进行子类化?我需要覆盖/实现小部件的哪些方法?
您的小部件可能与默认小部件没有任何共同之处(Select,如果有的话?!)。 Widget 的子类,如果你发现任何内置的通用模式,你仍然可以在以后更改它。
实现以下方法:
render(self, name, value, attrs=None, renderer=None)
查看Input.render 以获得一个简单的示例。它还支持包含在 HTML 中的用户定义属性。您可能还想添加“id”属性,请参阅MultipleHiddenInput.render 了解如何执行此操作。直接输出 HTML 时不要忘记使用mark_safe。如果您有一个相当复杂的小部件,您可以使用模板渲染 (example)。
_has_changed(self, initial, data)
可选。在管理员中用于记录有关更改内容的消息。
什么小部件方法负责从用户输入返回数据模型?
这与小部件无关 - Django 无法知道在较早的请求中使用了什么小部件。它只能使用从表单发送的表单(POST)数据。因此,字段方法Field.to_python用于将输入转换为Python数据类型(如果输入无效,可能会引发ValidationError)。
【讨论】:
field.to_python,或者覆盖它需要自定义字段?
django.forms.forms.BoundField.as_widget的来源,有对render的调用:name参数是用于表单元素的HTML名称,value是绑定到它的值(可能None 用于未绑定字段,不知道)。 attrs 是应包含在 HTML 元素中的附加属性的字典,但这不是您在构造函数中定义的属性(例如 text = forms.TextArea(..., attrs = {...}) - 我找不到真正使用 attrs 参数的地方。
to_python,当然你不能覆盖覆盖内置的Field.to_python,因为这可能会破坏其他字段,所以你必须继承@987654349 @。一般来说,我建议您在这种情况下只搜索源代码 - 只要缺少文档。
value_from_datadict 方法转换 POST 数据 -- 这有点与小部件的 render 方法相反。这种方法大概是 Django 文档关于小部件的内容,当它说“小部件处理 HTML 的呈现,以及从对应于小部件的 GET/POST 字典中提取数据”时。
除了其他答案,这是一个自定义小部件的小代码示例:
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>
小部件现在使用form rendering API 呈现。
【讨论】:
注意:这里有三个问题。对于前两个问题,请参阅 AndiDog 的完整答案。我这里只回答第三个问题:
问。什么小部件方法负责从用户输入返回数据模型?
A. value_from_datadict 方法——它有点类似于小部件的 render 方法。这个方法大概是关于小部件的 Django 文档所指的,当它说“小部件处理 HTML 的呈现,以及从对应于小部件的 GET/POST 字典中提取数据”时。文档中没有进一步说明这一点,但您可以从内置小部件的代码中了解它是如何工作的。
【讨论】:
通常我从继承现有小部件之一开始,添加新的所需属性,然后修改渲染方法。这是我实现的可过滤选择小部件的示例。过滤是通过 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))
然后在我创建表单的视图中,我将为字段设置 data_filter。
some_form.fields["some_field"] = \
forms.ChoiceField(choices=choices,
widget=FilterableSelectWidget)
some_form.fields["some_field"].widget.data_filter = \
data_filter
【讨论】:
Django 网站上的文档对此一点帮助都没有。这是关于小部件定制的建议,here,打破了 form.as_p() 的使用,这会危及 Django 中呈现的表单的价值,即:小部件的组合。
我最喜欢的解决方案是floppyforms。它有助于使用模板定义小部件,并且是(几乎)透明的 Django 自己的表单模块的替代品。它具有出色的文档并且易于上手。
【讨论】: