Django形式,继承和表单字段的顺序

时间:2009-05-27 01:46:50

标签: python django django-forms

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

以下是我定义表单的方式:

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


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

名称是不可变的,只应在创建实体时列出。我使用继承来增加一致性和DRY原则。事实上完全预料到的不是错误的是名称字段在视图/ html的最后列出,但我希望名称字段位于摘要和描述之上。我确实意识到我可以通过将摘要和描述复制到create_form并松散继承来轻松修复它,但我想知道这是否可行。

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

那么,我该如何覆盖这种行为?

修改

显然,如果不经历讨厌的黑客攻击(摆弄.field属性),没有正确的方法可以做到这一点。 .field属性是SortedDict(Django的内部数据结构之一),它不提供任何重新排序键:值对的方法。它确实提供了一种在给定索引处插入项目的方法,但这会将项目从类成员移动到构造函数中。这种方法可行,但使代码可读性降低。我认为唯一合适的另一种方法是修改框架本身,这在大多数情况下都不是最佳的。

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

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())

那让我闭嘴:)

11 个答案:

答案 0 :(得分:98)

来自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']

答案 1 :(得分:16)

来自Django 1.9:https://docs.djangoproject.com/en/1.10/ref/forms/api/#notes-on-field-ordering

原始回答: Django 1.9默认会在field_order的表单上支持此功能:

class MyForm(forms.Form):
    ...
    field_order = ['field_1', 'field_2']
    ...

https://github.com/django/django/commit/28986da4ca167ae257abcaf7caea230eca2bcd80

答案 2 :(得分:11)

我使用了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'])

答案 3 :(得分: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

我敢肯定有人可以把它打成两三线,但对于S.O.目的我清楚地表明它的工作原理比切割器更好。

答案 4 :(得分: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()

答案 5 :(得分:6)

接受的答案的方法是使用在Django 1.7中更改的内部Django表单API。 The project team's opinion is that it should never have been used in the first place.我现在使用此函数重新排序表单。此代码使用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 ChangeOpenQuestion(ChangeMultipleChoice):

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

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

答案 6 :(得分:3)

请参阅this SO question中有关Django内部跟踪字段顺序的说明;答案包括如何根据自己的喜好“重新排序”字段的建议(最后归结为弄乱.fields属性)。

答案 7 :(得分: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方法仍然让人感觉最清楚。

答案 8 :(得分:1)

基于@akaihola的回答并更新为使用最新的Django 1.5 self.fields.insert正在depreciated

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()

在上面我们正在扩展一个EasyContactUsForm基类,因为它在django-easycontactus包中定义。

答案 9 :(得分: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:使FirstName和LastName出现在顶部:在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 %}

答案 10 :(得分:0)

以上答案正确但不完整。仅当所有字段都定义为类变量时,它们才起作用。那么必须在初始化器(__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