Django表单,表单域的继承和顺序


69

我在我的网站中使用Django表单,并希望控制字段的顺序。

这是我定义表单的方式:

class edit_form(forms.Form):
    summary = forms.CharField()
    description = forms.CharField(widget=forms.TextArea)


class create_form(edit_form):
    name = forms.CharField()

名称是不可变的,仅在创建实体时列出。我使用继承来添加一致性和DRY原则。发生的事情并非错误,实际上是完全可以预料的,是名称字段在view / html中最后列出,但是我希望名称字段位于摘要和描述的顶部。我确实意识到,可以通过将摘要和描述复制到create_form并释放继承来轻松解决此问题,但是我想知道是否可行。

为什么?想象一下,您在edit_form中有100个字段,并且必须在create_form的顶部添加10个字段-复制和维护这两个表单看起来并不那么性感。(这是不是我的情况,我只是在举一个例子)

因此,如何覆盖此行为?

编辑:

显然,没有经过讨厌的破解(摆弄.field属性)就没有适当的方法。.field属性是SortedDict(Django内部数据结构之一),它不提供任何对key:value对进行重新排序的方法。但是,它确实提供了一种在给定索引处插入项目的方法,但是会将项目从类成员移动到构造函数中。该方法可以工作,但是会使代码的可读性降低。我认为合适的另一种方法是修改框架本身,这在大多数情况下都不是最优的。

简而言之,代码将变成这样:

class edit_form(forms.Form):
    summary = forms.CharField()
    description = forms.CharField(widget=forms.TextArea)


class create_form(edit_form):
    def __init__(self,*args,**kwargs):
        forms.Form.__init__(self,*args,**kwargs)

        self.fields.insert(0,'name',forms.CharField())

那让我闭嘴:)


5
多年以后,我确定您已经知道了这一点,但是python中的类名称应始终为“ CamelCased”。仅方法名称为“ named_with_underscore”。
Kit Sunde 2013年

您的意思是“ camelCased”吗?:)
jball037

5
我相信TitleCase是正确的术语。
马修·辛克尔

Answers:


107

从Django 1.9+起

Django 1.9添加了一个新Form属性,field_order无论字段在类中的声明顺序如何,都可以对字段进行排序。

class MyForm(forms.Form):
    summary = forms.CharField()
    description = forms.CharField(widget=forms.TextArea)
    author = forms.CharField()
    notes = form.CharField()

    field_order = ['author', 'summary']

缺少的字段按field_order类顺序排列,并附加在列表中指定的字段之后。上面的示例将按以下顺序生成字段:['author', 'summary', 'description', 'notes']

请参阅文档:https : //docs.djangoproject.com/en/stable/ref/forms/api/#notes-on-field-ordering

最高Django 1.6

我遇到了同样的问题,我发现了另一种在Django CookBook中对字段重新排序的技术:

class EditForm(forms.Form):
    summary = forms.CharField()
    description = forms.CharField(widget=forms.TextArea)


class CreateForm(EditForm):
    name = forms.CharField()

    def __init__(self, *args, **kwargs):
        super(CreateForm, self).__init__(*args, **kwargs)
        self.fields.keyOrder = ['name', 'summary', 'description']

6
假设SortedDict“不提供任何重新排序键:值对的方法”的假设似乎可行,并且证明是错误的。
akaihola

1
很好,但请记住仅将其用于衍生自的形式forms.Form。如果是forms.ModelForm,则应在nested class Metainfields属性中进行设置。在这里看到:stackoverflow.com/questions/2893471/...
托马斯Gandor

11
在Django 1.7切换至collections.OrderedDict
Thom Wiggers 2014年

@ThomWiggers-是否可以在1.7+版本中修改OrderedDict(通过弹出并重新插入)来解决?
askvictor 2014年

@askvictor弹出并重新插入似乎效率很低。我已经在下面发布了我的解决方案。
Thom Wiggers 2014年


12

我使用了Selene发布的解决方案,但发现它删除了所有未分配给keyOrder的字段。我要子类化的表单具有很多字段,因此这对我而言效果不佳。我使用akaihola的答案对此功能进行了编码,但是如果您希望它像Selene一样工作,则只需将其设置throw_away为即可True

def order_fields(form, field_list, throw_away=False):
    """
    Accepts a form and a list of dictionary keys which map to the
    form's fields. After running the form's fields list will begin
    with the fields in field_list. If throw_away is set to true only
    the fields in the field_list will remain in the form.

    example use:
    field_list = ['first_name', 'last_name']
    order_fields(self, field_list)
    """
    if throw_away:
        form.fields.keyOrder = field_list
    else:
        for field in field_list[::-1]:
            form.fields.insert(0, field, form.fields.pop(field))

这就是我在自己的代码中使用它的方式:

class NestableCommentForm(ExtendedCommentSecurityForm):
    # TODO: Have min and max length be determined through settings.
    comment = forms.CharField(widget=forms.Textarea, max_length=100)
    parent_id = forms.IntegerField(widget=forms.HiddenInput, required=False)

    def __init__(self, *args, **kwargs):
        super(NestableCommentForm, self).__init__(*args, **kwargs)
        order_fields(self, ['comment', 'captcha'])

10

似乎在某些时候,字段顺序的基础结构已从django更改SordedDict为python标准OrderedDict

因此,在1.7版中,我必须执行以下操作:

from collections import OrderedDict

class MyForm(forms.Form):

    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
        original_fields = self.fields
        new_order = OrderedDict()
        for key in ['first', 'second', ... 'last']:
            new_order[key] = original_fields[key]
        self.fields = new_order

我敢肯定有人可以打成两行或三行,但是出于特殊目的,我认为清楚地表明它的工作原理比切肉刀更好。


4
是一个适用于Django新旧版本的oneliners的漂亮示例。它使用功能检测,因此支持字段的SortedDict和OrderedDict实现。
Arseny 2014年

感谢您的回答Ted和@aruseni的评论。我认为这是最好的方法,因为它实际上并没有深入到Django核心。我确认它仍然适用于1.9。
Yoone

7

您还可以创建一个装饰器来订购字段(受Joshua解决方案的启发):

def order_fields(*field_list):
    def decorator(form):
        original_init = form.__init__
        def init(self, *args, **kwargs):
            original_init(self, *args, **kwargs)        
            for field in field_list[::-1]:
                self.fields.insert(0, field, self.fields.pop(field))
        form.__init__ = init
        return form            
    return decorator

这将确保传递给装饰器的所有字段都排在最前面。您可以像这样使用它:

@order_fields('name')
class CreateForm(EditForm):
    name = forms.CharField()

6

公认的答案方法使用内部Django表单API,该API在Django 1.7中已更改。项目团队的意见是,一开始就不应使用它。现在,我使用此功能对表单进行重新排序。这段代码利用了OrderedDict

def reorder_fields(fields, order):
    """Reorder form fields by order, removing items not in order.

    >>> reorder_fields(
    ...     OrderedDict([('a', 1), ('b', 2), ('c', 3)]),
    ...     ['b', 'c', 'a'])
    OrderedDict([('b', 2), ('c', 3), ('a', 1)])
    """
    for key, v in fields.items():
        if key not in order:
            del fields[key]

    return OrderedDict(sorted(fields.items(), key=lambda k: order.index(k[0])))

我在这样的类中使用:

class ChangeOpenQuestionForm(ChangeMultipleChoiceForm):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        key_order = ['title',
                     'question',
                     'answer',
                     'correct_answer',
                     'incorrect_answer']

        self.fields = reorder_fields(self.fields, key_order)

3

有关Django内部跟踪字段顺序的方式,请参见此SO问题中的注释。答案包括有关如何根据自己的喜好对字段进行“重新排序”的建议(最终归结为弄乱.fields属性)。


2

更改字段顺序的替代方法:

弹出并插入:

self.fields.insert(0, 'name', self.fields.pop('name'))

弹出并追加:

self.fields['summary'] = self.fields.pop('summary')
self.fields['description'] = self.fields.pop('description')

弹出并附加所有内容:

for key in ('name', 'summary', 'description'):
    self.fields[key] = self.fields.pop(key)

订购副本:

self.fields = SortedDict( [ (key, self.fields[key])
                            for key in ('name', 'summary' ,'description') ] )

但是Selene在Django CookBook中的方法仍然感觉最为清晰。


您不能插入OrderedDict
chhantyal,2015年

1

基于由@akaihola回答和更新的工作与最新的Django 1.5作为self.fields.insert正在贬值

from easycontactus.forms import *
from django import forms
class  CustomEasyContactUsForm(EasyContactUsForm):
    ### form settings and configuration
    CAPTHCA_PHRASE = 'igolf'

    ### native methods
    def __init__(self, *args, **kwargs):
        super(CustomEasyContactUsForm, self).__init__(*args, **kwargs)
        # re-order placement of added attachment field 
        self.fields.keyOrder.insert(self.fields.keyOrder.index('captcha'),
                                    self.fields.keyOrder.pop(self.fields.keyOrder.index('attachment'))
                                    )

    ### field defintitions
    attachment = forms.FileField()

在上面,我们扩展了django-easycontactus包中定义的EasyContactUsForm基类。


0

我从Django-Registration-Redux的'RegistrationForm'继承了一个表格'ExRegistrationForm'。我遇到了两个问题,其中一个是在创建新表单后重新排序html输出页面上的字段。

我解决了如下问题:

1.问题1:从注册表单中删除用户名:在my_app.forms.py中

    class ExRegistrationForm(RegistrationForm):
          #Below 2 lines extend the RegistrationForm with 2 new fields firstname & lastname
          first_name = forms.CharField(label=(u'First Name'))
          last_name = forms.CharField(label=(u'Last Name'))

          #Below 3 lines removes the username from the fields shown in the output of the this form
          def __init__(self, *args, **kwargs):
          super(ExRegistrationForm, self).__init__(*args, **kwargs)
          self.fields.pop('username')

2.问题2:使名字和姓氏出现在顶部:在templates / registration / registration_form.html中

您可以按所需顺序分别显示字段。如果字段的数量较少,这将有所帮助,但是如果您有很多字段,那么实际上就不可能以表格形式编写它们,这将是没有帮助的。

     {% extends "base.html" %}
     {% load i18n %}

     {% block content %}
     <form method="post" action=".">
          {% csrf_token %}

          #The Default html is: {{ form.as_p }} , which can be broken down into individual elements as below for re-ordering.
          <p>First Name: {{ form.first_name }}</p>
          <p>Last Name:  {{ form.last_name }}</p>
          <p>Email: {{ form.email }}</p>
          <p>Password: {{ form.password1 }}</p>
          <p>Confirm Password: {{ form.password2 }}</p>

          <input type="submit" value="{% trans 'Submit' %}" />
      </form>
      {% endblock %}

0

以上答案是正确的,但不完整。仅当所有字段都定义为类变量时,它们才起作用。那么在inititializer(__init__)中必须定义的动态表单字段呢?

from django import forms

class MyForm(forms.Form):
    field1 = ...
    field2 = ...

    field_order = ['val', 'field1', 'field2']

    def __init__(self, val_list, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
        vals = zip(val_list, val_list)
        self.fields['val'] = forms.CharField(choices=vals)

上面的方法永远不会起作用,val但是会适用于field1field2(如果我们重新排序)。您可能想要尝试field_order在初始化程序中进行定义:

class MyForm(forms.Form):
    # other fields

    def __init__(self, val_list, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
        vals = zip(val_list, val_list)
        self.fields['val'] = forms.CharField(choices=vals)
        self.field_order = ['val', 'field1', 'field2']

但是这也将失败,因为该场顺序是固定的在调用,的super()

因此,唯一的解决方案是构造函数(__new__)并将其设置field_order为类变量。

class MyForm(forms.Form):
    # other fields

    field_order = ['val', 'field1', 'field2']

    def __new__(cls, val_list, *args, **kwargs):
        form = super(MyForm, cls).__new__(cls)
        vals = zip(val_list, val_list)
        form.base_fields['val'] = forms.CharField(choices=vals)
        return form
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.