Skąd Django poznaje kolejność renderowania pól formularza?

91

Jeśli mam formularz Django taki jak:

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

Wywołuję metodę as_table () instancji tego formularza, Django wyrenderuje pola w takiej samej kolejności, jak określono powyżej.

Moje pytanie brzmi: skąd Django zna kolejność definiowania zmiennych klas?

(Również jak zmienić tę kolejność, na przykład, gdy chcę dodać pole z metody init klasy ?)

Greg
źródło

Odpowiedzi:

89

[UWAGA: ta odpowiedź jest teraz całkiem nieaktualna - zobacz dyskusję poniżej i nowsze odpowiedzi].

Jeśli fjest to formularz, jego pola to f.fields, czyli a django.utils.datastructures.SortedDict (przedstawia elementy w kolejności ich dodawania). Po skonstruowaniu formularza f.fields posiada atrybut keyOrder, który jest listą zawierającą nazwy pól w kolejności, w jakiej powinny być prezentowane. Możesz ustawić to na właściwą kolejność (chociaż musisz uważać, aby nie pomijać elementów ani nie dodawać dodatków).

Oto przykład, który właśnie utworzyłem w moim obecnym projekcie:

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
źródło
20
Od jakiegoś czasu pola będą uporządkowane zgodnie z atrybutem „fields” w ModelForm. Z docs.djangoproject.com/en/1.3/topics/forms/modelforms/ ... : Kolejność, w jakiej nazwy pól są określone na tej liście, jest przestrzegana podczas renderowania ich w formularzu. Działa to tylko w przypadku ModelForms - Twoje podejście jest nadal bardzo przydatne w przypadku zwykłych formularzy, zwłaszcza w podklasach formularzy, które dodają dodatkowe pola.
Chris Lawlor
3
To działa, gdy robisz funky, nadpisując niektóre pola, ale nie inne. +1
Nathan Keller
„To” jest sugestią Chrisa Lawlora? Ta odpowiedź jest na tyle stara, że ​​prawdopodobnie nie jest już aktualna (ale prawdopodobnie była dobra dla wersji 1.2). Ważne jest, aby upewnić się, że niewiarygodne informacje zostaną oznaczone, w przeciwnym razie nowoprzybyli nie wiedzą, czemu ufać. Dzięki za wkład.
holdenweb
72

Nowością w Django 1.9 są Form.field_order i 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']
Steve Tjoa
źródło
1
To jest odpowiedź, której szukam!
sivabudh
1
od 1.9 powinna to być odpowiedź, na którą powinni spojrzeć nowi poszukiwacze, oczywiście nadal działa w 1.10
Brian H.
2
Uwaga: to nie jest „fields_order”, ale „field_order” (no 's')
Davy
1
Możesz nawet określić tylko podzbiór pól formularza, które chcesz zamówić
danleyb2
40

Poszedłem dalej i odpowiedziałem na własne pytanie. Oto odpowiedź na przyszłość:

W Django form.pyrobi trochę mrocznej magii, używając __new__metody do załadowania zmiennych klasowych ostatecznie self.fieldsw kolejności zdefiniowanej w klasie. self.fieldsjest SortedDictinstancją Django (zdefiniowaną w datastructures.py).

Aby to zmienić, powiedzmy w moim przykładzie, że chcesz, aby nadawca był pierwszy, ale musisz go dodać w metodzie init , zrobiłbyś:

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())))
Greg
źródło
12
Możesz zdefiniować pole „nadawca” normalnie u góry, a następnie użyć odmiany w wierszu wstawiania:self.fields.insert( 0, 'sender', self.fields['sender'] )
EvdB
4
To już nie działa w Django 1.7, ponieważ pola formularzy używają OrderedDict, która nie obsługuje dołączania. Zobacz moją zaktualizowaną odpowiedź poniżej (zbyt długa na komentarz) ...
Paul Kenjora
11

Pola są wymienione w kolejności, w jakiej są zdefiniowane w ModelClass._meta.fields. Ale jeśli chcesz zmienić kolejność w formularzu, możesz to zrobić za pomocą funkcji keyOrder. Na przykład :

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']
Hugh Saunders
źródło
6

W Django> = 1.7 musisz zmodyfikować ContactForm.base_fieldsjak poniżej:

from collections import OrderedDict

...

class ContactForm(forms.Form):
    ...

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

Ta sztuczka jest używana w Django Admin PasswordChangeForm: Source na Github

Zulus
źródło
3
Zabawne, szukałem kolejności pól, próbując dowiedzieć się, dlaczego podklasy PasswordChangeFormzmieniają kolejność pól, więc twój przykład był dla mnie bardzo przydatny. Wygląda na to, że base_fieldsdzieje się tak, ponieważ nie jest przenoszony do podklasy. Rozwiązanie wydaje się mówić PasswordChangeFormSubclass.base_fields = PasswordChangeForm.base_fieldspo definicji `` PasswordChangeFormSubclass
orblivion
@orblivion: użyj ContactForm.base_fields[k]podczas budowania zamówionego dyktu. W odpowiedzi jest literówka,
edytuję
5

Pola formularza mają atrybut kolejności tworzenia o nazwie creation_counter. .fieldsatrybut jest słownikiem, więc proste dodanie do słownika i zmiana creation_counteratrybutów we wszystkich polach w celu odzwierciedlenia nowej kolejności powinno wystarczyć (nigdy tego nie próbowałem).

zgoda
źródło
5

Użyj licznika w klasie Field. Sortuj według tego licznika:

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

Wynik:

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

nosklo
źródło
4

Od wersji Django 1.7 formularze używają OrderedDict, która nie obsługuje operatora dołączania. Musisz więc odbudować słownik od podstaw ...

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 
Paul Kenjora
źródło
Od pythona 3.2 możesz używać OrderedDict.move_to_end () do dołączania elementów na początku lub na końcu.
bparker
3

Na przyszłość: od czasu Newforms sytuacja nieco się zmieniła. Jest to jeden ze sposobów zmiany kolejności pól z podstawowych klas formularzy, nad którymi nie masz kontroli:

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

Istnieje również trochę dokumentacji dotyczącej SortedDict, która base_fieldsużywa tutaj: http://code.djangoproject.com/wiki/SortedDict

Stijn Debrouwere
źródło
Używanie „pól” w mojej klasie Meta działało dla mnie (z ModelForm), dopóki nie miałem powodu, aby ustawić wartość początkową dla pola klucza obcego za pomocą MyFormSet.form.base_fields, w którym to momencie kolejność w „polach” nie była już szanowany. Moje rozwiązanie polegało na zmianie kolejności pól w modelu bazowym.
Paul J
2

Jeśli fields = '__all__':

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

lub excludesą używane:

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

Następnie Django odwołuje się do kolejności pól zdefiniowanej w modelu . Właśnie to mnie zaskoczyło, więc pomyślałem, że o tym wspomnę. Jest przywoływany w dokumentacji ModelForm :

Jeśli zostanie użyty którykolwiek z nich, kolejność pojawiania się pól w formularzu będzie zgodna z kolejnością, w jakiej pola są zdefiniowane w modelu, przy czym instancje ManyToManyField pojawią się jako ostatnie.

Paul J.
źródło
2

Najłatwiejszym sposobem uporządkowania pól w formularzach django 1.9 jest użycie field_orderw formularzu Form.field_order

Oto mały przykład

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

Spowoduje to wyświetlenie wszystkiego w kolejności określonej w field_orderdict.

Tahir Fazal
źródło
1

Użycie fieldsw Metaklasie wewnętrznej zadziałało w przypadku mnie 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']

Tak proste jak to.

ariel17
źródło
1
wygląda na to, że ta metoda jest ważna tylko dla modelform, normalna forma django.formsnie działa
Pengfei.X
1

Użyłem tego do przenoszenia pól o:

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)

Działa to w wersji 1.5 i jestem pewien, że nadal działa w nowszych wersjach.

Ian Rolfe
źródło
0

Ma to związek z klasą meta używaną do definiowania klasy formularza. Myślę, że przechowuje wewnętrzną listę pól i jeśli wstawisz je na środku listy, może to zadziałać. Minęło trochę czasu, odkąd przyjrzałem się temu kodowi.

Sam Corder
źródło