In che modo Django conosce l'ordine di rendering dei campi modulo?


89

Se ho un modulo Django come:

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

E chiamo il metodo as_table () di un'istanza di questo modulo, Django renderà i campi nello stesso ordine specificato sopra.

La mia domanda è: come fa Django a conoscere l'ordine in cui le variabili di classe sono state definite?

(Inoltre come faccio a sovrascrivere questo ordine, ad esempio quando voglio aggiungere un campo dal metodo init della classe ?)

Risposte:


89

[NOTA: questa risposta è ora piuttosto completamente obsoleta - si prega di vedere la discussione sotto e le risposte più recenti].

Se fè un modulo, i suoi campi sono f.fields, che è un django.utils.datastructures.SortedDict (presenta gli elementi nell'ordine in cui sono stati aggiunti). Dopo la costruzione del modulo, f.fields ha un attributo keyOrder, che è un elenco contenente i nomi dei campi nell'ordine in cui dovrebbero essere presentati. Puoi impostarlo sull'ordine corretto (anche se devi prestare attenzione per assicurarti di non omettere articoli o aggiungere extra).

Ecco un esempio che ho appena creato nel mio progetto attuale:

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

19
Da qualche tempo, i campi verranno ordinati come specificato dall'attributo 'fields' di un ModelForm. Da docs.djangoproject.com/en/1.3/topics/forms/modelforms/… : L'ordine in cui i nomi dei campi sono specificati in quell'elenco viene rispettato quando il modulo li visualizza. Funziona solo per ModelForms: il tuo approccio è ancora abbastanza utile per i moduli regolari, specialmente nelle sottoclassi di moduli che aggiungono campi aggiuntivi.
Chris Lawlor

3
Funziona quando stai facendo cose funky che sovrascrivono alcuni campi ma non altri. +1
Nathan Keller

"Questo" è il suggerimento di Chris Lawlor? Questa risposta è abbastanza vecchia che probabilmente non è più attuale (ma probabilmente era valida per 1.2). È importante assicurarsi che le informazioni inaffidabili vengano contrassegnate, altrimenti i nuovi arrivati ​​non sanno di cosa fidarsi. Grazie per il tuo contributo.
holdenweb

67

Le novità di Django 1.9 sono 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']

1
Questa è la risposta che sto cercando!
sivabudh

1
a partire da 1.9 questa dovrebbe essere la risposta che i nuovi ricercatori dovrebbero guardare, ovviamente funziona ancora in 1.10
Brian H.

2
Nota: non è "fields_order" ma "field_order" (no 's')
Davy

1
Puoi anche specificare solo un sottoinsieme dei campi del modulo che desideri ordinare
danleyb2

40

Sono andato avanti e ho risposto alla mia domanda. Ecco la risposta per riferimento futuro:

In Django form.pyfa un po 'di magia oscura usando il __new__metodo per caricare le variabili di classe self.fieldsalla fine nell'ordine definito nella classe. self.fieldsè SortedDictun'istanza Django (definita indatastructures.py ).

Quindi, per sovrascrivere questo, diciamo nel mio esempio che volevi che il mittente venisse prima ma dovevi aggiungerlo in un metodo init , dovresti fare:

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())))

12
Puoi definire il campo "mittente" come di consueto in alto e quindi utilizzare una variazione nella riga di inserimento:self.fields.insert( 0, 'sender', self.fields['sender'] )
EvdB

4
Questo non funziona più in Django 1.7 perché i campi del modulo usano OrderedDict che non supporta l'aggiunta. Vedi la mia risposta aggiornata qui sotto (troppo lunga per un commento) ...
Paul Kenjora

11

I campi sono elencati nell'ordine in cui sono definiti in ModelClass._meta.fields. Ma se vuoi cambiare l'ordine in Form, puoi farlo usando la funzione keyOrder. Per esempio :

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']

6

Con Django> = 1.7 devi modificare ContactForm.base_fieldscome di seguito:

from collections import OrderedDict

...

class ContactForm(forms.Form):
    ...

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

Questo trucco viene utilizzato in Django Admin PasswordChangeForm: Source su Github


3
Strano, ho cercato l'ordine dei campi quando ho cercato di capire perché la sottoclasse ha PasswordChangeFormcambiato l'ordine dei campi, quindi il tuo esempio mi è stato molto utile. Sembra che sia perché base_fieldsnon viene trasferito alla sottoclasse. La soluzione sembra essere detta PasswordChangeFormSubclass.base_fields = PasswordChangeForm.base_fieldsdopo la definizione di `PasswordChangeFormSubclass
orblivion

@orblivion: utilizzare ContactForm.base_fields[k]durante la creazione del dict ordinato. C'è un errore di battitura nella risposta, modifico
blueFast

5

I campi del modulo hanno un attributo per l'ordine di creazione, chiamato creation_counter. .fieldsattributo è un dizionario, quindi è sufficiente aggiungere al dizionario e modificare gli creation_counterattributi in tutti i campi per riflettere il nuovo ordinamento (non l'ho mai provato, però).


5

Usa un contatore nella classe Field. Ordina per quel contatore:

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

Produzione:

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


4

A partire da Django 1.7 i moduli utilizzano OrderedDict che non supporta l'operatore di aggiunta. Quindi devi ricostruire il dizionario da 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 

A partire da python 3.2 puoi usare OrderedDict.move_to_end () per anteporre o aggiungere elementi.
bparker

3

Per riferimento futuro: le cose sono un po 'cambiate da newforms. Questo è un modo per riordinare i campi dalle classi del modulo di base su cui non hai controllo:

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

Inoltre, c'è un po 'di documentazione su SortedDict che base_fieldsutilizza qui: http://code.djangoproject.com/wiki/SortedDict


Usare "fields" nella mia classe Meta ha funzionato per me (con un ModelForm) fino a quando non ho avuto un motivo per impostare un valore iniziale per un campo di chiave esterna utilizzando MyFormSet.form.base_fields, a quel punto l'ordine in "fields" non era più rispettato. La mia soluzione era in realtà solo riordinare i campi nel modello sottostante.
Paul J

2

Se uno dei due fields = '__all__':

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

oppure excludevengono utilizzati:

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

Quindi Django fa riferimento all'ordine dei campi definito nel modello . Questo mi ha appena catturato, quindi ho pensato di menzionarlo. È referenziato nei documenti ModelForm :

Se uno di questi viene utilizzato, l'ordine in cui i campi vengono visualizzati nel modulo sarà l'ordine in cui i campi sono definiti nel modello, con le istanze ManyToManyField che appaiono per ultime.


2

Il modo più semplice per ordinare i campi nei moduli django 1.9 è usare field_ordernel modulo Form.field_order

Ecco un piccolo esempio

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

Questo mostrerà tutto nell'ordine che hai specificato in field_orderdict.


1

L'utilizzo fieldsnella Metaclasse interna è ciò che ha funzionato per me su 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']

Così semplice.


1
sembra che questo metodo sia valido solo per modelform, la forma normale django.formsnon funziona
Pengfei.X

1

L'ho usato per spostare i campi su:

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)

Funziona con la 1.5 e sono ragionevolmente sicuro che funzioni ancora nelle versioni più recenti.


0

Ha a che fare con la meta classe utilizzata per definire la classe del form. Penso che mantenga un elenco interno dei campi e se lo inserisci al centro dell'elenco potrebbe funzionare. È passato un po 'di tempo da quando ho guardato quel codice.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.