Django admin: 仅在更改表单中排除字段

13 投票
6 回答
23969 浏览
提问于 2025-04-15 13:27

有没有办法检测一个模型中的信息是否被添加或更改了呢?

如果有的话,这些信息能不能用来排除某些字段?

下面是一些伪代码,来说明我想表达的意思。

class SubSectionAdmin(admin.ModelAdmin):
    if something.change_or_add = 'change':
        exclude = ('field',)
    ...

谢谢

6 个回答

14

设置 self.exclude 就像 @steve-pike 提到的那样,会让整个 SubSectionAdmin 的单例改变它的排除属性。单例是一种类,每次创建这个类的实例时都会重用同一个实例,也就是说,只有在第一次使用构造函数时会创建一个实例,之后再使用构造函数时会返回同一个实例。想了解更多,可以查看这个 维基页面

这意味着,如果你写代码来在更改时排除某个字段,那么如果你第一次添加一个项目,这个字段会存在,但如果你打开一个项目进行更改,这个字段在你后续访问添加页面时就会被排除。

实现每次请求行为的最简单方法是使用 get_fields 方法,并根据 obj 参数进行测试。如果我们是在添加一个对象,obj 就是 None;如果我们是在更改一个对象,obj 就是一个对象的实例。get_fields 方法从 Django 1.7 开始提供。

class SubSectionAdmin(admin.ModelAdmin):
    def get_fields(self, request, obj=None):
        fields = super(SubSectionAdmin, self).get_fields(request, obj)
        if obj:  # obj will be None on the add page, and something on change pages
            fields.remove('field')
        return fields

更新:

请注意,get_fields 可能会返回一个元组,所以你可能需要将 fields 转换成列表来移除某些元素。如果你尝试移除的字段名不在列表中,你可能会遇到错误。因此,在某些情况下,如果你有其他因素排除字段,构建一个排除集合并使用列表推导式来移除可能会更好:

class SubSectionAdmin(admin.ModelAdmin):
    def get_fields(self, request, obj=None):
        fields = list(super(SubSectionAdmin, self).get_fields(request, obj))
        exclude_set = set()
        if obj:  # obj will be None on the add page, and something on change pages
            exclude_set.add('field')
        return [f for f in fields if f not in exclude_set]

另外,你也可以在 get_fieldsets 方法中对结果进行 deepcopy,在其他用例中这可能会让你更好地了解排除某些内容的上下文。最明显的是,如果你需要对字段集名称进行操作,这会非常有用。而且,如果你实际上使用字段集,这是唯一的做法,因为这样会省去对 get_fields 的调用。

from copy import deepcopy

class SubSectionAdmin(admin.ModelAdmin):
    def get_fieldsets(self, request, obj=None):
        """Custom override to exclude fields"""
        fieldsets = deepcopy(super(SubSectionAdmin, self).get_fieldsets(request, obj))

        # Append excludes here instead of using self.exclude.
        # When fieldsets are defined for the user admin, so self.exclude is ignored.
        exclude = ()

        if not request.user.is_superuser:
            exclude += ('accepted_error_margin_alert', 'accepted_error_margin_warning')

        # Iterate fieldsets
        for fieldset in fieldsets:
            fieldset_fields = fieldset[1]['fields']

            # Remove excluded fields from the fieldset
            for exclude_field in exclude:
                if exclude_field in fieldset_fields:
                    fieldset_fields = tuple(field for field in fieldset_fields if field != exclude_field)  # Filter
                    fieldset[1]['fields'] = fieldset_fields  # Store new tuple

        return fieldsets
18

orwellian的回答会让整个SubSectionAdmin这个单例的排除属性发生变化。

为了确保在每次请求时都能排除特定字段,可以这样做:

class SubSectionAdmin(admin.ModelAdmin):
    # ...
    def get_form(self, request, obj=None, **kwargs):
        """Override the get_form and extend the 'exclude' keyword arg"""
        if obj:
            kwargs.update({
                'exclude': getattr(kwargs, 'exclude', tuple()) + ('field',),
            })
        return super(SubSectionAdmin, self).get_form(request, obj, **kwargs)

这样做只是告诉表单要排除那些额外的字段。

不过,我不太确定如果排除了一个必填字段会有什么样的表现……

11
class SubSectionAdmin(admin.ModelAdmin):
    # ...
    def change_view(self, request, object_id, extra_context=None):       
        self.exclude = ('field', )
        return super(SubSectionAdmin, self).change_view(request, object_id, extra_context)

当然可以!请把你想要翻译的内容发给我,我会帮你用简单易懂的语言解释清楚。

撰写回答