【问题标题】:Django admin: exclude field on change form onlyDjango 管理员:仅在更改表单上排除字段
【发布时间】:2023-05-20 12:16:01
【问题描述】:

是否有办法检测模型中的信息是否正在添加或更改。

如果有这个信息可以用来排除字段。

一些伪代码来说明我在说什么。

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

谢谢

【问题讨论】:

    标签: python django django-admin


    【解决方案1】:

    设置self.exclude 就像@steve-pike 提到的那样,使整个SubSectionAdmin 单例更改其排除属性。 单例是一个类,每次实例化时都会重用同一个实例,所以一个实例只会在第一次使用构造函数时创建,后续使用构造函数将返回相同的实例。请参阅wiki page 以获得更深入的描述。 这意味着,如果您编写代码以排除更改时的字段,这意味着如果您首先添加一个项目,该字段将在那里,但如果您打开一个项目进行更改,则该字段将被排除在您的后续访问中到添加页面。

    实现每个请求行为的最简单方法是使用get_fields 并测试obj 参数,如果我们正在添加一个对象,则为None,如果我们正在更改,则为一个对象的实例一个东西。 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
    

    【讨论】:

      【解决方案2】:

      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)
      

      这只会通知表单排除那些额外的字段。

      不确定在排除必填字段的情况下这将如何表现...

      【讨论】:

        【解决方案3】:

        以下方法的优点是不覆盖对象范围的exclude 属性;而是根据每种类型的请求重置它

        class SubSectionAdmin(admin.ModelAdmin):
            add_exclude = ('field1', 'field2')
            edit_exclude = ('field2',)
        
            def add_view(self, *args, **kwargs):
                self.exclude = getattr(self, 'add_exclude', ())
                return super(SubSectionAdmin, self).add_view(*args, **kwargs)
        
            def change_view(self, *args, **kwargs):
                self.exclude = getattr(self, 'edit_exclude', ())
                return super(SubSectionAdmin, self).change_view(*args, **kwargs)
        

        【讨论】:

        • ModelAdmin 类是单例的,所以修改它们的状态是有问题的
        【解决方案4】:
        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)
        

        【讨论】:

        • self.exclude = ('field',)
        • 如果该字段是管理表单中的自定义字段怎么办?
        • 请参阅下面的答案,了解为什么这是危险的,而不是要走的路。 Admin 对象是单例的,因此设置 self.exclude 会更改 SubSectionAdmin 的状态以供后续 add_view 调用。
        【解决方案5】:

        我相信您可以覆盖ModeAdmin 类的get_fieldsets 方法。看下面的例子,在下面的代码示例中,我只想在添加新国家时在表单中显示country字段,为了检查是否正在添加对象,我们只需要检查obj == None,我我正在指定我需要的字段。现在否则obj != None 表示正在更改现有对象,因此您可以指定要从更改表单中排除的字段。

        def get_fieldsets(self, request: HttpRequest, obj=None):
            fieldset = super().get_fieldsets(request, obj=obj)
            if obj == None: # obj is None when you are adding new object.
                fieldset[0][1]["fields"] = ["country"]
            else:
                fieldset[0][1]["fields"] = [
                    f.name
                    for f in self.model._meta.fields
                    if f.name not in ["id", "country"]
                ]
            return fieldset
        

        【讨论】: