Как мне потребовать встроенный администратор Django?
У меня есть следующая настройка администратора, так что я могу добавлять/редактировать пользователя и их профиль одновременно.
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)
Проблема в том, что при добавлении пользователя мне нужно два поля в встроенной форме профиля. Встроенная форма не проверяется, если вход не введен. В любом случае, чтобы сделать встроенную строку, чтобы она не оставалась пустой?
Ответы
Ответ 1
Я взял совет Карла и сделал гораздо более эффективную реализацию, чем тот, который я упомянул в своем комментарии к его ответу. Вот мое решение:
Из моих 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)
Это делает именно то, что я хочу, это делает проверку встроенного formet профиля. Так как в форме профиля есть обязательные поля, он будет проверять и терпеть неудачу, если требуемая информация не будет введена во встроенную форму.
Ответ 2
Теперь с 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)
Ответ 3
Возможно, вы можете это сделать, но вам придется замарать руки в код формы/встроенного кода.
Прежде всего, я думаю, вы хотите, чтобы в этом случае всегда была одна форма в наборе форм и не более одного, поэтому вам нужно установить max_num= 1 и extra= 1 в вашей профильной строке.
Основная проблема заключается в том, что 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
Поиграйте и посмотрите, что вы можете сделать!
Ответ 4
Самый простой и естественный способ сделать это через 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).
Ответ 5
Вам нужно установить min_num во встроенном и validate_min в formset.
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