29

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

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)

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

4

6 回答 6

33

我接受了 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 内联表单集得到验证。因此,由于配置文件表单中有必填字段,如果未在内联表单中输入必填信息,它将验证并失败。

于 2009-08-05T14:32:01.963 回答
29

现在使用 Django 1.7,您可以使用 parameter 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)
于 2015-05-17T16:35:56.417 回答
9

您可能可以这样做,但您必须在表单集/内联代码中弄脏您的手。

首先,我认为在这种情况下,您希望表单集中始终存在一个表单,并且永远不会超过一个,因此您需要在 ProfileInline 中设置max_num =1 和extra =1。

您的核心问题是BaseFormSet._construct_form 将 empty_permitted=True 传递给表单集中的每个“额外”(即空)表单。此参数告诉表单如果未更改则绕过验证。您只需要找到一种方法来为表单设置 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 后,手动戳返回的表单集中的表单:

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

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

于 2009-07-31T00:27:19.523 回答
8

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

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

于 2010-02-04T12:10:05.607 回答
6

您需要在 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
于 2018-12-20T11:49:15.020 回答
2

从 Django 3+ 开始,这很简单:

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

快乐编码

于 2020-11-04T20:56:52.883 回答