Comment Django connaît-il l'ordre de rendu des champs de formulaire?

90

Si j'ai un formulaire Django tel que:

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

Et j'appelle la méthode as_table () d'une instance de ce formulaire, Django rendra les champs dans le même ordre que spécifié ci-dessus.

Ma question est de savoir comment Django connaît l'ordre dans lequel les variables de classe ont été définies?

(Comment puis-je remplacer cet ordre, par exemple lorsque je veux ajouter un champ à partir de la méthode init de la classe ?)

Greg
la source

Réponses:

89

[REMARQUE: cette réponse est maintenant complètement obsolète - veuillez consulter la discussion ci-dessous et les réponses plus récentes].

Si fest un formulaire, ses champs sont f.fields, qui est un django.utils.datastructures.SortedDict (il présente les éléments dans l'ordre où ils sont ajoutés). Après la construction du formulaire, f.fields a un attribut keyOrder, qui est une liste contenant les noms de champs dans l'ordre dans lequel ils doivent être présentés. Vous pouvez le définir dans le bon ordre (bien que vous deviez faire attention pour vous assurer de ne pas omettre d'articles ou d'ajouter des extras).

Voici un exemple que je viens de créer dans mon projet actuel:

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
la source
19
Depuis un certain temps maintenant, les champs seront classés comme spécifié par l'attribut 'fields' d'un ModelForm. De docs.djangoproject.com/en/1.3/topics/forms/modelforms / ... : L'ordre dans lequel les noms de champs sont spécifiés dans cette liste est respecté lorsque le formulaire les rend. Cela ne fonctionne que pour ModelForms - votre approche est toujours très utile pour les formulaires normaux, en particulier dans les sous-classes de formulaires qui ajoutent des champs supplémentaires.
Chris Lawlor
3
Cela fonctionne lorsque vous faites des trucs géniaux remplaçant certains champs mais pas d'autres. +1
Nathan Keller
"C'est" la suggestion de Chris Lawlor? Cette réponse est suffisamment ancienne pour qu'elle ne soit probablement plus actuelle (mais était probablement bonne pour la version 1.2). Il est important de s'assurer que les informations non fiables sont signalées, sinon les nouveaux arrivants ne savent pas à quoi faire confiance. Merci pour votre contribution.
holdenweb
67

Les nouveautés de Django 1.9 sont Form.field_order et 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
la source
1
C'est la réponse que je recherche!
sivabudh le
1
à partir de la version 1.9, cela devrait être la réponse que les nouveaux chercheurs devraient regarder, cela fonctionne évidemment toujours en 1.10
Brian H.
2
Remarque: ce n'est pas "fields_order" mais "field_order" (pas de 's')
Davy
1
Vous pouvez même spécifier juste un sous-ensemble des champs de formulaire que vous souhaitez commander
danleyb2
40

Je suis allé de l'avant et j'ai répondu à ma propre question. Voici la réponse pour référence future:

Dans Django form.pyfait de la magie noire en utilisant la __new__méthode pour charger finalement vos variables de classe self.fieldsdans l'ordre défini dans la classe. self.fieldsest une SortedDictinstance Django (définie dans datastructures.py).

Donc, pour remplacer cela, disons dans mon exemple que vous vouliez que l'expéditeur vienne en premier mais que vous deviez l'ajouter dans une méthode init , vous feriez:

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
la source
12
Vous pouvez définir le champ 'expéditeur' comme normal en haut, puis utiliser une variation sur votre ligne d'insertion:self.fields.insert( 0, 'sender', self.fields['sender'] )
EvdB
4
Cela ne fonctionne plus dans Django 1.7 car les champs de formulaire utilisent OrderedDict qui ne prend pas en charge l'ajout. Voir ma réponse mise à jour ci-dessous (trop longue pour un commentaire) ...
Paul Kenjora
11

Les champs sont répertoriés dans l'ordre dans lequel ils sont définis dans ModelClass._meta.fields. Mais si vous souhaitez modifier l'ordre dans Form, vous pouvez le faire en utilisant la fonction keyOrder. Par exemple :

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
la source
6

Avec Django> = 1.7, vous devez modifier ContactForm.base_fieldscomme ci-dessous:

from collections import OrderedDict

...

class ContactForm(forms.Form):
    ...

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

Cette astuce est utilisée dans Django Admin PasswordChangeForm: Source sur Github

zoulou
la source
3
C'est drôle, j'ai cherché l'ordre des champs en essayant de comprendre pourquoi le sous-classement a PasswordChangeFormchangé l'ordre des champs, donc votre exemple s'est avéré être très utile pour moi. Il semble que ce soit parce qu'il base_fieldsn'est pas transféré à la sous-classe. La solution semble être de dire PasswordChangeFormSubclass.base_fields = PasswordChangeForm.base_fieldsaprès la définition de `PasswordChangeFormSubclass
orblivion
@orblivion: utilisation ContactForm.base_fields[k]lors de la construction du dict commandé. Il y a une faute de frappe dans la réponse, je vais modifier
blueFast
5

Les champs de formulaire ont un attribut pour l'ordre de création, appelé creation_counter. .fieldsL'attribut est un dictionnaire, donc un simple ajout au dictionnaire et la modification des creation_counterattributs dans tous les champs pour refléter le nouvel ordre devrait suffire (jamais essayé cela, cependant).

zgoda
la source
5

Utilisez un compteur dans la classe Field. Trier par ce compteur:

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

Production:

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

nosklo
la source
4

Depuis les formulaires Django 1.7, utilisez OrderedDict qui ne prend pas en charge l'opérateur d'ajout. Il faut donc reconstruire le dictionnaire à partir de zéro ...

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
la source
Depuis python 3.2, vous pouvez utiliser OrderedDict.move_to_end () pour ajouter ou ajouter des éléments.
bparker
3

Pour référence future: les choses ont un peu changé depuis les nouvelles formes. C'est une façon de réorganiser les champs des classes de formulaire de base sur lesquelles vous n'avez aucun contrôle:

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

De plus, il y a un peu de documentation sur le SortedDict qui base_fieldsutilise ici: http://code.djangoproject.com/wiki/SortedDict

Stijn Debrouwere
la source
L'utilisation de 'fields' dans ma classe Meta fonctionnait pour moi (avec un ModelForm) jusqu'à ce que j'aie une raison de définir une valeur initiale pour un champ de clé étrangère en utilisant MyFormSet.form.base_fields, à quel point l'ordre dans 'fields' n'était pas plus respecté. Ma solution consistait simplement à réorganiser les champs dans le modèle sous-jacent.
Paul J
2

Si l'un ou l'autre fields = '__all__':

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

ou excludesont utilisés:

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

Django référence ensuite l'ordre des champs tel que défini dans le modèle . Cela m'a juste surpris, alors j'ai pensé le mentionner. Il est référencé dans la documentation ModelForm :

Si l'un de ces éléments est utilisé, l'ordre dans lequel les champs apparaissent dans le formulaire sera l'ordre dans lequel les champs sont définis dans le modèle, les instances ManyToManyField apparaissant en dernier.

Paul J
la source
2

La manière la plus simple de classer les champs dans les formulaires django 1.9 est d'utiliser field_orderdans votre formulaire Form.field_order

Voici un petit exemple

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

Cela affichera tout dans l'ordre que vous avez spécifié dans field_orderdict.

Tahir Fazal
la source
1

L'utilisation fieldsen Metaclasse interne est ce qui a fonctionné pour moi 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']

Aussi simple que cela.

ariel17
la source
1
semble que cette méthode n'est valide que pour modelform, la forme normale django.formsne fonctionne pas
Pengfei.X
1

J'ai utilisé ceci pour déplacer des champs sur:

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)

Cela fonctionne dans la version 1.5 et je suis raisonnablement sûr que cela fonctionne toujours dans les versions plus récentes.

Ian Rolfe
la source
0

Cela a à voir avec la classe méta utilisée pour définir la classe de formulaire. Je pense qu'il garde une liste interne des champs et si vous insérez au milieu de la liste, cela pourrait fonctionner. Cela fait un moment que j'ai regardé ce code.

Sam Corder
la source