Как Django узнает порядок отображения полей формы?

91

Если у меня есть форма Django, например:

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()

И я вызываю метод as_table () экземпляра этой формы, Django будет отображать поля в том же порядке, как указано выше.

Мой вопрос: как Django узнает порядок, в котором определены переменные класса?

(Также как мне переопределить этот порядок, например, когда я хочу добавить поле из метода init класса ?)

Грег
источник

Ответы:

89

[ПРИМЕЧАНИЕ: этот ответ сейчас полностью устарел - см. Обсуждение ниже и более свежие ответы].

Если fэто форма, ее поля - это f.fields, то есть django.utils.datastructures.SortedDict (элементы представлены в порядке их добавления). После построения формы f.fields имеет атрибут keyOrder, который представляет собой список, содержащий имена полей в том порядке, в котором они должны быть представлены. Вы можете установить правильный порядок (хотя вам нужно проявлять осторожность, чтобы не пропустить элементы или добавить дополнительные).

Вот пример, который я только что создал в своем текущем проекте:

class PrivEdit(ModelForm):
    def __init__(self, *args, **kw):
        super(ModelForm, self).__init__(*args, **kw)
        self.fields.keyOrder = [
            'super_user',
            'all_districts',
            'multi_district',
            'all_schools',
            'manage_users',
            'direct_login',
            'student_detail',
            'license']
    class Meta:
        model = Privilege
Holdenweb
источник
20
В течение некоторого времени поля будут упорядочены, как указано в атрибуте fields в ModelForm. Из docs.djangoproject.com/en/1.3/topics/forms/modelforms/… : Порядок, в котором имена полей указаны в этом списке, соблюдается при их отображении в форме. Это работает только для ModelForms - ваш подход по-прежнему весьма полезен для обычных форм, особенно в подклассах форм, которые добавляют дополнительные поля.
Крис Лоулор
3
Это работает, когда вы делаете забавные вещи, переопределяя одни поля, но не другие. +1
Натан Келлер
«Это» предложение Криса Лолора? Этот ответ достаточно старый, поэтому он, вероятно, уже устарел (но, вероятно, годился для версии 1.2). Важно убедиться, что ненадежная информация помечается, иначе новички не будут знать, чему доверять. Спасибо за ваш вклад.
holdenweb
72

Новым в Django 1.9 являются Form.field_order и Form.order_fields () .

# forms.Form example
class SignupForm(forms.Form):

    password = ...
    email = ...
    username = ...

    field_order = ['username', 'email', 'password']


# forms.ModelForm example
class UserAccount(forms.ModelForm):

    custom_field = models.CharField(max_length=254)

    def Meta:
        model = User
        fields = ('username', 'email')

    field_order = ['username', 'custom_field', 'password']
Стив Тхоа
источник
1
Это ответ, который я ищу!
sivabudh
1
начиная с версии 1.9 это должно быть ответом, на который должны обратить внимание новые поисковики, очевидно, все еще работает в 1.10
Брайан Х.
2
Примечание: это не "fields_order", а "field_order" (без 's')
Дэви
1
Вы даже можете указать только часть полей формы, которые хотите упорядочить
danleyb2
40

Я пошел дальше и ответил на свой вопрос. Вот ответ для использования в будущем:

В Django form.pyтворится темная магия, используя __new__метод для загрузки переменных вашего класса в конечном итоге в self.fieldsпорядке, определенном в классе. self.fieldsявляется SortedDictэкземпляром Django (определенным в datastructures.py).

Итак, чтобы переопределить это, скажем, в моем примере вы хотите, чтобы отправитель был первым, но вам нужно добавить его в метод инициализации , вы должны сделать:

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    def __init__(self,*args,**kwargs):
        forms.Form.__init__(self,*args,**kwargs)
        #first argument, index is the position of the field you want it to come before
        self.fields.insert(0,'sender',forms.EmailField(initial=str(time.time())))
Грег
источник
12
Вы можете определить поле «отправитель» как обычно вверху, а затем использовать вариант в строке вставки:self.fields.insert( 0, 'sender', self.fields['sender'] )
EvdB,
4
Это больше не работает в Django 1.7, потому что поля формы используют OrderedDict, который не поддерживает добавление. См. Мой обновленный ответ ниже (слишком длинный для комментариев) ...
Пол Кенджора
11

Поля перечислены в том порядке, в котором они определены в ModelClass._meta.fields. Но если вы хотите изменить порядок в форме, вы можете сделать это с помощью функции keyOrder. Например :

class ContestForm(ModelForm):
  class Meta:
    model = Contest
    exclude=('create_date', 'company')

  def __init__(self, *args, **kwargs):
    super(ContestForm, self).__init__(*args, **kwargs)
    self.fields.keyOrder = [
        'name',
        'description',
        'image',
        'video_link',
        'category']
Хью Сондерс
источник
6

С Django> = 1.7 вы должны изменить, ContactForm.base_fieldsкак показано ниже:

from collections import OrderedDict

...

class ContactForm(forms.Form):
    ...

ContactForm.base_fields = OrderedDict(
    (k, ContactForm.base_fields[k])
    for k in ['your', 'field', 'in', 'order']
)

Этот трюк используется в Django Admin PasswordChangeForm: Source на Github

Зулусский
источник
3
Забавно, я искал порядок полей, когда пытался выяснить, почему подклассы PasswordChangeFormизменили порядок полей, поэтому ваш пример оказался для меня очень полезным. Похоже, это потому, base_fieldsчто не переносится в подкласс. Решение, кажется, говорит PasswordChangeFormSubclass.base_fields = PasswordChangeForm.base_fieldsпосле определения `PasswordChangeFormSubclass
orblivion
@orblivion: используйте ContactForm.base_fields[k]при построении упорядоченного dict. В ответе опечатка, отредактирую
blueFast
5

Поля формы имеют атрибут порядка создания, называемый creation_counter. .fieldsattribute - это словарь, поэтому достаточно простого добавления в словарь и изменения creation_counterатрибутов во всех полях, чтобы отразить новый порядок (хотя я никогда не пробовал этого).

zgoda
источник
5

Используйте счетчик в классе Field. Сортировать по этому счетчику:

import operator
import itertools

class Field(object):
    _counter = itertools.count()
    def __init__(self):
        self.count = Field._counter.next()
        self.name = ''
    def __repr__(self):
        return "Field(%r)" % self.name

class MyForm(object):
    b = Field()
    a = Field()
    c = Field()

    def __init__(self):
        self.fields = []
        for field_name in dir(self):
            field = getattr(self, field_name)
            if isinstance(field, Field):
                field.name = field_name
                self.fields.append(field)
        self.fields.sort(key=operator.attrgetter('count'))

m = MyForm()
print m.fields # in defined order

Выход:

[Field('b'), Field('a'), Field('c')]

носкло
источник
4

В формах Django 1.7 используется OrderedDict, который не поддерживает оператор добавления. Значит, вам придется перестраивать словарь с нуля ...

class ChecklistForm(forms.ModelForm):

  class Meta:
    model = Checklist
    fields = ['name', 'email', 'website']

  def __init__(self, guide, *args, **kwargs):
    self.guide = guide
    super(ChecklistForm, self).__init__(*args, **kwargs)

    new_fields = OrderedDict()
    for tier, tasks in guide.tiers().items():
      questions = [(t['task'], t['question']) for t in tasks if 'question' in t]
      new_fields[tier.lower()] = forms.MultipleChoiceField(
        label=tier,
        widget=forms.CheckboxSelectMultiple(),
        choices=questions,
        help_text='desired set of site features'
      )

    new_fields['name'] = self.fields['name']
    new_fields['email'] = self.fields['email']
    new_fields['website'] = self.fields['website']
    self.fields = new_fields 
Пол Кенджора
источник
Начиная с python 3.2 вы можете использовать OrderedDict.move_to_end () для добавления или добавления элементов.
bparker
3

Для справки в будущем: все немного изменилось с момента появления новых форм. Это один из способов переупорядочения полей из базовых классов форм, которые вы не контролируете:

def move_field_before(form, field, before_field):
    content = form.base_fields[field]
    del(form.base_fields[field])
    insert_at = list(form.base_fields).index(before_field)
    form.base_fields.insert(insert_at, field, content)
    return form

Кроме того, здесь есть небольшая документация по SortedDict base_fields: http://code.djangoproject.com/wiki/SortedDict

Стейн Дебрувер
источник
Использование «полей» в моем классе Meta работало для меня (с ModelForm), пока у меня не возникла причина установить начальное значение для поля внешнего ключа с помощью MyFormSet.form.base_fields, после чего порядок в «полях» не был больше уважают. Мое решение состояло в том, чтобы просто переупорядочить поля в базовой модели.
Пол Дж.
2

Если либо fields = '__all__':

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = '__all__'

или excludeиспользуются:

class PartialAuthorForm(ModelForm):
    class Meta:
        model = Author
        exclude = ['title']

Затем Django ссылается на порядок полей, определенный в модели . Это просто зацепило меня, поэтому я подумал, что упомяну об этом. Он упоминается в документации ModelForm :

Если используется любой из них, порядок отображения полей в форме будет соответствовать порядку, в котором поля определены в модели, причем экземпляры ManyToManyField появляются последними.

Пол Дж
источник
2

Самый простой способ упорядочить поля в формах django 1.9 - использовать field_orderв вашей форме Form.field_order

Вот небольшой пример

class ContactForm(forms.Form):
     subject = forms.CharField(max_length=100)
     message = forms.CharField()
     sender = forms.EmailField()
     field_order = ['sender','message','subject']

Это покажет все в том порядке, который вы указали в field_orderdict.

Тахир Фазал
источник
1

Использование fieldsво внутреннем Metaклассе - это то, что у меня сработало Django==1.6.5:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Example form declaration with custom field order.
"""

from django import forms

from app.models import AppModel


class ExampleModelForm(forms.ModelForm):
    """
    An example model form for ``AppModel``.
    """
    field1 = forms.CharField()
    field2 = forms.CharField()

    class Meta:
        model = AppModel
        fields = ['field2', 'field1']

Так просто, как, что.

ariel17
источник
1
похоже, что этот метод действителен только для modelform, нормальная форма django.formsне работает
Pengfei.X
1

Я использовал это для перемещения полей:

def move_field_before(frm, field_name, before_name):
    fld = frm.fields.pop(field_name)
    pos = frm.fields.keys().index(before_name)
    frm.fields.insert(pos, field_name, fld)

Это работает в 1.5, и я вполне уверен, что это все еще работает в более поздних версиях.

Ян Рольф
источник
0

Это связано с мета-классом, который используется при определении класса формы. Я думаю, что он хранит внутренний список полей, и если вы вставите его в середину списка, он может сработать. Я давно не смотрел этот код.

Сэм Кордер
источник