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

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

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

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

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

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

Ответ 1

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

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

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

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

Ответ 2

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

Если 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

Ответ 3

Новым в 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']

Ответ 4

Поля указаны в том порядке, в котором они определены в 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']

Ответ 5

С 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: Источник на Github

Ответ 6

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

Ответ 7

Используйте счетчик в классе 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')]

Ответ 8

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

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 

Ответ 9

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

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

Ответ 10

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

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

или exclude:

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

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

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

Ответ 11

Самый простой способ упорядочить поля в формах 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_order dict.

Ответ 12

Использование 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']

Проще всего.

Ответ 13

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

Ответ 14

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

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, и я уверен, что он по-прежнему работает в более поздних версиях.