【问题标题】:Django: How to build a custom form widget?Django:如何构建自定义表单小部件?
【发布时间】:2011-06-10 01:58:07
【问题描述】:

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

我的问题是:

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

谢谢。

【问题讨论】:

标签: python django forms


【解决方案1】:

你说得对,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,或者覆盖它需要自定义字段?
  • @Rosarch:检查django.forms.forms.BoundField.as_widget的来源,有对render的调用:name参数是用于表单元素的HTML名称,value是绑定到它的值(可能None 用于未绑定字段,不知道)。 attrs 是应包含在 HTML 元素中的附加属性的字典,但这不是您在构造函数中定义的属性(例如 text = forms.TextArea(..., attrs = {...}) - 我找不到真正使用 attrs 参数的地方。
  • @Rosarch:好像我之前的评论不见了...关于to_python,当然你不能覆盖覆盖内置的Field.to_python,因为这可能会破坏其他字段,所以你必须继承@987654349 @。一般来说,我建议您在这种情况下只搜索源代码 - 只要缺少文档。
  • @AndiDog -- 事实上,小部件确实通过value_from_datadict 方法转换 POST 数据 -- 这有点与小部件的 render 方法相反。这种方法大概是 Django 文档关于小部件的内容,当它说“小部件处理 HTML 的呈现,以及从对应于小部件的 GET/POST 字典中提取数据”时。
【解决方案2】:

Django

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

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 呈现。

【讨论】:

  • 请注意,小部件的 HTML 必须有一个 name 属性,否则 Django 管理员将无法读取它们的值,它们将被默默地从表单提交中排除。
【解决方案3】:

注意:这里有三个问题。对于前两个问题,请参阅 AndiDog 的完整答案。我这里只回答第三个问题:

问。什么小部件方法负责从用户输入返回数据模型?

A. value_from_datadict 方法——它有点类似于小部件的 render 方法。这个方法大概是关于小部件的 Django 文档所指的,当它说“小部件处理 HTML 的呈现,以及从对应于小部件的 GET/POST 字典中提取数据”时。文档中没有进一步说明这一点,但您可以从内置小部件的代码中了解它是如何工作的。

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      Django 网站上的文档对此一点帮助都没有。这是关于小部件定制的建议,here,打破了 form.as_p() 的使用,这会危及 Django 中呈现的表单的价值,即:小部件的组合。

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

      【讨论】:

        猜你喜欢
        • 2013-04-03
        • 2011-06-12
        • 1970-01-01
        • 2012-07-18
        • 2010-11-16
        • 2011-07-23
        • 2012-11-11
        • 2011-02-03
        • 2017-09-03
        相关资源
        最近更新 更多