如何在Django表单中指定字段的顺序?

odopli94  于 2023-06-25  发布在  Go
关注(0)|答案(4)|浏览(183)

我们正在使用Django 2.2,我想升级到Django 3.0。我们有一个mixin(写于2017年),它可以向表单添加字段:

class LocalizedFirstLastNameMixin(object):
    def __init__(self, *args, **kwargs):
        self.language_code = kwargs.pop('language_code', 'en')
        super().__init__(*args, **kwargs)
        for loc_field in reversed(self.get_localized_fields()):
            self.fields[loc_field] = User._meta.get_field(loc_field).formfield()
            self.fields[loc_field].required = True
            self.fields.move_to_end(loc_field, last=False)
            self.initial[loc_field] = getattr(self.instance, loc_field, '')

self.get_localized_fields()返回英语的('first_name_en', 'last_name_en')(按此顺序),或本地化为我们正在使用的当前语言的相同内容。
这个mixin被用作继承自ModelForm的表单的基类之一:

class RegistrationForm(AddAttributesToFieldsMixin, CleanEmailMixin, CleanNewPasswordMixin, CleanDateOfBirthMixin, LocalizedFirstLastNameMixin, forms.ModelForm):
    ....

class ProfileForm(AddAttributesToFieldsMixin, CleanDateOfBirthMixin, LocalizedFirstLastNameMixin, forms.ModelForm):
    ....

它适用于Django 2.2版本。但是当我升级到3.0时,我得到这个错误消息:
AttributeError: 'dict' object has no attribute 'move_to_end'
此函数的信息:
Move an existing element to the end (or beginning if last==False).
它属于OrderedDict
所以我想我们希望这些字段在表单字段的开头。
Django 3.0中表单中字段的实现有变化吗?如何指定字段的顺序?如果我改变它,它会在以前的版本中工作吗,比如Django 2.2?
我检查了Django 3.0 release notes以及从3.0.1到3.0.5的版本,我没有找到任何关于这个问题的文档。

**更新:**我发现我可以调用self.order_fields(...),但如何定义来自模型的字段?我只想在字段列表的开头添加两个额外的字段。

koaltpgm

koaltpgm1#

我在Django开发者邮件列表中询问,他们告诉我不要自己操作字段的顺序,而是使用这里记录的支持的API方法。所以我修改了代码,使用了self.order_fields

class LocalizedFirstLastNameMixin(object):
    def __init__(self, *args, **kwargs):
        self.language_code = kwargs.pop('language_code', 'en')
        super().__init__(*args, **kwargs)
        localized_fields = self.get_localized_fields()
        for loc_field in localized_fields:
            self.fields[loc_field] = User._meta.get_field(loc_field).formfield()
            self.fields[loc_field].required = True
            self.initial[loc_field] = getattr(self.instance, loc_field, '')
        self.order_fields(field_order=localized_fields)

请注意,我只对前两个字段进行排序。所有其他字段都按默认顺序保留。我现在也不必以相反的顺序添加字段。

lymnna71

lymnna712#

表单中字段的实现是否有变化
Django 2.2和3之间的变化是声明字段的初始化方式:

  • Django 2.2.12 https://github.com/django/django/blob/2.2.12/django/forms/forms.py#L39
  • Django 3 https://github.com/django/django/blob/master/django/forms/forms.py#L36。

我猜这是因为Django 3支持Python 3.6或更高版本(https://docs.djangoproject.com/en/3.0/faq/install/),并且Python 3.6的dicts是插入顺序的(Are dictionaries ordered in Python 3.6+?)。
我会将self.fields转换为OrderedDict(基本上回到2.2版中的状态),以再次启用self.fields.move_to_end

from collections import OrderedDict

class LocalizedFirstLastNameMixin(object):
    def __init__(self, *args, **kwargs):
        self.language_code = kwargs.pop('language_code', 'en')
        super().__init__(*args, **kwargs)
        self.fields = OrderedDict(self.fields)
        for loc_field in reversed(self.get_localized_fields()):
            self.fields[loc_field] = User._meta.get_field(loc_field).formfield()
            self.fields[loc_field].required = True
            self.fields.move_to_end(loc_field, last=False)
            self.initial[loc_field] = getattr(self.instance, loc_field, '')
pbwdgjma

pbwdgjma3#

OrderedDict来自标准库。

from collections import OrderedDict

示例有.move_to_end(),它只是说你的dict对象没有.move_to_end(),这意味着你使用的是一个普通的dict。转换为OrderedDict,如下所示

x = { "key" : "value"}
y = OrderedDict(x)
# OrderedDict([('key', 'value')])

现在.move_to_end()将工作

jaxagkaj

jaxagkaj4#

我来晚了,但这可能对别人有用。在最近的Python(3.6+)中,普通的dict按照键的添加顺序保存键。对于早期版本,您需要使用collections.OrderedDict。Django曾经使用它自己的类似dict的类,但现在使用vanilla dictprint( type(form_instance.fields))如果有疑问。
无论如何,您可以通过在表单的__init__方法中重新定义self.fields来以任何方式重新排序字段。如:

class OrderUncWaferBatchForm( ModelForm): 

    #added non-model fields ...
    ponum = forms.CharField( max_length=10,
        label=mark_safe("Purchase Order<br>Number")
    )
    supplier = forms.CharField( max_length = 16)

    def __init__(self, *args, **kwargs):
        super().__init__( *args, **kwargs)

        self.fields = reorder_dict( self.fields,
            # these fields in this order at the top of the form 
            ['ponum','supplier', 's_cost', 'wafer_stock_orig'])

    class Meta:
        model=WaferBatch
        fields = ...

由于我已经编写了一个非常通用的reorder_dict函数,所以它在这里

def reorder_dict(old_dict, reordering, dict_class=None):
    """ Returns a new dict with keys added in the order specified by
        reordering. This is a sequence of tuples (fromkey, tokey) which
        operates on the list of keys of old_dict. Each tuple
        (from_key, to_key) removes the named key from its current
        posotion and inserts it before to_key. Each tuple operates
        on the list permuted by all previous ones.

        Any key that is not in old_dict will cause an IndexError

        to_key can also be an integer for list.insert position. Using
        a bignum will insert at the end. Using 0, at the top. 

        As a special case reordering can also be a list of key strings (not tuples). 
        This means to put the keys in the list at the top (positions 0,1,...) and
        leave any keys not mentioned in theit original order after the
        ones in the list.

        Efficiency is not a consideration. It's intended for use
        on form.fields, which rarely contains more than a handful of fields
    """
    if dict_class is None:
        dict_class = dict  # on old Python wouold have to be an OrderedDict

    if isinstance( reordering[0], str):
        reordering = [ (k,n) for n,k in enumerate( reordering) ]

    keys = list( old_dict.keys() )

    for from_key, to_key in reordering:

        fom = keys.index( from_key)  # from is reserved word
        if isinstance( to_key, int):
            to = to_key
        else:
            to = keys.index( to_key)

        keys.insert( to, from_key)
        if to <= fom:
            fom += 1  # it's been pushed down
        del keys[fom]

    result = dict_class()
    for k in keys:
        result[k] = old_dict[k]

    return result

相关问题