Вопрос

У меня есть следующие настройки администратора, чтобы я мог добавлять / редактировать пользователя и его профиль одновременно.

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)

Проблема в том, что при добавлении пользователя необходимо заполнить два поля в встроенной форме профиля. Встроенная форма не проверяется, если ввод не введен. Есть ли какой-либо способ сделать строку обязательной, чтобы ее нельзя было оставить пустой?

Это было полезно?

Решение

Я воспользовался советом Карла и сделал намного лучшую реализацию, чем хакерский, который я упомянул в своем комментарии к его ответу. Вот мое решение:

Из моего 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)

Это именно то, что я хочу, это делает проверку встроенного набора профилей. Таким образом, поскольку в форме профиля есть обязательные поля, он будет проверяться и завершаться ошибкой, если в встроенную форму не будет введена необходимая информация.

Другие советы

Теперь с 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)

Возможно, вы можете сделать это, но вам придется запачкать руки в форме набора / встроенном коде.

Прежде всего, я думаю, вы хотите, чтобы в этом случае в наборе форм всегда была одна форма, а не больше одной, поэтому вам нужно установить max_num = 1 и extra = 1 в вашем ProfileInline.

Основная проблема заключается в том, что BaseFormSet._construct_form передает empty_permitted = True для каждого "дополнительного" (то есть пустой) формы в форме. Этот параметр указывает форме обойти проверку, если она не изменилась. Вам просто нужно найти способ установить empty_permitted = False для формы.

Вы можете использовать свой собственный подкласс BaseInlineFormset в своем встроенный, так что это может помочь. Заметив, что _construct_form принимает ** kwargs и позволяет переопределить kwargs, передаваемые отдельным экземплярам Form, вы можете переопределить _construct_forms в своем подклассе Formset и передать его empty_permitted = False при каждом вызове _construct_form. Недостатком является то, что вы полагаетесь на внутренние API (и вам придется переписывать _construct_forms).

В качестве альтернативы, вы можете попробовать переопределить метод get_formset в своей ProfileInline, и после вызова 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

Поиграйте и посмотрите, на что вы способны!

Самый простой и естественный способ сделать это - использовать 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

(Вдохновленный этим Мэтью Фрагмент Фланагана и комментарий Митара ниже, проверенный на работу в Django 1.11 и 2.0).

Вам нужно установить min_num во встроенном режиме и validate_min в наборе форм.

https://docs.djangoproject.com/ ан / 1,8 / темы / формы / FormSets / # Validate-мин

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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top