Django:如何构建自定义表单小部件?

时间:2011-01-16 18:27:04

标签: python django forms

我很难找到有关如何编写自定义小部件的文档。

我的问题是:

  • 如果我构建自定义窗口小部件,是否可以等效地用于管理界面或普通表单?
  • 如果我想允许用户编辑项目列表,我应该将哪些小部件子类化?我需要覆盖/实现哪些小部件方法?
  • 哪种窗口小部件方法负责从用户的输入回到数据模型?

感谢。

5 个答案:

答案 0 :(得分:47)

你说得对,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)

    可选。在admin中用于记录有关更改内容的消息。

  

哪种小部件方法负责从用户的输入回到数据模型?

这与小部件无关 - Django无法知道先前请求中使用了哪个小部件。它只能使用从表单发送的表单(POST)数据。因此,字段方法Field.to_python用于将输入转换为Python数据类型(如果输入无效,可能会引发ValidationError。)

答案 1 :(得分:21)

Django< 1.11

除了其他答案,这是自定义小部件的一个小代码示例:

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

现在使用form rendering API呈现窗口小部件。

答案 2 :(得分:5)

注意:这里有三个问题。对于前两个问题,请参阅AndiDog的更全面的答案。我现在只回答第三个问题:

Q值。什么小部件方法负责从用户的输入回到数据模型?

一个。 value_from_datadict方法 - 它与窗口小部件的render方法相反。这个方法可能就是小部件上的Django文档所说的“小部件处理HTML的呈现,以及从与小部件对应的GET / POST字典中提取数据”。在文档中没有进一步的内容,但您可以从内置小部件的代码中看到它是如何工作的。

答案 3 :(得分:3)

通常我首先从一个现有的小部件继承,添加一个新的所需属性,然后修改一个render方法。这是我实现的可过滤选择小部件的示例。过滤是通过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

答案 4 :(得分:0)

关于Django网站的文档根本没有帮助。它关于小部件定制的建议,here,打破了form.as_p()的使用,然后危害了Django中呈现的形式的价值,即:小部件的集合。

我最喜欢的解决方案是floppyforms。它有助于使用模板定义小部件,并且是Django自己的表单模块的(几乎)透明替代品。它具有出色的文档,易于获取。