【问题标题】:How do I require an inline in the Django Admin?如何在 Django Admin 中要求内联?
【发布时间】:2009-07-30 14:21:19
【问题描述】:

我有以下管理员设置,以便我可以同时添加/编辑用户及其个人资料。

class ProfileInline(admin.StackedInline):
    """
    Allows profile to be added when creating user
    """
    model = Profile


class UserProfileAdmin(admin.ModelAdmin):
    """
    Options for the admin interface
    """
    inlines = [ProfileInline]
    list_display = ['edit_obj', 'name', 'username', 'email', 'is_active',
        'last_login', 'delete_obj']
    list_display_links = ['username']
    list_filter = ['is_active']
    fieldsets = (
        (None, {
            'fields': ('first_name', 'last_name', 'email', 'username',
                'is_active', 'is_superuser')}),
        )
    ordering = ['last_name', 'first_name']
    search_fields = ['first_name', 'last_name']

admin.site.register(User, UserProfileAdmin)

问题是添加用户时我需要配置文件内联表单中的两个字段。除非输入输入,否则内联表单不会验证。反正有没有要求内联,所以不能留空?

【问题讨论】:

    标签: python python-3.x django django-admin inline


    【解决方案1】:

    我接受了 Carl 的建议,并做出了比我在对他的回答的评论中提到的 hack-ish 更好的实施。这是我的解决方案:

    来自我的 forms.py:

    from django.forms.models import BaseInlineFormSet
    
    
    class RequiredInlineFormSet(BaseInlineFormSet):
        """
        Generates an inline formset that is required
        """
    
        def _construct_form(self, i, **kwargs):
            """
            Override the method to change the form attribute empty_permitted
            """
            form = super(RequiredInlineFormSet, self)._construct_form(i, **kwargs)
            form.empty_permitted = False
            return form
    

    还有 admin.py

    class ProfileInline(admin.StackedInline):
        """
        Allows profile to be added when creating user
        """
        model = Profile
        extra = 1
        max_num = 1
        formset = RequiredInlineFormSet
    
    
    class UserProfileAdmin(admin.ModelAdmin):
        """
        Options for the admin interface
        """
        inlines = [ProfileInline]
        list_display = ['edit_obj', 'name', 'username', 'email', 'is_active',
            'last_login', 'delete_obj']
        list_display_links = ['username']
        list_filter = ['is_active']
        fieldsets = (
            (None, {
                'fields': ('first_name', 'last_name', 'email', 'username',
                    'is_active', 'is_superuser')}),
            (('Groups'), {'fields': ('groups', )}),
        )
        ordering = ['last_name', 'first_name']
        search_fields = ['first_name', 'last_name']
    
    
    admin.site.register(User, UserProfileAdmin)
    

    这正是我想要的,它使 Profile 内联表单集验证。因此,由于配置文件表单中有必填字段,因此如果未在内联表单中输入必填信息,它将验证并失败。

    【讨论】:

    • 如果您使用的是GenericInlineModelAdmin,请将BaseInlineFormSet 替换为BaseGenericInlineFormSet
    • 谢谢!但是,如果您的表单没有必填字段,则它仍然不会在为空时验证和保存。使用 form.has_changed = lambda: True 对表单进行猴子修补,尽管它是空的,但仍保存它。
    【解决方案2】:

    现在使用 Django 1.7,您可以使用参数 min_num。你不再需要 RequiredInlineFormSet 类了。

    https://docs.djangoproject.com/en/1.8/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.min_num

    class ProfileInline(admin.StackedInline):
        """
        Allows profile to be added when creating user
        """
        model = Profile
        extra = 1
        max_num = 1
        min_num = 1 # new in Django 1.7
    
    
    class UserProfileAdmin(admin.ModelAdmin):
        """
        Options for the admin interface
        """
        inlines = [ProfileInline]
        ...
    
    
    admin.site.register(User, UserProfileAdmin)
    

    【讨论】:

    • 这仅控制显示的内联数。不需要填写任何内容。
    • 其实如果 inline 有其中一个字段是必填的,它也控制了 inline 本身的填写。
    • 在 1.10 中进行测试,即使设置了“min_num”,用户仍然可以在所有记录上选择“删除”,而不会留下任何记录。
    【解决方案3】:

    您可能可以这样做,但您必须在表单集/内联代码中亲自动手。

    首先,我认为在这种情况下,您希望表单集中始终有一个表单,并且永远不会超过一个,因此您需要在您的配置文件内联。

    您的核心问题是BaseFormSet._construct_form passes empty_permitted=True 到表单集中的每个“额外”(即空)表单。此参数告诉表单如果未更改则绕过验证。您只需要找到一种方法来为表单设置 empty_permitted=False。

    您可以在您的内联中use your own BaseInlineFormset subclass,这样可能会有所帮助。注意到 _construct_form 采用 **kwargs 并允许它覆盖传递给各个 Form 实例的 kwargs,您可以在 Formset 子类中覆盖 _construct_forms 并让它在每次调用 _construct_form 时传递 empty_permitted=False。缺点是您依赖于内部 API(并且您必须重写 _construct_forms)。

    或者,您可以尝试覆盖 ProfileInline 上的 get_formset 方法,并在调用父级的 get_formset 后,手动戳返回的表单集中的表单:

    def get_formset(self, request, obj=None, **kwargs):
        formset = super(ProfileInline, self).get_formset(request, obj, **kwargs)
        formset.forms[0].empty_permitted = False
        return formset
    

    玩一玩,看看你能做些什么!

    【讨论】:

    • 感谢您的信息。我确实提出了一个解决方案,但它非常hack-ish,我并不为此感到特别自豪。我最终覆盖了 ModelAdmin 的 add_view 并从默认视图中复制了所有代码并修改了表单集值。我将看看您的建议,看看我是否可以以更清洁的方式实施它。感谢您的领导!
    【解决方案4】:

    最简单、最自然的方法是通过 fomset clean():

    class RequireOneFormSet(forms.models.BaseInlineFormSet):
        def clean(self):
            super().clean()
            if not self.is_valid():
                return
            if not self.forms or not self.forms[0].cleaned_data:
                raise ValidationError('At least one {} required'
                                      .format(self.model._meta.verbose_name))
    
    class ProfileInline(admin.StackedInline):
        model = Profile
        formset =  RequireOneFormSet
    

    (受this Matthew Flanagan's snippet 和下方 Mitar 评论的启发,经测试可在 Django 1.11 和 2.0 中工作)。

    【讨论】:

    • 完美!我稍微改了一下,使用if not self.is_valid():而不是手动通过self.errors并使用self.model._meta.verbose_name
    【解决方案5】:

    您需要在 inline 中设置 min_num,在 formset 中设置 validate_min

    https://docs.djangoproject.com/en/1.8/topics/forms/formsets/#validate-min

    class SomeInline(admin.TabularInline):
        ...
        min_num = 1
    
        def get_formset(self, request, obj=None, **kwargs):
            formset = super().get_formset(request, obj=None, **kwargs)
            formset.validate_min = True
            return formset
    

    【讨论】:

    • 为什么这不是投票和接受最多的答案?这是一个非常干净的解决方案,我唯一要问的是如何自定义它读取的验证错误:请提交 {min_num} 或更多表单。
    【解决方案6】:

    截至 Django 3+ 这就像你喜欢的那样简单:

    class EmployeeAddressMap(admin.StackedInline):
        model = EmployeeAddress
        min_num = 1
        max_num = 1
        can_delete = False #specified that this cannnot be removed
    

    愉快的编码

    【讨论】:

      【解决方案7】:

      2022 年 3 月更新:

      "min_num" 可以使内联字段成为必需。 (内联字段默认不需要,默认显示3个不需要的内联字段)

      因此,如果您想要 2 个必需的内联字段,请设置 "min_num = 2" 如下所示,在这种情况下,将显示 2 个必需和 3 个非必需的内联字段,您可以添加更多不需要的内联字段内联字段,但您不能在表单中添加必需的内联字段:

      from django.contrib import admin
      from .models import Profile, UserProfile
      
      class ProfileInline(admin.TabularInline):
          model = Profile
          min_num = 2  # 2 required inline fields displayed
                       # 3 unrequired inline fields displayed by default
      
      @admin.register(UserProfile)
      class UserProfileAdmin(admin.ModelAdmin):
          inlines = [ProfileInline]
      

      另外,您可以使用“extra”设置显示非必填字段的数量,您可以使用“max_num”设置总内联的最大数量字段,包括必需和非必需的内联字段。因此,在下面的这种情况下,总内联字段的最大数量为 10 和 3 个必填字段,并显示 2 个不需要的内联字段,您最多可以添加 5 个不需要的内联字段,但不能在表单中添加必填内联字段:

      from django.contrib import admin
      from .models import Profile, UserProfile
      
      class ProfileInline(admin.TabularInline):
          model = Profile
          min_num = 3  # 3 required inline fields displayed
          extra = 2    # 2 unrequired inline fields displayed
          max_num = 10 # 10 inline fields as a maximum
      
      @admin.register(UserProfile)
      class UserProfileAdmin(admin.ModelAdmin):
          inlines = [ProfileInline]
      

      【讨论】:

        猜你喜欢
        • 2011-10-22
        • 2011-02-23
        • 2012-04-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-18
        • 2011-02-16
        • 1970-01-01
        相关资源
        最近更新 更多