Wie fordere ich ein Inline in der Django Admin?
Frage
ich folgende Admin-Setup haben, so dass ich hinzufügen / bearbeiten Sie einen Benutzer und sein Profil zugleich.
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)
Das Problem ist, ich brauche zwei der Felder im Profil Inline-Form benötigt werden, wenn der Benutzer das Hinzufügen. Die Inline-Form überprüft nicht, es sei denn Eingang eingegeben wird. Gibt es trotzdem, die Inline erforderlich zu machen, so dass es nicht leer gelassen werden kann?
Lösung
Ich nahm Carl Rat und machte eine viel bessere Umsetzung dann den Hack-ish, die ich auf seine Antwort in meinem Kommentar erwähnt. Hier ist meine Lösung:
Aus meiner 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
Und die 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)
Das ist genau das, was ich will, es macht das Profil Inline formset Validieren. Also da gibt es Felder in der Profilform erforderlich sind, wird es bestätigen und fehlschlagen, wenn die erforderlichen Informationen nicht auf dem Inline-Formular eingegeben werden.
Andere Tipps
Jetzt mit Django 1.7 können Sie Parameter min_num
verwenden. Sie haben keine Klasse RequiredInlineFormSet
mehr benötigen.
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)
Sie können dies wahrscheinlich tun, aber Sie werden Ihre Hände schmutzig in dem formset / Inline-Code bekommen.
Zunächst einmal, ich glaube, Sie wollen es immer eine Form in der formset in diesem Fall sein, und nie mehr als ein, so dass Sie setzen wollen? MAX_NUM = 1 und Extra = 1 in Ihrem ProfileInline.
Ihr Kernproblem ist, dass BaseFormSet._construct_form geht empty_permitted = True zu jeder "extra" (dh leer) Form in der formset. Dieser Parameter teilt das Formularvalidierung, wenn seine unverändert zu umgehen. Sie müssen nur einen Weg finden, empty_permitted = False für das Formular festgelegt.
Alternativ könnten Sie versuchen, die get_formset Methode auf Ihrem ProfileInline überschreiben, und nach den Eltern get_formset Aufruf steckt manuell an der Form innerhalb des zurück 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
Spielen Sie herum und sehen, was Sie zur Arbeit machen!
Die einfachste und natürlichste Art und Weise zu tun, ist über 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
(inspiriert von dieser Matthew Flanagans Schnipsel und Mitar Kommentar unten, getestet arbeiten in Django 1.11 und 2.0).
Sie müssen auf min_num in Inline- und validate_min in formset.
https://docs.djangoproject.com/ de / 1.8 / Themen / Formulare / Formularsätze / # 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