Woher kennt Django die Reihenfolge zum Rendern von Formularfeldern?

89

Wenn ich ein Django-Formular habe wie:

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

Und ich rufe die as_table () -Methode einer Instanz dieses Formulars auf. Django rendert die Felder in derselben Reihenfolge wie oben angegeben.

Meine Frage ist, woher weiß Django die Reihenfolge, in der Klassenvariablen definiert wurden?

(Wie überschreibe ich diese Reihenfolge, wenn ich beispielsweise ein Feld aus der Init- Methode der Klasse hinzufügen möchte ?)

Greg
quelle

Antworten:

89

[HINWEIS: Diese Antwort ist jetzt ziemlich veraltet - siehe die Diskussion darunter und neuere Antworten].

Wenn fes sich um ein Formular handelt, sind seine Felder f.fieldsa django.utils.datastructures.SortedDict (es zeigt die Elemente in der Reihenfolge an, in der sie hinzugefügt wurden). Nach der Formularerstellung verfügt f.fields über ein keyOrder-Attribut. Hierbei handelt es sich um eine Liste, die die Feldnamen in der Reihenfolge enthält, in der sie angezeigt werden sollen. Sie können dies auf die richtige Reihenfolge einstellen (obwohl Sie vorsichtig sein müssen, um sicherzustellen, dass Sie keine Artikel auslassen oder Extras hinzufügen).

Hier ist ein Beispiel, das ich gerade in meinem aktuellen Projekt erstellt habe:

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
quelle
19
Seit einiger Zeit werden die Felder gemäß dem Attribut 'fields' einer ModelForm sortiert. Aus docs.djangoproject.com/de/1.3/topics/forms/modelforms/… : Die Reihenfolge, in der die Feldnamen in dieser Liste angegeben werden, wird beim Rendern des Formulars berücksichtigt. Dies funktioniert nur für ModelForms - Ihr Ansatz ist für reguläre Formulare immer noch sehr nützlich, insbesondere in Formularunterklassen, die zusätzliche Felder hinzufügen.
Chris Lawlor
3
Dies funktioniert, wenn Sie funky Dinge tun, die einige Felder überschreiben, andere jedoch nicht. +1
Nathan Keller
"Dies" ist Chris Lawlors Vorschlag? Diese Antwort ist alt genug, dass sie wahrscheinlich nicht mehr aktuell ist (aber wahrscheinlich gut für 1.2 war). Es ist wichtig sicherzustellen, dass unzuverlässige Informationen gekennzeichnet werden, da Neulinge sonst nicht wissen, was sie vertrauen sollen. Danke für deinen Beitrag.
Holdenweb
64

Neu in Django 1.9 sind Form.field_order und 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
quelle
1
Das ist die Antwort, die ich suche!
Sivabudh
1
Ab 1.9 sollte dies die Antwort sein, die sich neue Suchende ansehen sollten. Offensichtlich funktioniert dies immer noch in 1.10
Brian H.
2
Hinweis: Es ist nicht "fields_order", sondern "field_order" (no 's')
Davy
Sie können sogar nur eine Teilmenge der Formularfelder angeben, die Sie bestellen möchten
danleyb2
40

Ich ging voran und beantwortete meine eigene Frage. Hier ist die Antwort zum späteren Nachschlagen:

In Django form.pywird dunkle Magie mit der __new__Methode ausgeführt, um Ihre Klassenvariablen letztendlich in self.fieldsder in der Klasse definierten Reihenfolge zu laden . self.fieldsist eine Django- SortedDictInstanz (definiert in datastructures.py).

Um dies zu überschreiben, sagen Sie in meinem Beispiel, dass der Absender an erster Stelle stehen soll, aber in einer Init- Methode hinzugefügt werden muss , würden Sie Folgendes tun:

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
quelle
12
Sie können das Feld 'Absender' oben wie self.fields.insert( 0, 'sender', self.fields['sender'] )
gewohnt
4
Dies funktioniert in Django 1.7 nicht mehr, da Formularfelder OrderedDict verwenden, das das Anhängen nicht unterstützt. Siehe meine aktualisierte Antwort unten (zu lang für einen Kommentar) ...
Paul Kenjora
11

Felder werden in der Reihenfolge aufgelistet, in der sie in ModelClass._meta.fields definiert sind. Wenn Sie jedoch die Reihenfolge in Form ändern möchten, können Sie dies mit der Funktion keyOrder tun. Zum Beispiel :

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
quelle
6

Mit Django> = 1.7 müssen Sie ContactForm.base_fieldsFolgendes ändern :

from collections import OrderedDict

...

class ContactForm(forms.Form):
    ...

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

Dieser Trick wird in Django Admin PasswordChangeForm: Source auf Github verwendet

Zulu-
quelle
3
Komisch, ich habe nach der Reihenfolge der Felder gesucht, als ich herausgefunden habe, warum die Unterklassen PasswordChangeFormdie Reihenfolge der Felder geändert haben. Ihr Beispiel war also sehr nützlich für mich. Es scheint, dass es daran base_fieldsliegt, dass es nicht in die Unterklasse übertragen wird. Die Lösung scheint PasswordChangeFormSubclass.base_fields = PasswordChangeForm.base_fieldsnach der Definition von `PasswordChangeFormSubclass
orblivion
@orblivion: Verwenden Sie diese Option, ContactForm.base_fields[k]wenn Sie das bestellte Diktat erstellen . Es gibt einen Tippfehler in der Antwort, ich werde bearbeiten
blueFast
5

Formularfelder haben ein Attribut für die Erstellungsreihenfolge, das aufgerufen wird creation_counter. .fieldsAttribut ist ein Wörterbuch, daher creation_countersollte es ausreichen , das Wörterbuch einfach hinzuzufügen und Attribute in allen Feldern zu ändern , um die neue Reihenfolge widerzuspiegeln (dies wurde jedoch nie versucht).

zgoda
quelle
5

Verwenden Sie einen Zähler in der Field-Klasse. Sortieren nach diesem Zähler:

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

Ausgabe:

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

nosklo
quelle
4

Ab Django 1.7-Formularen wird OrderedDict verwendet, das den Append-Operator nicht unterstützt. Sie müssen das Wörterbuch also von Grund auf neu erstellen ...

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
quelle
Seit Python 3.2 können Sie mit OrderedDict.move_to_end () Elemente voranstellen oder anhängen.
bparker
3

Zum späteren Nachschlagen: Die Dinge haben sich seit den neuen Formen ein wenig geändert. Dies ist eine Möglichkeit, Felder aus Basisformelklassen neu zu ordnen, über die Sie keine Kontrolle haben:

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

Außerdem gibt es eine kleine Dokumentation zu SortedDict, base_fieldsdie hier verwendet wird: http://code.djangoproject.com/wiki/SortedDict

Stijn Debrouwere
quelle
Die Verwendung von 'Feldern' in meiner Klasse Meta funktionierte für mich (mit einem ModelForm), bis ich einen Grund hatte, einen Anfangswert für ein Fremdschlüsselfeld mit MyFormSet.form.base_fields festzulegen. Zu diesem Zeitpunkt war die Reihenfolge in 'Felder' Nein länger respektiert. Meine Lösung bestand eigentlich nur darin, die Felder im zugrunde liegenden Modell neu zu ordnen.
Paul J
2

Wenn entweder fields = '__all__':

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

oder excludeverwendet werden:

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

Dann verweist Django auf die im Modell definierte Reihenfolge der Felder . Das hat mich nur erwischt, also dachte ich, ich würde es erwähnen. In den ModelForm-Dokumenten wird darauf verwiesen :

Wenn eine dieser Optionen verwendet wird, entspricht die Reihenfolge, in der die Felder im Formular angezeigt werden, der Reihenfolge, in der die Felder im Modell definiert sind, wobei die ManyToManyField-Instanzen zuletzt angezeigt werden.

Paul J.
quelle
2

Der einfachste Weg, Felder in Django 1.9-Formularen zu bestellen, ist die Verwendung field_orderin Ihrem Formular Form.field_order

Hier ist ein kleines Beispiel

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

Dies zeigt alles in der Reihenfolge an, die Sie in field_orderdict angegeben haben.

Tahir Fazal
quelle
1

Die Verwendung fieldsin der inneren MetaKlasse hat für mich funktioniert 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']

So einfach ist das.

ariel17
quelle
1
Diese Methode scheint nur für die Modellform gültig zu sein. Die normale Form django.formsfunktioniert nicht
Pengfei.X
0

Dies hat mit der Metaklasse zu tun, die beim Definieren der Formularklasse verwendet wird. Ich denke, es führt eine interne Liste der Felder und wenn Sie in die Mitte der Liste einfügen, könnte es funktionieren. Es ist eine Weile her, seit ich mir diesen Code angesehen habe.

Sam Corder
quelle
0

Ich habe dies verwendet, um Felder zu verschieben über:

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)

Dies funktioniert in Version 1.5 und ich bin mir ziemlich sicher, dass es auch in neueren Versionen funktioniert.

Ian Rolfe
quelle