Я использую Django-формы на своем веб-сайте и хочу контролировать порядок полей.
Вот как я определяю свои формы:
class edit_form(forms.Form):
summary = forms.CharField()
description = forms.CharField(widget=forms.TextArea)
class create_form(edit_form):
name = forms.CharField()
Имя является неизменным и должно указываться только при создании объекта. Я использую наследование, чтобы добавить последовательность и СУХИЕ принципы. То, что происходит, что не является ошибочным, на самом деле полностью ожидаемым, состоит в том, что поле имени указано последним в представлении /html, но я бы хотел, чтобы поле имени находилось поверх резюме и описания. Я понимаю, что могу легко исправить это, скопировав резюме и описание в create_form и потеряв наследование, но я хотел бы знать, возможно ли это.
Зачем? Представьте, что у вас есть 100 полей в edit_form и вам нужно добавить 10 полей вверху в create_form - копирование и поддержка двух форм выглядят не так сексуально. (Это не мой случай, я просто пример)
Итак, как я могу переопределить это поведение?
Edit:
По-видимому, нет правильного способа сделать это, не пропуская неприятных хаков (играя с атрибутом .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())
Это закрыло меня:)
Ответ 1
Из Джанго 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.
До Джанго 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']
Ответ 2
Из 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
Ответ 3
Я использовал решение, отправленное 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 fields. After running the form 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'])
Ответ 4
Похоже, что в какой-то момент базовая структура порядка поля была изменена с конкретного 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
Я уверен, что кто-то может играть в гольф на две или три линии, но для С.О. цели, я думаю, что ясное представление о том, как это работает, лучше, чем обрезчик.
Ответ 5
Вы также можете создать декоратор для заказа полей (на основе решения Джошуа):
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
В принятом способе ответа используется внутренний API форм Django, который был изменен в 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 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)
Ответ 7
См. примечания в этом вопросе SO о том, как внутренние элементы Django отслеживают порядок полей; ответы включают предложения о том, как "переупорядочить" поля по своему усмотрению (в конце концов это сводится к возиться с атрибутом .fields
).
Ответ 8
Альтернативные методы для изменения порядка полей:
Поп-и-вставки:
self.fields.insert(0, 'name', self.fields.pop('name'))
Поп-и-Append:
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 по-прежнему чувствует себя лучше всех.
Ответ 9
На основе ответа от @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()
В приведенном выше примере мы расширяем базовый класс EasyContactUsForm, как он определен в пакете django-easycontactus.
Ответ 10
Я построил форму "ExRegistrationForm", унаследованную от "RegistrationForm" из Django-Registration-Redux. Я столкнулся с двумя проблемами, одна из которых была переупорядочиванием полей на странице вывода 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 сверху: В шаблонах /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 %}
Ответ 11
Приведенные выше ответы правильны, но неполны. Они работают, только если все поля определены как переменные класса. А как насчет полей динамической формы, которые должны быть определены в intitialiser (__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
(если мы 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