Como o Django conhece a ordem para renderizar os campos do formulário?

89

Se eu tiver um formulário Django, como:

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

E eu chamo o método as_table () de uma instância deste formulário, o Django irá renderizar os campos na mesma ordem especificada acima.

Minha pergunta é como o Django sabe a ordem em que as variáveis ​​de classe foram definidas?

(Além disso, como faço para substituir essa ordem, por exemplo, quando quero adicionar um campo do método init da classe ?)

Greg
fonte

Respostas:

89

[NOTA: esta resposta agora está completamente desatualizada - consulte a discussão abaixo e as respostas mais recentes].

Se ffor um formulário, seus campos são f.fields, que é um django.utils.datastructures.SortedDict (apresenta os itens na ordem em que são adicionados). Após a construção do formulário f.fields possui um atributo keyOrder, que é uma lista contendo os nomes dos campos na ordem em que devem ser apresentados. Você pode definir a ordem correta (embora seja necessário ter cuidado para não omitir itens ou adicionar extras).

Aqui está um exemplo que acabei de criar em meu projeto atual:

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
fonte
19
Há algum tempo, os campos serão ordenados conforme especificado pelo atributo 'fields' de um ModelForm. De docs.djangoproject.com/en/1.3/topics/forms/modelforms/… : A ordem em que os nomes dos campos são especificados nessa lista é respeitada quando o formulário os renderiza. Isso funciona apenas para ModelForms - sua abordagem ainda é bastante útil para formulários regulares, especialmente em subclasses de formulário que adicionam campos adicionais.
Chris Lawlor
3
Isso funciona quando você está fazendo coisas funky, substituindo alguns campos, mas não outros. +1
Nathan Keller
"Esta" é a sugestão de Chris Lawlor? Essa resposta é antiga o suficiente para que provavelmente não seja mais atual (mas provavelmente era boa para 1.2). É importante garantir que informações não confiáveis ​​sejam sinalizadas, caso contrário, os novatos não saberão em que confiar. Obrigado pela sua contribuição.
holdenweb
67

As novidades no Django 1.9 são Form.field_order e 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
fonte
1
Esta é a resposta que procuro!
sivabudh
1
a partir de 1.9, essa deve ser a resposta que os novos pesquisadores devem olhar, obviamente ainda funciona em 1.10
Brian H.
2
Observação: não é "fields_order", mas "field_order" (no 's')
Davy,
1
Você pode até especificar apenas um subconjunto dos campos do formulário que deseja ordenar
danleyb2
40

Eu fui em frente e respondi minha própria pergunta. Aqui está a resposta para referência futura:

No Django form.pyfaz alguma magia negra usando o__new__ método para carregar as variáveis ​​de sua classe self.fieldsna ordem definida na classe. self.fieldsé uma SortedDictinstância do Django (definida em datastructures.py).

Então, para substituir isso, digamos no meu exemplo que você quisesse que o remetente viesse primeiro, mas precisava adicioná-lo em um método init , você faria:

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
fonte
12
Você pode definir o campo 'remetente' normalmente na parte superior e, em seguida, usar uma variação em sua linha de inserção:self.fields.insert( 0, 'sender', self.fields['sender'] )
EvdB
4
Isso não funciona mais no Django 1.7 porque os campos do formulário usam OrderedDict, que não suporta append. Veja minha resposta atualizada abaixo (muito longa para comentários) ...
Paul Kenjora
11

Os campos são listados na ordem em que são definidos em ModelClass._meta.fields. Mas se você quiser mudar a ordem no Form, você pode fazer usando a função keyOrder. Por exemplo :

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

Com Django> = 1.7, você deve modificar ContactForm.base_fieldsconforme abaixo:

from collections import OrderedDict

...

class ContactForm(forms.Form):
    ...

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

Este truque é usado no Django Admin PasswordChangeForm: Fonte no Github

zulu
fonte
3
Engraçado, eu procurei a ordem dos campos ao tentar descobrir por que a subclasse PasswordChangeFormmudou a ordem dos campos, então seu exemplo acabou sendo muito útil para mim. Parece que é porque base_fieldsnão é transportado para a subclasse. A solução parece estar dizendo PasswordChangeFormSubclass.base_fields = PasswordChangeForm.base_fieldsapós a definição de `PasswordChangeFormSubclass
orblivion
@orblivion: use ContactForm.base_fields[k]ao construir o dicionário ordenado. Há um erro de digitação na resposta, vou editar
blueFast
5

Os campos do formulário possuem um atributo para a ordem de criação, chamado creation_counter. .fieldsattribute é um dicionário, portanto, adicionar ao dicionário e alterar creation_counteratributos em todos os campos para refletir a nova ordem deve ser suficiente (nunca tentei isso, no entanto).

zgoda
fonte
5

Use um contador na classe Field. Classifique por esse contador:

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

Resultado:

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

nosklo
fonte
4

A partir do Django 1.7, os formulários usam OrderedDict, que não suporta o operador append. Então você tem que reconstruir o dicionário do zero ...

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
fonte
Desde o python 3.2 você pode usar OrderedDict.move_to_end () para preceder ou acrescentar itens.
bparker
3

Para referência futura: as coisas mudaram um pouco desde as novas formas. Esta é uma maneira de reordenar campos de classes de formulário básicas sobre as quais você não tem controle:

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

Além disso, há um pouco de documentação sobre SortedDict que pode ser base_fieldsusado aqui: http://code.djangoproject.com/wiki/SortedDict

Stijn Debrouwere
fonte
Usar 'campos' em minha classe Meta estava funcionando para mim (com um ModelForm) até que eu tive um motivo para definir um valor inicial para um campo de chave estrangeira usando MyFormSet.form.base_fields, ponto em que a ordem em 'campos' era não mais respeitado. Minha solução foi apenas reordenar os campos no modelo subjacente.
Paul J de
2

Se fields = '__all__':

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

ou excludesão usados:

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

Então o Django referencia a ordem dos campos conforme definido no modelo . Isso simplesmente me pegou, então pensei em mencionar isso. É referenciado na documentação ModelForm :

Se qualquer um deles for usado, a ordem em que os campos aparecem no formulário será a ordem em que os campos são definidos no modelo, com as instâncias de ManyToManyField aparecendo por último.

Paul J
fonte
2

A maneira mais fácil de solicitar campos nos formulários django 1.9 é usar field_orderem seu formulário Form.field_order

Aqui está um pequeno exemplo

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

Isso mostrará tudo na ordem especificada em field_orderdict.

Tahir Fazal
fonte
1

Usar fieldsna Metaaula interna é o que funcionou para mim em 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']

Tão simples como isso.

ariel17
fonte
1
parece que este método só é válido para modelform, a forma normal django.formsnão funciona
Pengfei.X
1

Eu usei isso para mover campos sobre:

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)

Isso funciona no 1.5 e estou razoavelmente certo de que ainda funciona nas versões mais recentes.

Ian Rolfe
fonte
0

Tem a ver com a meta classe que é usada na definição da classe do formulário. Acho que mantém uma lista interna dos campos e se você inserir no meio da lista pode funcionar. Já faz um tempo desde que eu olhei esse código.

Sam Corder
fonte