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

How do I specify order of fields in Django form?

我们正在使用 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() returns ('first_name_en', 'last_name_en')(按此顺序)英文,或本地化为我们正在使用的当前语言。

此 mixin 用作继承自 ModelForm 的表单的基础之一 类:

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

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

它适用于最高 2.2 的 Django 版本。但是当我升级到 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(...),但是如何定义来自模型的字段?我只想在字段列表的开头添加两个附加字段。

Is there a change in the implementation of the fields in forms

Django 2.2 和 3 之间的变化是声明字段的初始化方式:

我想这是因为 Django 3 支持 Python 从版本 3.6 或更高版本 (https://docs.djangoproject.com/en/3.0/faq/install/) and since Python 3.6 dicts are insertion ordered ()。


我会将 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, '')

OrderedDict 来自标准库。

from collections import OrderedDict

和实例有 .move_to_end(),它只是说你 dict 对象没有 .move_to_end() 这意味着你使用的是普通字典。像这样将其转换为 OrderedDict

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

现在 .move_to_end() 可以使用

我在Django developers mailing list and I have been told not to manipulate the order of fields myself, but instead to use the supported API methods documented here问过。所以我更改了代码并使用 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)

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