Bagaimana Django Mengetahui Urutan untuk Merender Bidang Formulir?

91

Jika saya memiliki bentuk Django seperti:

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

Dan saya memanggil metode as_table () dari turunan bentuk ini, Django akan membuat bidang sebagai urutan yang sama seperti ditentukan di atas.

Pertanyaan saya adalah bagaimana Django mengetahui urutan variabel kelas dimana didefinisikan?

(Juga bagaimana cara mengganti urutan ini, misalnya ketika saya ingin menambahkan bidang dari metode init kelas ?)

Greg
sumber

Jawaban:

89

[CATATAN: jawaban ini sekarang benar-benar ketinggalan jaman - silakan lihat diskusi di bawahnya, dan jawaban yang lebih baru].

Jika fadalah formulir, kolomnya adalah f.fields, yaitu a django.utils.datastructures.SortedDict (menampilkan item dalam urutan penambahannya). Setelah konstruksi formulir f.fields memiliki atribut keyOrder, yang merupakan daftar yang berisi nama field dalam urutan penyajiannya. Anda dapat menyetel ini ke urutan yang benar (meskipun Anda perlu berhati-hati untuk memastikan Anda tidak menghilangkan item atau menambahkan ekstra).

Berikut adalah contoh yang baru saya buat di proyek saya saat ini:

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
sumber
20
Untuk beberapa waktu sekarang, bidang akan diurutkan seperti yang ditentukan oleh atribut 'bidang' dari ModelForm. Dari docs.djangoproject.com/en/1.3/topics/forms/modelforms/… : Urutan di mana nama bidang ditentukan dalam daftar itu dipatuhi ketika formulir merendernya. Ini hanya berfungsi untuk ModelForms - pendekatan Anda masih cukup berguna untuk formulir biasa, terutama dalam subkelas formulir yang menambahkan bidang tambahan.
Chris Lawlor
3
Ini berfungsi saat Anda melakukan hal-hal funky yang menimpa beberapa bidang tetapi tidak yang lain. +1
Nathan Keller
"Ini" adalah saran Chris Lawlor? Jawaban ini cukup tua sehingga mungkin tidak lagi terkini (tapi mungkin bagus untuk 1.2). Penting untuk memastikan bahwa informasi yang tidak dapat diandalkan akan ditandai, jika tidak, pendatang baru tidak tahu apa yang harus dipercaya. Terima kasih atas masukan Anda.
holdenweb
72

Baru di Django 1.9 adalah Form.field_order dan 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
sumber
1
Inilah jawaban yang saya cari!
sivabudh
1
mulai 1.9 ini seharusnya menjadi jawaban yang harus dilihat oleh pencari baru, jelas masih berfungsi di 1.10
Brian H.
2
Catatan: ini bukan "field_order" tapi "field_order" (tanpa 's')
Davy
1
Anda bahkan dapat menentukan hanya sebagian dari kolom formulir yang ingin dipesan
danleyb2
40

Saya melanjutkan dan menjawab pertanyaan saya sendiri. Inilah jawaban untuk referensi di masa mendatang:

Di Django form.pymelakukan beberapa sihir gelap menggunakan __new__metode untuk memuat variabel kelas Anda pada akhirnya ke self.fieldsdalam urutan yang ditentukan dalam kelas. self.fieldsadalah SortedDictturunan Django (didefinisikan dalam datastructures.py).

Jadi untuk menimpa ini, katakanlah dalam contoh saya Anda ingin pengirim datang lebih dulu tetapi perlu menambahkannya dalam metode init , Anda akan melakukan:

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
sumber
12
Anda dapat menentukan bidang 'pengirim' seperti biasa di bagian atas dan kemudian menggunakan variasi pada baris sisipan Anda:self.fields.insert( 0, 'sender', self.fields['sender'] )
EvdB
4
Ini tidak lagi bekerja di Django 1.7 karena bidang formulir menggunakan OrderedDict yang tidak mendukung penambahan. Lihat jawaban saya yang diperbarui di bawah ini (terlalu lama untuk komentar) ...
Paul Kenjora
11

Bidang dicantumkan dalam urutan yang ditentukan dalam ModelClass._meta.fields. Namun jika Anda ingin mengubah urutan di Form, Anda bisa melakukannya dengan menggunakan fungsi keyOrder. Sebagai contoh :

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

Dengan Django> = 1.7 Anda harus memodifikasi ContactForm.base_fieldsseperti di bawah ini:

from collections import OrderedDict

...

class ContactForm(forms.Form):
    ...

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

Trik ini digunakan di Django Admin PasswordChangeForm: Sumber di Github

Zulu
sumber
3
Lucu, saya mencari pengurutan bidang ketika mencoba mencari tahu mengapa subclass PasswordChangeFormmengubah urutan bidang, jadi contoh Anda kebetulan sangat berguna bagi saya. Sepertinya itu karena base_fieldstidak terbawa ke subclass. Solusinya tampaknya dikatakan PasswordChangeFormSubclass.base_fields = PasswordChangeForm.base_fieldssetelah definisi `PasswordChangeFormSubclass
orblivion
@orblivion: gunakan ContactForm.base_fields[k]saat membuat dict yang dipesan. Ada kesalahan ketik jawabannya, saya akan mengedit
blueFast
5

Bidang formulir memiliki atribut untuk pesanan pembuatan, disebut creation_counter. .fieldsatribut adalah kamus, jadi menambahkan ke kamus dan mengubah creation_counteratribut di semua bidang untuk mencerminkan pengurutan baru sudah cukup (jangan pernah mencoba ini).

zgoda.dll
sumber
5

Gunakan penghitung di kelas Lapangan. Urutkan berdasarkan penghitung itu:

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

Keluaran:

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

nosklo.dll
sumber
4

Dari bentuk Django 1.7 gunakan OrderedDict yang tidak mendukung operator tambahan. Jadi, Anda harus membangun kembali kamus dari awal ...

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
sumber
Sejak python 3.2 Anda dapat menggunakan OrderedDict.move_to_end () untuk menambahkan atau menambahkan item.
bparker
3

Untuk referensi di masa mendatang: banyak hal telah berubah sedikit sejak bentuk baru. Ini adalah salah satu cara menyusun ulang bidang dari kelas formulir dasar yang tidak dapat Anda kendalikan:

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

Juga, ada sedikit dokumentasi tentang SortedDict yang base_fieldsdigunakan di sini: http://code.djangoproject.com/wiki/SortedDict

Stijn Debrouwere
sumber
Menggunakan 'bidang' di kelas saya Meta bekerja untuk saya (dengan ModelForm) sampai saya punya alasan untuk menetapkan nilai awal untuk bidang kunci asing menggunakan MyFormSet.form.base_fields, di mana urutan di 'bidang' tidak ada lagi dihormati. Solusi saya sebenarnya hanya mengatur ulang bidang dalam model yang mendasarinya.
Paul J
2

Jika salah satu fields = '__all__':

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

atau excludedigunakan:

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

Kemudian Django mereferensikan urutan bidang seperti yang ditentukan dalam model . Ini baru saja menarik perhatian saya, jadi saya pikir saya akan menyebutkannya. Ini direferensikan di dokumen ModelForm :

Jika salah satu dari ini digunakan, urutan bidang yang muncul di formulir akan menjadi urutan bidang yang ditentukan dalam model, dengan instance ManyToManyField muncul terakhir.

Paul J.
sumber
2

Cara termudah untuk memesan bidang dalam formulir django 1.9 adalah dengan menggunakan field_orderformulir Anda Form.field_order

Ini contoh kecilnya

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

Ini akan menampilkan semuanya dalam urutan yang Anda tentukan di field_orderdict.

Tahir Fazal
sumber
1

Menggunakan fieldsdi Metakelas dalam adalah hal yang berhasil bagi saya 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']

Sesimpel itu.

ariel17
sumber
1
Sepertinya metode ini hanya berlaku untuk modelform, bentuk normal django.formstidak berfungsi
Pengfei.X
1

Saya telah menggunakan ini untuk memindahkan bidang tentang:

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)

Ini berfungsi di 1.5 dan saya cukup yakin ini masih berfungsi di versi yang lebih baru.

Ian Rolfe
sumber
0

Ini ada hubungannya dengan kelas meta yang digunakan dalam mendefinisikan kelas formulir. Saya pikir itu menyimpan daftar internal bidang dan jika Anda memasukkan ke tengah daftar itu mungkin berhasil. Sudah lama sejak saya melihat kode itu.

Sam Corder
sumber