Джанго формы, наследование и порядок полей формы - PullRequest
62 голосов
/ 27 мая 2009

Я использую формы Django на своем веб-сайте и хотел бы контролировать порядок полей.

Вот как я определяю свои формы:

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


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

Имя является неизменным и должно быть указано только при создании объекта. Я использую наследование, чтобы добавить последовательность и принципы СУХОЙ. То, что происходит, что не является ошибочным, на самом деле вполне ожидаемым, это то, что поле имени указано последним в view / 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())

Это меня заткнуло :)

Ответы [ 11 ]

98 голосов
/ 15 июля 2009

Из Джанго 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']
16 голосов
/ 12 июня 2015

От Джанго 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

11 голосов
/ 12 апреля 2010

Я использовал решение, опубликованное Selene, но обнаружил, что оно удалило все поля, которые не были назначены keyOrder. Форма, которую я делю на подклассы, имеет много полей, поэтому мне это не очень помогло. Я кодировал эту функцию, чтобы решить проблему, используя ответ акайхолы, но если вы хотите, чтобы она работала как Селена, все, что вам нужно сделать, это установить 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 голосов
/ 12 мая 2014

Похоже, что в какой-то момент базовая структура порядка полей была изменена с специфического для 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

Я уверен, что кто-то может сыграть в гольф на две-три линии, но для С.О. Я думаю, что цели, показывающие, как это работает, лучше, чем колун.

7 голосов
/ 12 декабря 2011

Вы также можете создать декоратор для упорядочения полей (на основе решения Джошуа):

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 голосов
/ 16 декабря 2014

В подходе принятого ответа используется внутренний 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)
3 голосов
/ 27 мая 2009

См. Примечания в этом SO-вопросе о том, как внутренности Джанго отслеживают порядок полей; ответы включают предложения о том, как «переупорядочить» поля по своему вкусу (в конце концов, все сводится к смешиванию с атрибутом .fields).

2 голосов
/ 10 ноября 2009

Альтернативные методы для изменения порядка полей:

Поп-и-вставка:

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

Pop-и-Append:

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

Pop-и-конкатенирующего все:

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') ] )

Но подход Селены из Поваренной книги Джанго все еще кажется яснее всего.

1 голос
/ 17 сентября 2012

Основано на ответе @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 .

0 голосов
/ 21 июня 2019

Приведенные выше ответы верны, но неполны. Они работают, только если все поля определены как переменные класса. А как насчет полей динамической формы, которые должны быть определены в инициализаторе (__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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...