如何在Django Admin中输入内联?

AJ.*_*AJ. 26 django inline admin

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

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)
Run Code Online (Sandbox Code Playgroud)

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

AJ.*_*AJ. 30

我接受了卡尔的建议并做了一个更好的实施,然后在我对他的回答的评论中提到了一个黑客.这是我的解决方案:

来自我的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
Run Code Online (Sandbox Code Playgroud)

和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)
Run Code Online (Sandbox Code Playgroud)

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

  • 如果您使用`GenericInlineModelAdmin`,请将`BaseInlineFormSet`替换为`BaseGenericInlineFormSet`. (2认同)

qui*_*ick 22

现在使用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)
Run Code Online (Sandbox Code Playgroud)

  • 实际上,如果内联有一个必需的字段,它也控制内联本身被填充. (5认同)
  • 在1.10中进行测试,即使设置了"min_num",仍然允许用户在所有记录上选择"删除",不会留下任何记录. (4认同)

Car*_*yer 9

你可以这样做,但你必须在formset /内联代码中弄脏你的手.

首先,我认为你希望在这种情况下总是在formset中有一个表单,并且永远不会超过一个,所以你需要在你的ProfileInline中设置max_num = 1和extra = 1.

您的核心问题是BaseFormSet._construct_form将empty_permitted = True传递给formset中的每个"extra"(即空)表单.此参数告诉表单如果未更改则绕过验证.您只需要找到一种方法为表单设置empty_permitted = False.

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

或者,您可以尝试覆盖ProfileInline上的get_formset方法,并在调用父级的get_formset之后,手动查看返回的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
Run Code Online (Sandbox Code Playgroud)

到处玩,看看你能做些什么!


un1*_*n1t 9

您需要在 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
Run Code Online (Sandbox Code Playgroud)


mrt*_*rts 7

最简单,最自然的方法是通过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
Run Code Online (Sandbox Code Playgroud)

(受到Matthew Flanagan的片段和Mitar在下面的评论的启发,测试在Django 1.11和2.0中工作).

  • 完善!我改变了一点,使用`if not self.is_valid():`而不是手动浏览`self.errors`并使用`self.model._meta.verbose_name`. (2认同)