如何在Django Admin中使用内联?

时间:2021-08-25 07:23:26

I have the following admin setup so that I can add/edit a user and their profile at the same time.

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

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)

The problem is I need two of the fields in the Profile inline form to be required when adding the user. The inline form doesn't validate unless input is entered. Is there anyway to make the inline required, so that it can't be left blank?

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

4 个解决方案

#1


29  

I took Carl's advice and made a much better implementation then the hack-ish one I mentioned in my comment to his answer. Here is my solution:

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

From my forms.py:

来自我的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

And the admin.py

和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)

This does exactly what I want, it makes the Profile inline formset validate. So since there are required fields in the profile form it will validate and fail if the required information isn't entered on the inline form.

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

#2


16  

Now with Django 1.7 you can use parameter min_num. You do not need class RequiredInlineFormSet anymore.

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

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

请参阅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)

#3


9  

You can probably do this, but you'll have to get your hands dirty in the formset/inline code.

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

First of all, I think you want there to be always one form in the formset in this case, and never more than one, so you'll want to set max_num=1 and extra=1 in your ProfileInline.

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

Your core problem is that BaseFormSet._construct_form passes empty_permitted=True to each "extra" (i.e. empty) form in the formset. This parameter tells the form to bypass validation if its unchanged. You just need to find a way to set empty_permitted=False for the form.

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

You can use your own BaseInlineFormset subclass in your inline, so that might help. Noticing that _construct_form takes **kwargs and allows that to override the kwargs passed to the individual Form instances, you could override _construct_forms in your Formset subclass and have it pass empty_permitted=False in every call to _construct_form. The downside there is that you're relying on internal APIs (and you'd have to rewrite _construct_forms).

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

Alternatively, you could try overriding the get_formset method on your ProfileInline, and after calling the parent's get_formset, manually poke at the form inside the returned formset:

或者,您可以尝试覆盖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

Play around and see what you can make work!

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

#4


7  

The easiest and most natural way to do that is via fomset clean():

最简单,最自然的方法是通过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

(Inspired by this Matthew Flanagan's snippet and Mitar's comment below, tested to work in Django 1.11 and 2.0).

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

#1


29  

I took Carl's advice and made a much better implementation then the hack-ish one I mentioned in my comment to his answer. Here is my solution:

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

From my forms.py:

来自我的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

And the admin.py

和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)

This does exactly what I want, it makes the Profile inline formset validate. So since there are required fields in the profile form it will validate and fail if the required information isn't entered on the inline form.

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

#2


16  

Now with Django 1.7 you can use parameter min_num. You do not need class RequiredInlineFormSet anymore.

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

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

请参阅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)

#3


9  

You can probably do this, but you'll have to get your hands dirty in the formset/inline code.

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

First of all, I think you want there to be always one form in the formset in this case, and never more than one, so you'll want to set max_num=1 and extra=1 in your ProfileInline.

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

Your core problem is that BaseFormSet._construct_form passes empty_permitted=True to each "extra" (i.e. empty) form in the formset. This parameter tells the form to bypass validation if its unchanged. You just need to find a way to set empty_permitted=False for the form.

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

You can use your own BaseInlineFormset subclass in your inline, so that might help. Noticing that _construct_form takes **kwargs and allows that to override the kwargs passed to the individual Form instances, you could override _construct_forms in your Formset subclass and have it pass empty_permitted=False in every call to _construct_form. The downside there is that you're relying on internal APIs (and you'd have to rewrite _construct_forms).

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

Alternatively, you could try overriding the get_formset method on your ProfileInline, and after calling the parent's get_formset, manually poke at the form inside the returned formset:

或者,您可以尝试覆盖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

Play around and see what you can make work!

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

#4


7  

The easiest and most natural way to do that is via fomset clean():

最简单,最自然的方法是通过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

(Inspired by this Matthew Flanagan's snippet and Mitar's comment below, tested to work in Django 1.11 and 2.0).

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