Как мне потребовать встроенный в Django 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)
Проблема в том, что при добавлении пользователя необходимо заполнить два поля в встроенной форме профиля. Встроенная форма не проверяется, если ввод не введен. Есть ли какой-либо способ сделать строку обязательной, чтобы ее нельзя было оставить пустой?
Решение
Я воспользовался советом Карла и сделал намного лучшую реализацию, чем хакерский, который я упомянул в своем комментарии к его ответу. Вот мое решение:
Из моего 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
.
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