¿Cómo sabe Django el orden para renderizar campos de formulario?

91

Si tengo un formulario de Django como:

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

Y llamo al método as_table () de una instancia de este formulario, Django representará los campos en el mismo orden especificado anteriormente.

Mi pregunta es ¿cómo sabe Django el orden en que se definieron las variables de clase?

(Además, ¿cómo anulo este orden, por ejemplo, cuando quiero agregar un campo desde el método init de la clase ?)

Greg
fuente

Respuestas:

89

[NOTA: esta respuesta ahora está bastante desactualizada; consulte la discusión a continuación y las respuestas más recientes].

Si fes un formulario, sus campos son f.fields, que es un django.utils.datastructures.SortedDict (presenta los elementos en el orden en que se agregan). Después de la construcción del formulario, f.fields tiene un atributo keyOrder, que es una lista que contiene los nombres de los campos en el orden en que deben presentarse. Puede configurarlo en el orden correcto (aunque debe tener cuidado para asegurarse de no omitir elementos ni agregar extras).

Aquí hay un ejemplo que acabo de crear en mi proyecto actual:

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
fuente
20
Desde hace algún tiempo, los campos se ordenarán según lo especificado por el atributo 'campos' de un ModelForm. De docs.djangoproject.com/en/1.3/topics/forms/modelforms/… : El orden en el que se especifican los nombres de los campos en esa lista se respeta cuando el formulario los representa. Esto solo funciona para ModelForms; su enfoque sigue siendo bastante útil para formularios regulares, especialmente en subclases de formularios que agregan campos adicionales.
Chris Lawlor
3
Esto funciona cuando estás haciendo cosas divertidas que anulan algunos campos pero no otros. +1
Nathan Keller
¿"Esta" es la sugerencia de Chris Lawlor? Esta respuesta es lo suficientemente antigua como para que probablemente ya no sea actual (pero probablemente sea buena para 1.2). Es importante asegurarse de que se marque la información no confiable, de lo contrario, los recién llegados no saben en qué confiar. Gracias por tu contribución.
holdenweb
72

Nuevo en Django 1.9 es Form.field_order y 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
fuente
1
¡Esta es la respuesta que estoy buscando!
sivabudh
1
a partir de 1.9, esta debería ser la respuesta que los nuevos buscadores deberían mirar, obviamente todavía funciona en 1.10
Brian H.
2
Nota: no es "fields_order" sino "field_order" (no 's')
Davy
1
Incluso puede especificar solo un subconjunto de los campos del formulario que desea ordenar
danleyb2
40

Seguí adelante y respondí mi propia pregunta. Aquí está la respuesta para referencia futura:

En Django se form.pyhace magia oscura usando el __new__método para cargar las variables de su clase en última instancia self.fieldsen el orden definido en la clase. self.fieldses una SortedDictinstancia de Django (definida en datastructures.py).

Entonces, para anular esto, digamos en mi ejemplo que deseaba que el remitente fuera primero pero necesitara agregarlo en un método de inicio , haría:

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
fuente
12
Puede definir el campo 'remitente' como normal en la parte superior y luego usar una variación en su línea de inserción:self.fields.insert( 0, 'sender', self.fields['sender'] )
EvdB
4
Esto ya no funciona en Django 1.7 porque los campos de formulario usan OrderedDict que no es compatible con append. Vea mi respuesta actualizada a continuación (demasiado larga para comentar) ...
Paul Kenjora
11

Los campos se enumeran en el orden en que se definen en ModelClass._meta.fields. Pero si desea cambiar el orden en el formulario, puede hacerlo utilizando la función keyOrder. Por ejemplo :

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

Con Django> = 1.7 debe modificar ContactForm.base_fieldscomo se muestra a continuación:

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 truco se usa en Django Admin PasswordChangeForm: Fuente en Github

zulú
fuente
3
Es curioso, busqué el orden de los campos cuando intentaba averiguar por qué la subclasificación PasswordChangeFormcambiaba el orden de los campos, por lo que su ejemplo resultó ser muy útil para mí. Parece que es porque base_fieldsno se transfiere a la subclase. La solución parece estar diciendo PasswordChangeFormSubclass.base_fields = PasswordChangeForm.base_fieldsdespués de la definición de `PasswordChangeFormSubclass
orblivion
@orblivion: se usa ContactForm.base_fields[k]al construir el dictado ordenado. Hay un error tipográfico en la respuesta, la
editaré
5

Los campos de formulario tienen un atributo para el orden de creación, llamado creation_counter. .fieldsEl atributo es un diccionario, por lo que simplemente agregarlo al diccionario y cambiar los creation_counteratributos en todos los campos para reflejar el nuevo orden debería ser suficiente (aunque nunca lo intenté).

zgoda
fuente
5

Utilice un contador en la clase Field. Ordenar por ese 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

Salida:

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

nosklo
fuente
4

A partir de Django 1.7, los formularios utilizan OrderedDict, que no admite el operador append. Entonces tienes que reconstruir el diccionario desde cero ...

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
fuente
Desde python 3.2, puede usar OrderedDict.move_to_end () para anteponer o agregar elementos.
bparker
3

Para referencia futura: las cosas han cambiado un poco desde las nuevas formas. Esta es una forma de reordenar los campos de las clases de formulario base sobre las que no tiene control:

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

Además, hay un poco de documentación sobre SortedDict que se base_fieldsusa aquí: http://code.djangoproject.com/wiki/SortedDict

Stijn Debrouwere
fuente
Usar 'campos' en mi clase Meta funcionó para mí (con un ModelForm) hasta que tuve una razón para establecer un valor inicial para un campo de clave externa usando MyFormSet.form.base_fields, momento en el que el orden en 'campos' no era más respetado. En realidad, mi solución fue simplemente reordenar los campos en el modelo subyacente.
Paul J
2

Si cualquiera fields = '__all__':

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

o excludese utilizan:

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

Luego, Django hace referencia al orden de los campos tal como se define en el modelo . Esto me sorprendió, así que pensé en mencionarlo. Se hace referencia en los documentos de ModelForm :

Si se utiliza cualquiera de estos, el orden en que aparecen los campos en el formulario será el orden en que se definen los campos en el modelo, con las instancias ManyToManyField apareciendo en último lugar.

Paul J
fuente
2

La forma más fácil de ordenar campos en formularios de django 1.9 es usar field_orderen su formulario Form.field_order

Aquí hay un pequeño ejemplo

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

Esto mostrará todo en el orden que especificó en field_orderdict.

Tahir Fazal
fuente
1

Usar fieldsen la Metaclase interna es lo que funcionó para mí en 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']

Tan sencillo como eso.

ariel17
fuente
1
parece que este método solo es válido para modelform, la forma normal django.formsno funciona
Pengfei.X
1

He usado esto 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)

Esto funciona en 1.5 y estoy razonablemente seguro de que todavía funciona en versiones más recientes.

Ian Rolfe
fuente
0

Tiene que ver con la metaclase que se usa para definir la clase de formulario. Creo que mantiene una lista interna de los campos y si inserta en el medio de la lista podría funcionar. Ha pasado un tiempo desde que miré ese código.

Sam Corder
fuente