【问题标题】:How does Django Know the Order to Render Form Fields?Django 如何知道呈现表单字段的顺序?
【发布时间】:2010-09-25 22:24:43
【问题描述】:

如果我有一个 Django 表单,例如:

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

我调用这个表单实例的 as_table() 方法,Django 将按照上面指定的顺序呈现字段。

我的问题是 Django 如何知道定义类变量的顺序?

(还有如何覆盖这个顺序,例如当我想从类的 init 方法中添加一个字段时?)

【问题讨论】:

  • 可能相关的是,python dicts 现在按添加顺序返回项目,而不是根据它们的散列方式以随机顺序返回。命名空间通常是字典(__slots__ 提供异常?)

标签: python django django-forms


【解决方案1】:

要添加一些东西,你可以使用这个(Django 3+):

class ...(forms.ModelForm):
    field = ...

    class Meta:
        model = Xxxxxx
        fields = '__all__'
    
    field_order = ['field', '__all__']

__all__ 有效

【讨论】:

    【解决方案2】:

    这些答案都不适合我,实际上,您不必做任何自定义,您只需在模型类中按照您想要的顺序对字段进行排序。例如...下面的代码

    from django.db import models
    
    
    class Student(models.Model):
        class Meta:
            verbose_name_plural = "categories"
    
        id = models.AutoField(primary_key=True)
        name = models.CharField(max_length=300)
        nick_name = models.CharField(max_length=300)
        created_at = models.DateTimeField(auto_now_add=True)
        updated_at = models.DateTimeField(auto_now=True)
    
        def __str__(self):
            return self.name
    

    您的模型管理界面将完全按照您在这种情况下声明的顺序显示字段(id, name, nick_name )

    【讨论】:

      【解决方案3】:

      Django 1.9 的新功能是 Form.field_orderForm.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.9 开始,这应该是新搜索者应该关注的答案,显然在 1.10 中仍然有效
      • 注意:不是“fields_order”而是“field_order”(没有's')
      • 您甚至可以只指定您想要订购的表单字段的子集
      • 仍然有效,是 Django 3.0 的最佳答案
      【解决方案4】:

      [注意:这个答案现在已经完全过时了 - 请参阅下面的讨论,以及更多最近的答案]。

      如果f 是一个表单,它的字段是f.fields,这是一个django.utils.datastructures.SortedDict(它按照添加的顺序显示项目)。在表单构造之后,f.fields 有一个 keyOrder 属性,它是一个列表,其中包含按照它们应该出现的顺序的字段名称。您可以将其设置为正确的顺序(尽管您需要小心确保不会遗漏项目或添加额外内容)。

      这是我刚刚在当前项目中创建的示例:

      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
      

      【讨论】:

      • 现在,字段将按照 ModelForm 的“字段”属性指定的顺序进行排序。来自docs.djangoproject.com/en/1.3/topics/forms/modelforms/…:当表单呈现它们时,会遵守在该列表中指定字段名称的顺序。这仅适用于 ModelForms - 您的方法对于常规表单仍然非常有用,尤其是在添加额外字段的表单子类中。
      • 当你在做一些时髦的东西覆盖某些字段而不是其他字段时,这很有效。 +1
      • "This" 是 Chris Lawlor 的建议?这个答案已经足够老了,它可能不再是最新的(但可能对 1.2 有好处)。确保标记不可靠的信息很重要,否则新手不知道该信任什么。感谢您的意见。
      【解决方案5】:

      在 django 1.9 表单中排序字段的最简单方法是在表单中使用 field_order Form.field_order

      这是一个小例子

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

      这将按照您在field_order dict 中指定的顺序显示所有内容。

      【讨论】:

        【解决方案6】:

        从 Django 1.7 开始,表单使用不支持附加运算符的 OrderedDict。所以你必须从头开始重建字典.​​.....

        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 
        

        【讨论】:

        • 从 python 3.2 开始,您可以使用 OrderedDict.move_to_end() 来添加或添加项目。
        【解决方案7】:

        对于 Django >= 1.7,您必须修改 ContactForm.base_fields 如下:

        from collections import OrderedDict
        
        ...
        
        class ContactForm(forms.Form):
            ...
        
        ContactForm.base_fields = OrderedDict(
            (k, ContactForm.base_fields[k])
            for k in ['your', 'field', 'in', 'order']
        )
        

        这个技巧用在 Django Admin PasswordChangeForm: Source on Github

        【讨论】:

        • 有趣的是,当我试图弄清楚为什么子类化PasswordChangeForm 改变了字段的顺序时,我寻找了字段的顺序,所以你的例子恰好对我很有用。似乎是因为base_fields 没有被继承到子类。解决方案似乎是在 `PasswordChangeFormSubclass 的定义之后说PasswordChangeFormSubclass.base_fields = PasswordChangeForm.base_fields
        • @orblivion:在构建有序字典时使用ContactForm.base_fields[k]。答案有错别字,我会编辑
        【解决方案8】:

        如果fields = '__all__'

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

        exclude 被使用:

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

        然后 Django 引用模型中定义的字段顺序。这让我很生气,所以我想我会提到它。在ModelForm docs中引用:

        如果使用其中任何一个,则字段在表单中出现的顺序将是字段在模型中定义的顺序,ManyToManyField 实例最后出现。

        【讨论】:

        • 确实,Django 3 中的默认顺序是模型中声明的,幸运的是,即使您在模型迁移后更改顺序(即,它是当前声明顺序而不是数据库中字段的顺序(如果有)。太好了,通过移动它们,我可以在默认表单上订购它们...
        【解决方案9】:

        我用它来移动字段:

        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)
        

        这在 1.5 中有效,我有理由确定它在更新的版本中仍然有效。

        【讨论】:

          【解决方案10】:

          Meta 内部类中使用fieldsDjango==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']
          

          就这么简单。

          【讨论】:

          • 好像这个方法只对modelform有效,正常的形式django.forms不起作用
          【解决方案11】:

          字段按照它们在 ModelClass._meta.fields 中定义的顺序列出。但是如果你想改变Form中的顺序,你可以使用keyOrder函数来完成。 例如:

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

          【讨论】:

            【解决方案12】:

            供将来参考:自新形式以来,情况发生了一些变化。这是从您无法控制的基本表单类中重新排序字段的一种方法:

            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
            

            此外,还有一些关于 base_fields 在这里使用的 SortedDict 的文档:http://code.djangoproject.com/wiki/SortedDict

            【讨论】:

            • 在我的课堂 Meta 中使用“字段”对我有用(使用 ModelForm),直到我有理由使用 MyFormSet.form.base_fields 为外键字段设置初始值,此时“字段”中的顺序不再受到尊重。我的解决方案实际上只是对底层模型中的字段重新排序。
            【解决方案13】:

            在 Field 类中使用计数器。按该计数器排序:

            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
            

            输出:

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

            【讨论】:

              【解决方案14】:

              我继续回答我自己的问题。以下是供日后参考的答案:

              在 Django 中,form.py 使用__new__ 方法执行一些黑魔法,最终按照类中定义的顺序将您的类变量加载到self.fieldsself.fields 是一个 Django SortedDict 实例(在datastructures.py 中定义)。

              所以要覆盖这一点,比如在我的示例中,您希望 sender 首先出现,但需要将其添加到 init 方法中,您可以这样做:

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

              【讨论】:

              • 您可以在顶部正常定义“发件人”字段,然后在插入行上使用变体:self.fields.insert( 0, 'sender', self.fields['sender'] )
              • 这在 Django 1.7 中不再有效,因为表单字段使用不支持追加的 OrderedDict。请参阅下面的更新答案(评论太长)...
              【解决方案15】:

              表单字段有一个创建顺序的属性,称为creation_counter.fields 属性是一个字典,所以简单地添加到字典并更改所有字段中的 creation_counter 属性以反映新的顺序就足够了(虽然从未尝试过)。

              【讨论】:

                【解决方案16】:

                它与用于定义表单类的元类有关。我认为它保留了字段的内部列表,如果您插入到列表的中间,它可能会起作用。我已经有一段时间没有看那个代码了。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-09-06
                  • 2011-12-30
                  • 1970-01-01
                  • 2017-04-23
                  • 2020-06-23
                  相关资源
                  最近更新 更多