Si estás creando una aplicación basada en bases de datos, es probable que tengas formularios que se ajusten muy bien a los modelos Django. Por ejemplo, podrías tener un modelo BlogComment y querer crear un formulario que permita a las personas enviar comentarios. En este caso, sería redundante definir el tipo de campo en tu formulario, ya que has definido los campos en tu modelo.
Django proporciona una clase de ayuda que te permite crear una clase Form desde un modelo de Django.
Por ejemplo:
>>> from django.forms import ModelForm
>>> from myapp.models import Article
# Create the form class.
>>> class ArticleForm(ModelForm):
... class Meta:
... model = Article
... fields = ["pub_date", "headline", "content", "reporter"]
...
# Creating a form to add an article.
>>> form = ArticleForm()
# Creating a form to change an existing article.
>>> article = Article.objects.get(pk=1)
>>> form = ArticleForm(instance=article)
La clase generada Form tendrá un campo de formulario para cada campo del modelo especificado, en el orden especificado en la atributo fields.
Cada campo de modelo tiene un campo de formulario correspondiente por defecto. Por ejemplo, un CharField en un modelo se representa como un CharField en un formulario. Un campo de modelo ManyToManyField se representa como un MultipleChoiceField. Aquí está la lista completa de conversión:
Campo del modelo |
Campo de formulario |
|---|---|
|
Los textos traducidos son: |
|
Los textos traducidos son: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:class>`CampoDirecciónIPGenérica` |
|
:class>`CampoJSON` |
|
:class>`CampoMuchosAMuchos` |
|
:class>`CampoEnteroPositivoBigInteger` |
|
|
|
|
|
|
|
|
Los textos traducidos son: |
|
|
|
|
|
|
|
|
|
|
Como podrías esperar, los tipos de campo de modelo ForeignKey y ManyToManyField son casos especiales:
ForeignKey se representa por django.forms.ModelChoiceField, que es un Campo de elección cuyas opciones son una consulta de modelos QuerySet.
ManyToManyField se representa por django.forms.ModelMultipleChoiceField, que es un Campo de elección múltiple cuyas opciones son una consulta de modelos QuerySet.
Además, cada campo del formulario generado tiene atributos configurados como sigue:
Si el campo de modelo tiene blank=True, entonces se establece required en False en el campo del formulario. De lo contrario, required=True.
El etiqueta del campo del formulario se establece en el verbose_name del campo de modelo, con la primera letra mayúscula.
El campo del formulario tiene su help_text establecido en el help_text del campo del modelo.
Si el campo del modelo tiene choices configurado, entonces el campo del formulario tendrá su widget establecido en Select, con opciones que provienen de las choices del campo del modelo. Las opciones incluirán normalmente la elección vacía que se selecciona por defecto. Si el campo es requerido, esto fuerza al usuario a hacer una selección. La elección vacía no estará incluida si el campo del modelo tiene blank=False y un valor default explícito (el valor default se seleccionará inicialmente en su lugar).
Finalmente, tenga en cuenta que puede sobrescribir el campo del formulario utilizado para un campo del modelo determinado. Consulte Sobreescribiendo los campos por defecto a continuación.
Consideren este conjunto de modelos:
from django.db import models
from django.forms import ModelForm
TITLE_CHOICES = {
"MR": "Mr.",
"MRS": "Mrs.",
"MS": "Ms.",
}
class Author(models.Model):
name = models.CharField(max_length=100)
title = models.CharField(max_length=3, choices=TITLE_CHOICES)
birth_date = models.DateField(blank=True, null=True)
def __str__(self):
return self.name
class Book(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ["name", "title", "birth_date"]
class BookForm(ModelForm):
class Meta:
model = Book
fields = ["name", "authors"]
Con estos modelos, las clases ModelForm anteriores serían aproximadamente equivalentes a esto (la única diferencia siendo el método save(), que discutiremos en un momento.):
from django import forms
class AuthorForm(forms.Form):
name = forms.CharField(max_length=100)
title = forms.CharField(
max_length=3,
widget=forms.Select(choices=TITLE_CHOICES),
)
birth_date = forms.DateField(required=False)
class BookForm(forms.Form):
name = forms.CharField(max_length=100)
authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
ModelForm¶Hay dos pasos principales involucrados en la validación de un ModelForm:
Al igual que la validación normal de formularios, la validación de modelos se desencadena implícitamente cuando se llama a is_valid() o se accede al atributo errors y explícitamente cuando se llama a full_clean(), aunque generalmente no utilizará el último método en la práctica.
La validación de modelos (Model.full_clean()) se desencadena desde dentro del paso de validación de la forma, justo después de que se llama al método clean() de la forma.
Advertencia
El proceso de limpieza modifica la instancia de modelo pasada a la constructora de ModelForm de diversas maneras. Por ejemplo, cualquier campo de fecha del modelo se convierte en objetos de fecha reales. La validación fallida puede dejar la instancia de modelo subyacente en un estado inconsistente y por lo tanto no se recomienda su reutilización.
clean()¶Puedes sobrescribir el método clean() en una forma de modelo para proporcionar validación adicional del mismo modo que puedes hacerlo con una forma normal.
Una instancia de forma de modelo asociada a un objeto de modelo contendrá un atributo instance que da acceso a sus métodos a esa instancia de modelo específica.
Advertencia
El método ModelForm.clean() establece una bandera que hace que el paso de validación de modelos (validación de objetos) valide la unicidad de los campos de modelo marcados como unique, unique_together o unique_for_date|month|year.
Si deseas sobrescribir el método clean() y mantener esta validación, debes llamar al método clean() de la clase padre.
Como parte del proceso de validación, ModelForm llamará al método clean() de cada campo de tu modelo que tenga un campo correspondiente en tu forma. Si has excluido cualquier campo de modelo, no se realizarán comprobaciones de validación en esos campos. Consulta la documentación de validación de formas para obtener más información sobre cómo funcionan la limpieza y la validación de campos.
Se llamará al método clean() del modelo antes de realizar cualquier comprobaciones de unicidad. Consulta Validar objetos para obtener más información sobre el hook de limpieza del modelo.
error_messages del modelo¶Los mensajes de error definidos a nivel de campo de formulario (form field) o a nivel de Meta del formulario siempre tienen prioridad sobre los mensajes de error definidos a nivel de campo del modelo (model field).
Los mensajes de error definidos en los campos del modelo (model fields) se utilizan solo cuando se levanta una ValidationError durante el paso de validación del modelo y no hay mensajes de error correspondientes definidos a nivel de formulario.
Puedes sobreescribir los mensajes de error de NON_FIELD_ERRORS levantados por la validación del modelo agregando la clave NON_FIELD_ERRORS al diccionario error_messages del interior de la clase Meta del formulario ModelForm:
from django.core.exceptions import NON_FIELD_ERRORS
from django.forms import ModelForm
class ArticleForm(ModelForm):
class Meta:
error_messages = {
NON_FIELD_ERRORS: {
"unique_together": "%(model_name)s's %(field_labels)s are not unique.",
}
}
save()¶Cada formulario ModelForm también tiene un método save(). Este método crea y guarda una objeto de base de datos a partir de los datos vinculados al formulario. Una subclase de ModelForm puede aceptar una instancia de modelo existente como argumento de palabra clave instance; si se proporciona, save() actualizará esa instancia. Si no se proporciona, save() creará una nueva instancia del modelo especificado:
>>> from myapp.models import Article
>>> from myapp.forms import ArticleForm
# Create a form instance from POST data.
>>> f = ArticleForm(request.POST)
# Save a new Article object from the form's data.
>>> new_article = f.save()
# Create a form to edit an existing Article, but use
# POST data to populate the form.
>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(request.POST, instance=a)
>>> f.save()
Ten en cuenta que si el formulario no ha sido validado, llamar a save() lo hará por verificar form.errors. Se levantará un ValueError si los datos del formulario no se validan – es decir, si form.errors evalúa a True.
Si un campo opcional no aparece en los datos del formulario, la instancia de modelo resultante utiliza el campo del modelo default, si existe, para ese campo. Este comportamiento no se aplica a campos que utilizan CheckboxInput, CheckboxSelectMultiple o SelectMultiple (o cualquier widget personalizado cuya método value_omitted_from_data() siempre devuelve False) ya que un checkbox no marcado y una <select multiple> no seleccionada no aparecen en los datos de una solicitud HTML. Utiliza un campo de formulario o widget personalizado si estás diseñando una API y quieres el comportamiento de retroceso por defecto para un campo que utiliza uno de estos widgets.
Este método save() acepta un argumento de palabra clave opcional commit, que puede ser True o False. Si llamas a save() con commit=False, entonces devolverá un objeto que aún no ha sido guardado en la base de datos. En este caso, es responsabilidad tuya llamar a save() en la instancia de modelo resultante. Esto es útil si quieres hacer procesamiento personalizado sobre el objeto antes de guardarla o si quieres utilizar una de las opciones de guardado de modelos. commit es True por defecto.
Otra consecuencia secundaria del uso de commit=False se ve cuando tu modelo tiene una relación muchos-a-muchos con otro modelo. Si tu modelo tiene una relación muchos-a-muchos y especificas commit=False al guardar un formulario, Django no puede guardar inmediatamente los datos del formulario para la relación muchos-a-muchos. Esto es porque no es posible guardar datos muchos-a-muchos para un instante hasta que el instante exista en la base de datos.
To trabajar alrededor de este problema, cada vez que guardas una forma utilizando commit=False, Django agrega un método save_m2m() a tu subclase de ModelForm. Después de haber guardado manualmente la instancia producida por la forma, puedes invocar save_m2m() para guardar los datos many-to-many.
# Create a form instance with POST data.
>>> f = AuthorForm(request.POST)
# Create, but don't save the new author instance.
>>> new_author = f.save(commit=False)
# Modify the author in some way.
>>> new_author.some_field = "some_value"
# Save the new instance.
>>> new_author.save()
# Now, save the many-to-many data for the form.
>>> f.save_m2m()
Llamar a save_m2m() es solo necesario si utilizas save(commit=False). Cuando usas un save() en una forma, todos los datos – incluyendo los muchos-a-muchos – se guardan sin la necesidad de ninguna llamada adicional al método. Por ejemplo:
# Create a form instance with POST data.
>>> a = Author()
>>> f = AuthorForm(request.POST, instance=a)
# Create and save the new author instance. There's no need to do anything else.
>>> new_author = f.save()
Otra que las save() y save_m2m() métodos, un ModelForm funciona exactamente igual que cualquier otra forma de forms. Por ejemplo, el método is_valid() se utiliza para verificar la validez, el método is_multipart() se utiliza para determinar si una forma requiere subida de archivos multipart (y por lo tanto si request.FILES debe ser pasado a la forma), etc. Consulta Asociar archivos subidos a un formulario para obtener más información.
Se recomienda fuertemente que establezcas explícitamente todos los campos que deberían editarse en la forma utilizando el atributo fields. El fracaso a hacerlo puede llevar fácilmente a problemas de seguridad cuando una forma permite sorpresivamente a un usuario establecer ciertos campos, especialmente cuando se agregan nuevos campos a un modelo. Dependiendo de cómo se renderice la forma, el problema puede no ser visible en la página web.
La alternativa sería incluir todos los campos automáticamente o eliminar solo algunos. Esta aproximación fundamental es conocida como mucho menos segura y ha llevado a explotaciones graves en sitios web importantes (por ejemplo, GitHub).
Sin embargo, hay dos atajos disponibles para casos donde puedes garantizar que estas preocupaciones de seguridad no se apliquen a ti:
Establece el atributo fields en el valor especial '__all__' para indicar que todos los campos del modelo deben utilizarse. Por ejemplo:
from django.forms import ModelForm
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = "__all__"
Establece el atributo exclude de la clase Meta interna del ModelForm a una lista de campos que se excluirán de la forma.
Por ejemplo:
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
exclude = ["title"]
Dado que el modelo Author tiene los 3 campos name, title y birth_date, esto dará como resultado que los campos name y birth_date estén presentes en la forma.
Si se utilizan alguno de estos, el orden en que aparecen los campos en la forma será el mismo orden en que están definidos en el modelo, con las instancias de ManyToManyField apareciendo última.
Además, Django aplica la siguiente regla: si estableces editable=False en el campo del modelo, cualquier formulario creado a partir del modelo mediante ModelForm no incluirá ese campo.
Nota
Cualquier campo que no esté incluido en una forma según la lógica anterior no se establecerá por el método save() de la forma. Además, si agregas manualmente los campos excluidos a la forma, no se inicializarán desde la instancia del modelo.
Django evitará cualquier intento de guardar un modelo incompleto, por lo que si el modelo no permite que los campos faltantes sean vacíos y no proporciona un valor predeterminado para los campos faltantes, cualquier intento de save() un ModelForm con campos faltantes fallará. Para evitar esta falla, debes instanciar tu modelo con valores iniciales para los campos faltantes pero requeridos:
author = Author(title="Mr")
form = PartialAuthorForm(request.POST, instance=author)
form.save()
Alternativamente, puedes utilizar save(commit=False) y establecer manualmente cualquier campo adicional requerido:
form = PartialAuthorForm(request.POST)
author = form.save(commit=False)
author.title = "Mr"
author.save()
Consulte la sección sobre guardar formularios para obtener más detalles sobre el uso de save(commit=False).
Los tipos de campo por defecto, tal como se describe en la tabla Tipos de campo anterior, son valores predeterminados sensatos. Si tienes un DateField en tu modelo, es probable que desees que ese campo esté representado como un DateField en tu formulario. Pero ModelForm te da la flexibilidad de cambiar el campo de formulario para un campo del modelo determinado.
Para especificar un widget personalizado para un campo, utiliza la propiedad widgets de la clase Meta interna. Esto debe ser un diccionario que mapee nombres de campos a clases o instancias de widgets.
Por ejemplo, si deseas que el campo CharField del atributo name de Author esté representado por un <textarea> en lugar del <input type="text"> predeterminado, puedes sobreescribir el widget del campo:
from django.forms import ModelForm, Textarea
from myapp.models import Author
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ["name", "title", "birth_date"]
widgets = {
"name": Textarea(attrs={"cols": 80, "rows": 20}),
}
La widgets dictionary acepta instancias de widget (por ejemplo, Textarea(...)) o clases (por ejemplo, Textarea). Ten en cuenta que la widgets dictionary se ignora para un campo de modelo con una atributo choices no vacío. En este caso, debes sobrescribir el campo del formulario para utilizar un widget diferente.
De manera similar, puedes especificar los atributos labels, help_texts y error_messages de la clase interna Meta si deseas personalizar aún más un campo.
Por ejemplo, si querías personalizar el texto de todas las cadenas que se enfrentan a los usuarios para el campo name:
from django.utils.translation import gettext_lazy as _
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ["name", "title", "birth_date"]
labels = {
"name": _("Writer"),
}
help_texts = {
"name": _("Some useful help text."),
}
error_messages = {
"name": {
"max_length": _("This writer's name is too long."),
},
}
También puedes especificar field_classes o formfield_callback para personalizar el tipo de campos instanciados por el formulario.
Por ejemplo, si deseabas utilizar MySlugFormField para el campo slug, podrías hacer lo siguiente:
from django.forms import ModelForm
from myapp.models import Article
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ["pub_date", "headline", "content", "reporter", "slug"]
field_classes = {
"slug": MySlugFormField,
}
o:
from django.forms import ModelForm
from myapp.models import Article
def formfield_for_dbfield(db_field, **kwargs):
if db_field.name == "slug":
return MySlugFormField()
return db_field.formfield(**kwargs)
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ["pub_date", "headline", "content", "reporter", "slug"]
formfield_callback = formfield_for_dbfield
Finalmente, si deseas tener control completo sobre un campo – incluyendo su tipo, validadores, requerido, etc. – puedes hacer esto declarando campos como harías en un formulario regular.
Si deseas especificar los validadores de un campo, puedes hacerlo definiendo el campo de manera declarativa y estableciendo su parámetro validators:
from django.forms import CharField, ModelForm
from myapp.models import Article
class ArticleForm(ModelForm):
slug = CharField(validators=[validate_slug])
class Meta:
model = Article
fields = ["pub_date", "headline", "content", "reporter", "slug"]
Nota
Cuando instancias explícitamente un campo del formulario de esta manera, es importante entender cómo se relacionan ModelForm y la forma regular.
ModelForm es una forma regular que puede generar automáticamente ciertos campos. Los campos que se generan automáticamente dependen del contenido de la clase Meta y de los campos que ya han sido definidos de manera declarativa. Basicamente, ModelForm solo generará campos que estén faltando en el formulario, o en otras palabras, campos que no hayan sido definidos de manera declarativa.
Los campos definidos declarativamente se dejan tal cual, por lo tanto cualquier personalización realizada en las propiedades de Meta como widgets, labels, help_texts o error_messages son ignoradas; solo aplican a los campos que se generan automáticamente.
De manera similar, los campos definidos declarativamente no obtienen sus atributos como max_length o required del modelo correspondiente. Si deseas mantener el comportamiento especificado en el modelo, debes establecer explícitamente los argumentos relevantes cuando declares el campo de formulario.
Por ejemplo, si el modelo Article se ve así:
class Article(models.Model):
headline = models.CharField(
max_length=200,
null=True,
blank=True,
help_text="Use puns liberally",
)
content = models.TextField()
y quieres realizar alguna validación personalizada para headline, mientras mantienes las propiedades blank y help_text como especificadas, podrías definir ArticleForm de la siguiente manera:
class ArticleForm(ModelForm):
headline = MyFormField(
max_length=200,
required=False,
help_text="Use puns liberally",
)
class Meta:
model = Article
fields = ["headline", "content"]
Debes asegurarte de que el tipo del campo de formulario pueda usarse para establecer los contenidos del campo correspondiente del modelo. Cuando no son compatibles, obtendrás un ValueError ya que no se produce ninguna conversión implícita.
Consultar la documentación sobre campos y argumentos <ref/forms/fields> para obtener más información.
Por defecto, los campos en un ModelForm no localizarán sus datos. Para habilitar la localización de campos, puedes utilizar la propiedad localized_fields en la clase Meta.
>>> from django.forms import ModelForm
>>> from myapp.models import Author
>>> class AuthorForm(ModelForm):
... class Meta:
... model = Author
... localized_fields = ['birth_date']
Si se establece localized_fields en el valor especial '__all__', todos los campos serán localizados.
Puedes extender y reutilizar clases ModelForm básicas heredándolas. Esto es útil si necesitas declarar campos o métodos adicionales en una clase padre para su uso en varias formas derivadas de modelos. Por ejemplo, utilizando la clase ArticleForm anterior:
>>> class EnhancedArticleForm(ArticleForm):
... def clean_pub_date(self): ...
...
Esto crea un formulario que se comporta exactamente igual que ArticleForm, excepto que hay algunas validaciones y limpiezas adicionales para el campo pub_date.
También puedes heredar la clase Meta interna del padre si deseas cambiar las listas Meta.fields o Meta.exclude:
>>> class RestrictedArticleForm(EnhancedArticleForm):
... class Meta(ArticleForm.Meta):
... exclude = ["body"]
...
Esto agrega el método extra de la clase EnhancedArticleForm y modifica el original ArticleForm.Meta para eliminar un campo.
Hay algunas cosas que tener en cuenta, sin embargo.
Aplican las reglas normales de resolución de nombres de Python. Si tienes varias clases base que declaran una clase Meta interna, solo se utilizará la primera. Esto significa que el Meta del hijo, si existe, o lo del primer padre, etc.
Es posible heredar tanto de Form como de ModelForm simultáneamente, sin embargo, debes asegurarte de que ModelForm aparezca primero en la jerarquía de resolución de métodos. Esto es porque estas clases dependen de diferentes metaclasses y una clase solo puede tener un metaclass.
Es posible declarar explícitamente eliminar un campo Field heredado de una clase base estableciendo el nombre a None en la subclase.
Solo puedes utilizar esta técnica para excluirse de un campo definido declarativamente por una clase base; no impedirá que el metaclasses ModelForm genere un campo predeterminado. Para excluir campos predeterminados, consulta Seleccionando los campos a utilizar.
Así como los formularios regulares, es posible especificar datos iniciales para formularios mediante la especificación de un parámetro initial cuando se instancia el formulario. Los valores iniciales proporcionados de esta manera sobrescribirán tanto los valores iniciales del campo del formulario como los valores de una instancia de modelo adjunta.
>>> article = Article.objects.get(pk=1)
>>> article.headline
'My headline'
>>> form = ArticleForm(initial={"headline": "Initial headline"}, instance=article)
>>> form["headline"].value()
'Initial headline'
Puedes crear formularios a partir de un modelo dado utilizando la función independiente modelform_factory(), en lugar de utilizar una definición de clase. Esto puede ser más conveniente si no tienes muchas personalizaciones que hacer:
>>> from django.forms import modelform_factory
>>> from myapp.models import Book
>>> BookForm = modelform_factory(Book, fields=["author", "title"])
También se puede utilizar para realizar modificaciones a formularios existentes, por ejemplo, especificando los widgets a utilizar para un campo determinado:
>>> from django.forms import Textarea
>>> Form = modelform_factory(Book, form=BookForm, widgets={"title": Textarea()})
Los campos a incluir se pueden especificar utilizando las argumentos de palabra clave fields y exclude, o las correspondientes atributos en la clase interna Meta del formulario ModelForm. Consulte la documentación sobre el Seleccionando los campos a utilizar de ModelForm.
… o habilitar la localización para campos específicos:
>>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=["birth_date"])
Al igual que los formularios regulares, Django proporciona una serie de clases de formset mejoradas para hacer más conveniente el trabajo con modelos de Django. Vamos a reutilizar el modelo Author anterior:
>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
La restricción de campos mediante fields limita el formset a utilizar solo los campos dados. Alternativamente, puedes adoptar un enfoque «opt-out», especificando los campos que deseas excluir:
>>> AuthorFormSet = modelformset_factory(Author, exclude=["birth_date"])
Esto creará un formset capaz de trabajar con los datos asociados al modelo Author. Funciona exactamente como un formset regular:
>>> formset = AuthorFormSet()
>>> print(formset)
<input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS"><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS"><input type="hidden" name="form-MIN_NUM_FORMS" value="0" id="id_form-MIN_NUM_FORMS"><input type="hidden" name="form-MAX_NUM_FORMS" value="1000" id="id_form-MAX_NUM_FORMS">
<div><label for="id_form-0-name">Name:</label><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100"></div>
<div><label for="id_form-0-title">Title:</label><select name="form-0-title" id="id_form-0-title">
<option value="" selected>---------</option>
<option value="MR">Mr.</option>
<option value="MRS">Mrs.</option>
<option value="MS">Ms.</option>
</select><input type="hidden" name="form-0-id" id="id_form-0-id"></div>
Nota
modelformset_factory() utiliza formset_factory() para generar formularios. Esto significa que un formulario de modelo es una extensión de un formulario básico que sabe interactuar con un modelo en particular.
Nota
Cuando se utiliza la herencia múltiple de tablas <multi-table-inheritance>, los formularios generados por una fábrica de formularios contendrán un campo de enlace a padre (por defecto <nombre_del_modelo_padre>_ptr) en lugar de un campo id.
Por defecto, cuando creas un formulario a partir de un modelo, el formulario utilizará una consulta que incluye todos los objetos del modelo (por ejemplo, Autor.objects.all()). Puedes sobrescribir este comportamiento utilizando el argumento queryset:
>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith="O"))
Alternativamente, puedes crear una subclase que establezca self.queryset en __init__:
from django.forms import BaseModelFormSet
from myapp.models import Author
class BaseAuthorFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.queryset = Author.objects.filter(name__startswith="O")
Luego, pasa tu clase BaseAuthorFormSet a la función de fábrica:
>>> AuthorFormSet = modelformset_factory(
... Author, fields=["name", "title"], formset=BaseAuthorFormSet
... )
Si deseas devolver un formulario que no incluya ningún instancia preexistente del modelo, puedes especificar una consulta vacía:
>>> AuthorFormSet(queryset=Author.objects.none())
Por defecto, cuando se utiliza modelformset_factory, se crea un formulario de modelo utilizando modelform_factory(). A menudo puede ser útil especificar un formulario de modelo personalizado. Por ejemplo, puedes crear un formulario de modelo personalizado que tenga validación personalizada:
class AuthorForm(forms.ModelForm):
class Meta:
model = Author
fields = ["name", "title"]
def clean_name(self):
# custom validation for the name field
...
Luego, pasa tu formulario de modelo a la función de fábrica:
AuthorFormSet = modelformset_factory(Author, form=AuthorForm)
No es necesario definir un formulario de modelo personalizado siempre. La función modelformset_factory tiene varios argumentos que se pasan a la función modelform_factory, que se describen a continuación.
widgets¶Al usar el parámetro widgets, puedes especificar un diccionario de valores para personalizar la clase del widget del formulario de modelo para un campo particular. Esto funciona de la misma manera que el diccionario de widgets en la clase Meta interna de un formulario de modelo:
>>> AuthorFormSet = modelformset_factory(
... Author,
... fields=["name", "title"],
... widgets={"name": Textarea(attrs={"cols": 80, "rows": 20})},
... )
localized_fields¶Al usar el parámetro localized_fields, puedes habilitar la localización para los campos del formulario.
>>> AuthorFormSet = modelformset_factory(
... Author, fields=['name', 'title', 'birth_date'],
... localized_fields=['birth_date'])
Si se establece localized_fields en el valor especial '__all__', todos los campos serán localizados.
De manera similar a las formularios regulares, es posible especificar datos iniciales <formsets-initial-data> para los formularios en el conjunto de formularios mediante la especificación de un parámetro initial al instanciar la clase del formulario de modelo devuelta por modelformset_factory(). Sin embargo, con formularios de modelos, los valores iniciales solo se aplican a los formularios adicionales, aquellos que no están asociados a una instancia de modelo existente. Si la longitud del initial supera el número de formularios adicionales, los datos iniciales excedentes son ignorados. Si los formularios adicionales con datos iniciales no se modifican por el usuario, no se validarán ni se guardarán.
De manera similar a un formulario de modelo regular, puedes guardar los datos como objeto de modelo. Esto se hace mediante el método save() del conjunto de formularios:
# Create a formset instance with POST data.
>>> formset = AuthorFormSet(request.POST)
# Assuming all is valid, save the data.
>>> instances = formset.save()
El método save() devuelve las instancias que han sido guardadas en la base de datos. Si los datos de una instancia dada no cambiaron en los datos vinculados, la instancia no se guardará en la base de datos y no estará incluida en el valor de retorno (instances, en el ejemplo anterior).
Cuando los campos faltan del formulario (por ejemplo, porque han sido excluidos), estos campos no serán establecidos por el método save(). Puedes encontrar más información sobre esta restricción, que también se aplica a los formularios de modelo regulares, en Seleccionar los campos a utilizar.
Pass commit=False para devolver las instancias de modelo no guardadas:
# don't save to the database
>>> instances = formset.save(commit=False)
>>> for instance in instances:
... # do something with instance
... instance.save()
...
Esto te da la capacidad de adjuntar datos a las instancias antes de guardarlas en la base de datos. Si tu formset contiene un campo ManyToManyField, también necesitarás llamar a formset.save_m2m() para asegurarte de que las relaciones muchos-a-muchos se guarden correctamente.
Después de llamar a save(), tu formulario de modelo tendrá tres nuevas atributos conteniendo los cambios del formset:
Al igual que con los formsets regulares, puedes utilizar los parámetros max_num y extra para modelformset_factory() para limitar el número de formas adicionales que se muestran.
max_num no previene la visualización de objetos existentes:
>>> Author.objects.order_by("name")
<QuerySet [<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]>
>>> AuthorFormSet = modelformset_factory(Author, fields=["name"], max_num=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by("name"))
>>> [x.name for x in formset.get_queryset()]
['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman']
También, extra=0 no previene la creación de nuevos instancias de modelo ya que puedes añadir formas adicionales con JavaScript o enviar datos POST adicionales. Consulta Prevenir la creación de nuevos objetos para saber cómo hacer esto.
Si el valor de max_num es mayor que el número de objetos relacionados existentes, se agregarán hasta extra formas en blanco adicionales al formset, siempre y cuando el número total de formas no supere max_num:
>>> AuthorFormSet = modelformset_factory(Author, fields=["name"], max_num=4, extra=2)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by("name"))
>>> for form in formset:
... print(form)
...
<div><label for="id_form-0-name">Name:</label><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100"><input type="hidden" name="form-0-id" value="1" id="id_form-0-id"></div>
<div><label for="id_form-1-name">Name:</label><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100"><input type="hidden" name="form-1-id" value="3" id="id_form-1-id"></div>
<div><label for="id_form-2-name">Name:</label><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100"><input type="hidden" name="form-2-id" value="2" id="id_form-2-id"></div>
<div><label for="id_form-3-name">Name:</label><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100"><input type="hidden" name="form-3-id" id="id_form-3-id"></div>
Un valor max_num de None (el valor predeterminado) pone un límite alto en el número de formularios que se muestran (1000). En la práctica, esto es equivalente a no tener límite alguno.
Utilizando el parámetro edit_only, puedes prevenir la creación de cualquier nuevo objeto:
>>> AuthorFormSet = modelformset_factory(
... Author,
... fields=["name", "title"],
... edit_only=True,
... )
Los formularios de conjunto solo editarán instancias existentes de Autor. No se crearán ni se editará ningún otro objeto.
Los formularios de conjunto de modelos son muy similares a los formularios de conjunto. Digamos que queremos presentar un formulario de conjunto para editar instancias del modelo Autor:
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
if request.method == "POST":
formset = AuthorFormSet(request.POST, request.FILES)
if formset.is_valid():
formset.save()
# do something.
else:
formset = AuthorFormSet()
return render(request, "manage_authors.html", {"formset": formset})
Como puedes ver, la lógica de la vista de un formulario de conjunto de modelos no difiere drásticamente de la de un «formulario normal». La única diferencia es que llamamos a formset.save() para guardar los datos en la base de datos. (Esto se describió anteriormente, en Guardar objetos en el conjunto de formularios.)
clean() en un ModelFormSet¶Al igual que con un ModelForm, por defecto el método clean() de un ModelFormSet validará que ninguna de las instancias del formulario violen las restricciones únicas de tu modelo (ya sea unique, unique_together o unique_for_date|month|year). Si deseas sobreescribir el método clean() en un ModelFormSet y mantener esta validación, debes llamar al método clean de la clase padre:
from django.forms import BaseModelFormSet
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super().clean()
# example custom validation across forms in the formset
for form in self.forms:
# your custom formset validation
...
También ten en cuenta que en este paso, las instancias individuales del modelo ya han sido creadas para cada Form. Modificar un valor en form.cleaned_data no es suficiente para afectar el valor guardado. Si deseas modificar un valor en ModelFormSet.clean(), debes modificar form.instance:
from django.forms import BaseModelFormSet
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super().clean()
for form in self.forms:
name = form.cleaned_data["name"].upper()
form.cleaned_data["name"] = name
# update the instance value.
form.instance.name = name
Como se indicó anteriormente, puedes sobreescribir la consulta por defecto utilizada por el formulario de conjunto de modelo:
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
queryset = Author.objects.filter(name__startswith="O")
if request.method == "POST":
formset = AuthorFormSet(
request.POST,
request.FILES,
queryset=queryset,
)
if formset.is_valid():
formset.save()
# Do something.
else:
formset = AuthorFormSet(queryset=queryset)
return render(request, "manage_authors.html", {"formset": formset})
Ten en cuenta que pasamos el argumento queryset tanto en los casos POST como GET en este ejemplo.
Hay tres formas de renderizar un conjunto de formularios en una plantilla Django.
Primero, puedes dejar que el conjunto de formularios haga la mayor parte del trabajo:
<form method="post">
{{ formset }}
</form>
Segundo, puedes renderizar manualmente el conjunto de formularios, pero deja que el formulario se encargue de sí mismo:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form }}
{% endfor %}
</form>
Cuando renderices manualmente los formularios tú mismo, asegúrate de renderizar el formulario de gestión como se muestra arriba. Consulta la documentación del formulario de gestión.
Tercero, puedes renderizar cada campo manualmente:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{% for field in form %}
{{ field.label_tag }} {{ field }}
{% endfor %}
{% endfor %}
</form>
Si optas por utilizar este tercer método y no iteras sobre los campos con un bucle {% for %}, necesitarás renderizar el campo de clave primaria. Por ejemplo, si estabas renderizando los campos name y age de un modelo:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form.id }}
<ul>
<li>{{ form.name }}</li>
<li>{{ form.age }}</li>
</ul>
{% endfor %}
</form>
Observa cómo debemos renderizar explícitamente {{ form.id }}. Esto garantiza que el conjunto de formularios del modelo, en el caso POST, funcionará correctamente. (Este ejemplo asume una clave primaria llamada id. Si has definido explícitamente tu propia clave primaria que no se llama id, asegúrate de que se renderice.)
Los conjuntos de formularios en línea son una capa de abstracción pequeña sobre los conjuntos de formularios del modelo. Estos simplifican el caso de trabajar con objetos relacionados a través de una clave foránea. Supongamos que tienes estos dos modelos:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
Si deseas crear un conjunto de formularios que te permita editar libros pertenecientes a un autor en particular, podrías hacer esto:
>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book, fields=["title"])
>>> author = Author.objects.get(name="Mike Royko")
>>> formset = BookFormSet(instance=author)
La forma de libro (BookFormSet) tiene el prefijo :ref:`prefix <formset-prefix>` que es “book_set” ( <model name>_set ). Si la clave foránea (ForeignKey) del libro (Book) al autor (Author) tiene un nombre relacionado (related_name), se utiliza en su lugar.
Nota
inlineformset_factory() utiliza modelformset_factory() y marca can_delete=True.
Ver también
Cuando sobreescribas métodos en InlineFormSet, debes heredar de BaseInlineFormSet en lugar de BaseModelFormSet.
Ejemplo: si deseas sobreescribir clean():
from django.forms import BaseInlineFormSet
class CustomInlineFormSet(BaseInlineFormSet):
def clean(self):
super().clean()
# example custom validation across forms in the formset
for form in self.forms:
# your custom formset validation
...
También consulta Sobreescribir clean() en un ModelFormSet.
Luego, cuando crees tu formulario en línea, pasa el argumento opcional formset:
>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(
... Author, Book, fields=["title"], formset=CustomInlineFormSet
... )
>>> author = Author.objects.get(name="Mike Royko")
>>> formset = BookFormSet(instance=author)
Si tu modelo contiene más de un campo foráneo a la misma modelo, necesitarás resolver la ambigüedad manualmente utilizando fk_name. Por ejemplo, considere el siguiente modelo:
class Friendship(models.Model):
from_friend = models.ForeignKey(
Friend,
on_delete=models.CASCADE,
related_name="from_friends",
)
to_friend = models.ForeignKey(
Friend,
on_delete=models.CASCADE,
related_name="friends",
)
length_in_months = models.IntegerField()
Puedes resolver esto utilizando fk_name para inlineformset_factory().
>>> FriendshipFormSet = inlineformset_factory(
... Friend, Friendship, fk_name="from_friend", fields=["to_friend", "length_in_months"]
... )
Puede que desee proporcionar una vista que permita al usuario editar los objetos relacionados de un modelo. Aquí está cómo puedes hacerlo:
def manage_books(request, author_id):
author = Author.objects.get(pk=author_id)
BookInlineFormSet = inlineformset_factory(Author, Book, fields=["title"])
if request.method == "POST":
formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
if formset.is_valid():
formset.save()
# Do something. Should generally end with a redirect. For example:
return HttpResponseRedirect(author.get_absolute_url())
else:
formset = BookInlineFormSet(instance=author)
return render(request, "manage_books.html", {"formset": formset})
Noten cómo se pasa instance en ambos casos de POST y GET.
inlineformset_factory utiliza modelformset_factory y pasa la mayoría de sus argumentos a modelformset_factory. Esto significa que puedes utilizar el parámetro widgets de manera similar a cuando se le pasa a modelformset_factory. Consulta **Especificar widgets para usar en el formulario con widgets**_ arriba.
may 31, 2026