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)
問題は、ユーザーを追加するときにプロファイルインラインフォームの2つのフィールドが必要になることです。入力が入力されない限り、インラインフォームは検証されません。とにかくインラインを必須にして、空白のままにできないようにしますか?
解決
私はCarlのアドバイスに従い、彼の答えに対するコメントで述べたハックっぽい実装よりもずっと良い実装をしました。ここに私の解決策があります:
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)
おそらくこれを行うことができますが、formset / inlineコードで手を汚す必要があります。
まず、この場合、formsetには常に1つのフォームが存在し、複数のフォームは存在しないようにする必要があるため、 max_num = 1および extra = 1。
主な問題は、 BaseFormSet._construct_formがempty_permittedを渡すことです= を各" extra"に対して(つまり空)formset内のフォーム。このパラメーターは、変更されていない場合、検証をバイパスするようにフォームに指示します。フォームに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を呼び出した後、返された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で動作するようにテストされたFlanaganのスニペットとMitarの以下のコメント。
min_num をインラインで、 validate_min をフォームセットで設定する必要があります。
https://docs.djangoproject.com/ ja / 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