Come posso richiedere un inline in Django Admin?
Domanda
Ho la seguente configurazione dell'amministratore in modo da poter aggiungere / modificare un utente e il suo profilo contemporaneamente.
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)
Il problema è che ho bisogno di due dei campi nel modulo inline Profile per essere richiesti quando si aggiunge l'utente. Il modulo in linea non viene convalidato se non viene immesso un input. Esiste un modo per rendere inline richiesto, in modo che non possa essere lasciato in bianco?
Soluzione
Ho seguito il consiglio di Carl e ho realizzato un'implementazione molto migliore di quella hack che ho citato nel mio commento alla sua risposta. Ecco la mia soluzione:
Dal mio form.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
E 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)
Fa esattamente quello che voglio, convalida il formset inline Profile. Pertanto, poiché nel modulo del profilo sono presenti campi obbligatori, verrà convalidato e non riuscirà se le informazioni richieste non vengono immesse nel modulo incorporato.
Altri suggerimenti
Ora con Django 1.7 puoi usare il parametro min_num
. Non è più necessaria la classe 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)
Probabilmente puoi farlo, ma dovrai sporcarti le mani nel codice formset / inline.
Prima di tutto, penso che tu voglia che ci sia sempre una forma nel formset in questo caso, e mai più di una, quindi ti consigliamo di impostare max_num = 1 e extra = 1 nella tua ProfileInline.
Il tuo problema principale è che BaseFormSet._construct_form passa empty_permitted = Vero per ogni " extra " (ovvero vuoto) nel formset. Questo parametro indica al modulo di ignorare la convalida se è invariato. Devi solo trovare un modo per impostare empty_permitted = False per il modulo.
Puoi utilizzare la tua sottoclasse BaseInlineFormset nella tua in linea, in modo che possa aiutare. Notando che _construct_form accetta ** kwargs e consente di sovrascrivere i kwarg passati alle singole istanze di Form, è possibile sovrascrivere _construct_forms nella sottoclasse Formset e far passare vuoto_permit = Falso in ogni chiamata a _construct_form. Il rovescio della medaglia è che stai facendo affidamento su API interne (e dovresti riscrivere _construct_forms).
In alternativa, puoi provare a sovrascrivere il metodo get_formset su ProfileInline e, dopo aver chiamato get_formset del genitore, frugare manualmente nel modulo all'interno del formset restituito:
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
Gioca e vedi cosa riesci a far funzionare!
Il modo più semplice e naturale per farlo è tramite 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
(Ispirato da questo Matthew Lo snippet di Flanagan e il commento di Mitar sotto, testati per funzionare in Django 1.11 e 2.0).
Devi impostare min_num in linea e validate_min nel formset.
https://docs.djangoproject.com/ it / 1.8 / argomenti / forme / 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