Django表单,表单字段的继承和顺序
Posted
技术标签:
【中文标题】Django表单,表单字段的继承和顺序【英文标题】:Django forms, inheritance and order of form fields 【发布时间】:2010-10-29 03:41:52 【问题描述】:我在我的网站中使用 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())
这让我闭嘴:)
【问题讨论】:
多年以后我相信你已经知道了,但是python中的类名应该总是“CamelCased”。只有方法名称是“named_with_underscore”。 您的意思是“camelCased”吗? :) TitleCase 是正确的术语,我相信。 【参考方案1】:接受的答案的方法使用了在 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 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)
【讨论】:
【参考方案2】:以上答案正确但不完整。它们仅在所有字段都定义为类变量时才有效。必须在初始化程序 (__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
,但适用于field1
和field2
(如果我们重新排序)。您可能想尝试在初始化程序中定义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
【讨论】:
【参考方案3】:来自 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']
【讨论】:
它似乎有效,并证明了 SortedDict “不提供任何方式来重新排序键:值对”的假设是错误的。 这很好,但请记住仅将它用于派生自forms.Form
的表单。对于forms.ModelForm
,您应该在嵌套class Meta
的fields
属性中设置它。见这里:***.com/questions/2893471/…
这在 Django 1.7 切换到 collections.OrderedDict
后不再有效
@ThomWiggers - 修改 OrderedDict(通过弹出和重新插入)会在 1.7+ 中解决这个问题吗?
@askvictor 弹出和重新插入似乎相当低效。我已经发布了我的解决方案below。【参考方案4】:
来自 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
【讨论】:
参见docs.djangoproject.com/en/1.10/ref/forms/api/… 获取文档【参考方案5】:我构建了一个表单“ExRegistrationForm”,它继承自 Django-Registration-Redux 的“RegistrationForm”。我遇到了两个问题,其中一个是在创建新表单后重新排序 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 %
【讨论】:
【参考方案6】:似乎在某些时候,字段顺序的底层结构从 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.目的我认为清楚地展示它是如何工作的比切肉刀更好。
【讨论】:
Here 是一个漂亮的例子,其中包含适用于新旧版本 Django 的 oneliner。它使用特征检测,因此支持字段的 SortedDict 和 OrderedDict 实现。 感谢 Ted 的回答和@aruseni 的评论。在我看来,这是最好的方法,因为它并没有真正深入 Django 核心。我确认它在 1.9 上仍然有效。【参考方案7】:基于@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 包中定义。
【讨论】:
【参考方案8】:您还可以创建一个装饰器来对字段进行排序(受 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()
【讨论】:
【参考方案9】:我使用了 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】:改变字段顺序的替代方法:
弹出插入:
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') ] )
但 Django CookBook 中的 Selene 方法仍然让人感觉最清晰。
【讨论】:
你不能插入OrderedDict
【参考方案11】:
请参阅this SO question 中关于 Django 内部跟踪字段顺序的方式的注释;答案包括有关如何根据自己的喜好“重新排序”字段的建议(最终归结为与 .fields
属性混淆)。
【讨论】:
以上是关于Django表单,表单字段的继承和顺序的主要内容,如果未能解决你的问题,请参考以下文章