¿Cómo necesito una línea en el administrador de Django?
Pregunta
Tengo la siguiente configuración de administrador para poder agregar / editar un usuario y su perfil al mismo tiempo.
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)
El problema es que necesito que dos de los campos del formulario en línea del perfil sean obligatorios al agregar el usuario. El formulario en línea no se valida a menos que se ingrese la entrada. ¿Hay alguna forma de hacer que la línea sea requerida, para que no se pueda dejar en blanco?
Solución
Tomé el consejo de Carl e hice una implementación mucho mejor que la piratería que mencioné en mi comentario a su respuesta. Aquí está mi solución:
Desde mi formulario.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
Y el 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)
Esto hace exactamente lo que quiero, hace que se valide el formset en línea del perfil. Entonces, dado que hay campos obligatorios en el formulario de perfil, se validará y fallará si la información requerida no se ingresa en el formulario en línea.
Otros consejos
Ahora con Django 1.7 puede usar el parámetro min_num
. Ya no necesita la clase 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)
Probablemente puedas hacer esto, pero tendrás que ensuciarte las manos con el código de formset / inline.
En primer lugar, creo que desea que siempre haya una forma en el conjunto de formularios en este caso, y nunca más de una, así que querrá establecer max_num = 1 y extra = 1 en su ProfileInline.
Su problema principal es que BaseFormSet._construct_form pasa empty_permitido = Verdadero a cada " extra " (es decir, vacío) forma en el formset. Este parámetro le dice al formulario que omita la validación si no se modifica. Solo necesita encontrar una manera de establecer empty_permitted = False para el formulario.
Puede usar su propia subclase BaseInlineFormset en su en línea, por lo que podría ayudar. Al darse cuenta de que _construct_form toma ** kwargs y permite anular los kwargs pasados ??a las instancias de Formularios individuales, podría anular _construct_forms en su subclase de Formset y hacer que pase empty_permitted = False en cada llamada a _construct_form. La desventaja es que confías en las API internas (y tendrías que volver a escribir _construct_forms).
Como alternativa, puede intentar anular el método get_formset en su ProfileInline y, después de llamar a get_formset de los padres, presione manualmente el formulario dentro del formset devuelto:
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
¡Juega y ve lo que puedes hacer funcionar!
La forma más fácil y natural de hacerlo es a través de 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
(Inspirado por este Mateo El fragmento de Flanagan y el comentario de Mitar a continuación, probados para funcionar en Django 1.11 y 2.0).
Debes configurar min_num en línea y validate_min en formset.
https://docs.djangoproject.com/ es / 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