Звідки Джанго знає порядок візуалізації полів форми?


91

Якщо у мене є форма Django, така як:

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

І я називаю метод as_table () екземпляра цієї форми, Django відображатиме поля у тому самому порядку, як зазначено вище.

Моє питання полягає в тому, звідки Django знає порядок змінних класу, де вони визначені?

(Також як я можу замінити цей порядок, наприклад, коли я хочу додати поле з ініціалізації classe методу ?)

Відповіді:


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

20
Деякий час поля будуть упорядковані відповідно до атрибута 'fields' ModelForm. З docs.djangoproject.com/en/1.3/topics/forms/modelforms/… : Порядок, у якому імена полів вказані в цьому списку, дотримується, коли форма їх відображає. Це працює лише для ModelForms - ваш підхід все ще досить корисний для звичайних форм, особливо у підкласах форм, які додають додаткові поля.
Chris Lawlor

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

Отже, щоб замінити це, скажімо в моєму прикладі, що ви хотіли, щоб відправник був першим, але вам потрібно було додати його методом 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())))

12
Ви можете визначити поле "відправник" як звичайне у верхній частині, а потім використати варіацію на self.fields.insert( 0, 'sender', self.fields['sender'] )
вставному

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]під час побудови замовленого дикту. У відповіді є помилка, я відредагую
blueFast

5

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


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, і в цей момент порядок у 'полях' був не довше поважали. Моє рішення насправді полягало лише в тому, щоб перевпорядкувати поля в базовій моделі.
Paul J,

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_orderдикті.


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

Так просто.


1
здається, цей метод дійсний лише для моделі, нормальна форма 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

Це пов’язано з мета-класом, який використовується для визначення класу форми. Я думаю, що він зберігає внутрішній список полів, і якщо ви вставите в середину списку, це може спрацювати. Минув деякий час з того часу, як я подивився цей код.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.