Sobre este documento
Este documento cubre los detalles sucios de la API de formularios de Django. Deberías leer la introducción a trabajar con formularios primero.
Una instancia de la clase Form es o bien vinculada a un conjunto de datos, o no vinculada.
Si está vinculada a un conjunto de datos, puede validar ese conjunto de datos y renderizar el formulario como HTML con los datos mostrados en el HTML.
Si está no vinculada, no puede realizar la validación (porque no hay datos para validar!), pero sí puede renderizar el formulario vacío como HTML.
Crear una instancia no vinculada de la clase Form, instanciando la clase:
>>> f = ContactForm()
Para vincular datos a un formulario, pasa los datos como un diccionario como el primer parámetro al constructor de tu clase Form:
>>> data = {
... "subject": "hello",
... "message": "Hi there",
... "sender": "foo@example.com",
... "cc_myself": True,
... }
>>> f = ContactForm(data)
En este diccionario, las claves son los nombres de campo, que corresponden a los atributos en tu clase Form. Los valores son los datos que estás intentando validar. Normalmente serán cadenas de texto, pero no hay requisito de que sean cadenas; el tipo de datos que pasas depende del Field, como veremos en un momento.
Si necesitas distinguir entre instancias de formulario vinculadas y no vinculadas en tiempo de ejecución, verifica el valor del atributo is_bound del formulario.
>>> f = ContactForm()
>>> f.is_bound
False
>>> f = ContactForm({"subject": "hello"})
>>> f.is_bound
True
Nota que pasar un diccionario vacío crea una forma vinculada con datos vacíos:
>>> f = ContactForm({})
>>> f.is_bound
True
Si tienes una instancia de un formulario (Form) vinculada y deseas cambiar los datos de alguna manera, o si quieres vincular a algunos datos una instancia no vinculada (Form), crea otra instancia de formulario (Form). No existe forma de cambiar los datos en una instancia de formulario (Form). Una vez creada la instancia de un formulario (Form), debes considerar sus datos inmutables, ya tenga datos o no.
Implementa un método clean() en tu Form cuando debes agregar validación personalizada para campos que están interdependientes. Consulta el ejemplo de uso en validando-campos-con-clean.
La tarea principal de un objeto Form es validar datos. Con una instancia de Form vinculada, llama al método is_valid() para ejecutar la validación y devuelve un valor booleano que indica si los datos eran válidos:
>>> data = {
... "subject": "hello",
... "message": "Hi there",
... "sender": "foo@example.com",
... "cc_myself": True,
... }
>>> f = ContactForm(data)
>>> f.is_valid()
True
Vamos a intentarlo con algunos datos inválidos. En este caso, subject está en blanco (un error, porque todos los campos son obligatorios por defecto) y sender no es una dirección de correo electrónico válida:
>>> data = {
... "subject": "",
... "message": "Hi there",
... "sender": "invalid email address",
... "cc_myself": True,
... }
>>> f = ContactForm(data)
>>> f.is_valid()
False
Access the errors attribute to get a dictionary of error
messages:
>>> f.errors
{'sender': ['Enter a valid email address.'], 'subject': ['This field is required.']}
En este diccionario, las claves son los nombres de los campos y los valores son listas de cadenas que representan los mensajes de error. Los mensajes de error se almacenan en listas porque un campo puede tener múltiples mensajes de error.
Puedes acceder a errors sin tener que llamar a is_valid() primero. Los datos del formulario se validarán la primera vez que llames a is_valid() o accedas a errors.
Las rutinas de validación solo se llamarán una vez, independientemente de cuántas veces accedas a errors o llames a is_valid(). Esto significa que si la validación tiene efectos laterales, esos efectos solo se desencadenarán una vez.
Devuelve un dict que mapea campos a sus instancias originales de ValidationError.
>>> f.errors.as_data()
{'sender': [ValidationError(['Enter a valid email address.'])],
'subject': [ValidationError(['This field is required.'])]}
Utiliza este método en cualquier momento en que necesites identificar un error por su code. Esto permite cosas como reescribir el mensaje del error o escribir lógica personalizada en una vista cuando un error dado esté presente. También se puede utilizar para serializar los errores en un formato personalizado (por ejemplo, XML); por ejemplo, as_json() depende de as_data().
La necesidad del método as_data() se debe a la compatibilidad hacia atrás. Anteriormente las instancias de ValidationError se perdían tan pronto como sus mensajes de error renderizados se agregaban al diccionario Form.errors. Idealmente Form.errors debería haber almacenado instancias de ValidationError y métodos con un prefijo as_ podrían renderizarlos, pero tuvo que hacerse la otra forma para no romper el código que espera mensajes de error renderizados en Form.errors.
Devuelve los errores serializados como JSON.
>>> f.errors.as_json()
{"sender": [{"message": "Enter a valid email address.", "code": "invalid"}],
"subject": [{"message": "This field is required.", "code": "required"}]}
Por defecto, as_json() no escape su salida. Si estás utilizando esto para algo como solicitudes AJAX a una vista del formulario donde el cliente interpreta la respuesta y inserta errores en la página, asegúrate de escapar los resultados en el lado del cliente para evitar la posibilidad de un ataque de inyección de código cruzado. Puedes hacerlo en JavaScript con element.textContent = errorText o con jQuery’s $(el).text(errorText) (en lugar de su función .html()).
Si por alguna razón no quieres utilizar la escapada del lado del cliente, también puedes establecer escape_html=True y los mensajes de error se escaparán para que puedas usarlos directamente en HTML.
Returns the errors as a dictionary suitable for serializando a JSON. Form.errors.as_json() devuelve JSON serializado, mientras que este método devuelve los datos de error antes de que se serialicen.
El parámetro escape_html comporta como se describe en Form.errors.as_json().
Este método permite agregar errores a campos específicos desde dentro del método Form.clean() o desde fuera de la forma en sí; por ejemplo, desde una vista.
El argumento field es el nombre del campo al que se deben agregar los errores. Si su valor es None, el error se tratará como un error no relacionado con un campo tal y como lo devuelve Form.non_field_errors().
El argumento error puede ser una cadena, o preferiblemente una instancia de ValidationError. Consulta Levantar ValidationError para obtener prácticas recomendadas sobre cómo definir errores de formulario.
Nota que Form.add_error() elimina automáticamente el campo relevante de cleaned_data.
Este método devuelve un booleano designando si un campo tiene un error con un código específico de error. Si code es None, devolverá True si el campo contiene cualquier error en absoluto.
Para comprobar errores no relacionados con campos utilice NON_FIELD_ERRORS como parámetro field.
Este método devuelve la lista de errores desde Form.errors que no están asociadas a un campo en particular. Esto incluye ValidationErrors que se levantan en Form.clean() y errores agregados utilizando Form.add_error(None, "...").
It’s meaningless to validate a form with no data, but, for the record, here’s what happens with unbound forms:
>>> f = ContactForm()
>>> f.is_valid()
False
>>> f.errors
{}
Utiliza el atributo initial para declarar el valor inicial de los campos de la forma en tiempo de ejecución. Por ejemplo, podrías querer rellenar un campo username con el nombre de usuario de la sesión actual.
Para lograr esto, utiliza el argumento initial a un Form. Este argumento, si se proporciona, debería ser un diccionario que mapee nombres de campos a valores iniciales. Solo incluye los campos para los cuales estás especificando un valor inicial; no es necesario incluir todos los campos de tu forma. Por ejemplo:
>>> f = ContactForm(initial={"subject": "Hi there!"})
Estos valores solo se muestran en formas sin datos y no se utilizan como valores por defecto si no se proporciona un valor particular.
Si un campo Field define el atributo initial y incluyes el atributo initial al instanciar la Form, entonces el último initial tendrá precedencia. En este ejemplo, initial se proporciona tanto a nivel de campo como a nivel de instancia de forma, y el último tiene precedencia:
>>> from django import forms
>>> class CommentForm(forms.Form):
... name = forms.CharField(initial="class")
... url = forms.URLField()
... comment = forms.CharField()
...
>>> f = CommentForm(initial={"name": "instance"}, auto_id=False)
>>> print(f)
<div>Name:<input type="text" name="name" value="instance" required></div>
<div>Url:<input type="url" name="url" required></div>
<div>Comment:<input type="text" name="comment" required></div>
Devuelve los datos iniciales para un campo de formulario. Recupera los datos del atributo Form.initial si está presente, de lo contrario intenta con Field.initial. Los valores llamables se evalúan.
Se recomienda utilizar el atributo BoundField.initial sobre la función get_initial_for_field() porque BoundField.initial tiene una interfaz más simple. Además, a diferencia de la función get_initial_for_field(), el atributo BoundField.initial almacena sus valores en caché. Esto es útil especialmente cuando se tratan con llamables cuyos valores pueden cambiar (por ejemplo datetime.now o uuid.uuid4):
>>> import uuid
>>> class UUIDCommentForm(CommentForm):
... identifier = forms.UUIDField(initial=uuid.uuid4)
...
>>> f = UUIDCommentForm()
>>> f.get_initial_for_field(f.fields["identifier"], "identifier")
UUID('972ca9e4-7bfe-4f5b-af7d-07b3aa306334')
>>> f.get_initial_for_field(f.fields["identifier"], "identifier")
UUID('1b411fab-844e-4dec-bd4f-e9b0495f04d0')
>>> # Using BoundField.initial, for comparison
>>> f["identifier"].initial
UUID('28a09c59-5f00-4ed9-9179-a3b074fa9c30')
>>> f["identifier"].initial
UUID('28a09c59-5f00-4ed9-9179-a3b074fa9c30')
Utiliza el método has_changed() en tu Form cuando necesites comprobar si los datos del formulario han cambiado desde los datos iniciales.
>>> data = {
... "subject": "hello",
... "message": "Hi there",
... "sender": "foo@example.com",
... "cc_myself": True,
... }
>>> f = ContactForm(data, initial=data)
>>> f.has_changed()
False
Cuando se envía el formulario, lo reconstruimos y proporcionamos los datos originales para que la comparación pueda realizarse:
>>> f = ContactForm(request.POST, initial=data)
>>> f.has_changed()
La función has_changed() será True si los datos de request.POST difieren de lo que se proporcionó en initial o False en caso contrario. El resultado se calcula llamando a la función Field.has_changed() para cada campo del formulario.
La propiedad changed_data devuelve una lista con los nombres de los campos cuyos valores en los datos vinculados al formulario (generalmente request.POST) difieren de lo que se proporcionó en initial. Devuelve una lista vacía si no hay diferencias.
>>> f = ContactForm(request.POST, initial=data)
>>> if f.has_changed():
... print("The following fields changed: %s" % ", ".join(f.changed_data))
...
>>> f.changed_data
['subject', 'message']
Puedes acceder a los campos de la instancia de Form desde su propiedad fields:
>>> for row in f.fields.values():
... print(row)
...
<django.forms.fields.CharField object at 0x7ffaac632510>
<django.forms.fields.URLField object at 0x7ffaac632f90>
<django.forms.fields.CharField object at 0x7ffaac3aa050>
>>> f.fields["name"]
<django.forms.fields.CharField object at 0x7ffaac6324d0>
Puedes alterar el campo y la clase BoundField de la instancia de Form para cambiar la forma en que se presenta en el formulario:
>>> f.as_div().split("</div>")[0]
'<div><label for="id_subject">Subject:</label><input type="text" name="subject" maxlength="100" required id="id_subject">'
>>> f["subject"].label = "Topic"
>>> f.as_div().split("</div>")[0]
'<div><label for="id_subject">Topic:</label><input type="text" name="subject" maxlength="100" required id="id_subject">'
Ten cuidado de no alterar la propiedad base_fields porque esta modificación influirá en todos los formularios ContactForm posteriores dentro del mismo proceso Python:
>>> f.base_fields["subject"].label_suffix = "?"
>>> another_f = ContactForm(auto_id=False)
>>> another_f.as_div().split("</div>")[0]
'<div><label for="id_subject">Subject?</label><input type="text" name="subject" maxlength="100" required id="id_subject">'
Cada campo en una clase Form es responsable no sólo de validar los datos, sino también de «limpiarlos» – normalizarlos a un formato consistente. Esto es una característica agradable porque permite que los datos para un campo particular se ingresen de varias maneras, siempre resultando en una salida consistente.
Por ejemplo, la clase DateField normaliza el input en un objeto datetime.date de Python. Independientemente de si le pasas una cadena en formato '1994-07-15', un objeto datetime.date, o varios otros formatos, DateField siempre normalizará a un objeto datetime.date siempre y cuando sea válido.
Una vez que hayas creado una instancia de Form con un conjunto de datos y lo hayas validado, puedes acceder a los datos limpios mediante su atributo cleaned_data:
>>> data = {
... "subject": "hello",
... "message": "Hi there",
... "sender": "foo@example.com",
... "cc_myself": True,
... }
>>> f = ContactForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'}
Ten en cuenta que cualquier campo de texto – como CharField o EmailField – siempre limpia la entrada en una cadena. Cubriremos las implicaciones de codificación más adelante en este documento.
Si tus datos no se validan, el diccionario cleaned_data contiene solo los campos válidos:
>>> data = {
... "subject": "",
... "message": "Hi there",
... "sender": "invalid email address",
... "cc_myself": True,
... }
>>> f = ContactForm(data)
>>> f.is_valid()
False
>>> f.cleaned_data
{'cc_myself': True, 'message': 'Hi there'}
cleaned_data siempre contendrá solo una clave para los campos definidos en la Form, incluso si pasas datos adicionales cuando defines la Form. En este ejemplo, pasamos un montón de campos adicionales al constructor de ContactForm, pero cleaned_data contiene solo los campos de la forma:
>>> data = {
... "subject": "hello",
... "message": "Hi there",
... "sender": "foo@example.com",
... "cc_myself": True,
... "extra_field_1": "foo",
... "extra_field_2": "bar",
... "extra_field_3": "baz",
... }
>>> f = ContactForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data # Doesn't contain extra_field_1, etc.
{'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'}
Cuando la Form es válida, cleaned_data incluirá una clave y valor para todos sus campos, incluso si el diccionario de datos no incluía un valor para algunos campos opcionales. En este ejemplo, el diccionario de datos no incluye un valor para el campo nick_name, pero cleaned_data lo incluye, con un valor vacío:
>>> from django import forms
>>> class OptionalPersonForm(forms.Form):
... first_name = forms.CharField()
... last_name = forms.CharField()
... nick_name = forms.CharField(required=False)
...
>>> data = {"first_name": "John", "last_name": "Lennon"}
>>> f = OptionalPersonForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'nick_name': '', 'first_name': 'John', 'last_name': 'Lennon'}
En el ejemplo anterior, el valor de cleaned_data para nick_name se establece en una cadena vacía, porque nick_name es CharField, y los campos CharField tratan los valores vacíos como una cadena vacía. Cada tipo de campo sabe qué es su «valor en blanco» – por ejemplo, para DateField, es None en lugar de la cadena vacía. Para obtener detalles completos sobre el comportamiento de cada campo en este caso, consulte la nota sobre «Valor vacío» para cada campo en la sección Clases de campo integradas Field a continuación.
Puedes escribir código para realizar validaciones para campos formularios específicos (basados en su nombre) o para la forma como un todo (considerando combinaciones de varios campos). Más información sobre esto se encuentra en Validación de formularios y campos.
La segunda tarea de un objeto Form es renderizarlo como HTML. Para hacerlo, imprímelo:
>>> f = ContactForm()
>>> print(f)
<div><label for="id_subject">Subject:</label><input type="text" name="subject" maxlength="100" required id="id_subject"></div>
<div><label for="id_message">Message:</label><input type="text" name="message" required id="id_message"></div>
<div><label for="id_sender">Sender:</label><input type="email" name="sender" required id="id_sender"></div>
<div><label for="id_cc_myself">Cc myself:</label><input type="checkbox" name="cc_myself" id="id_cc_myself"></div>
Si la forma está ligada a datos, el salida en HTML incluirá esos datos apropiadamente. Por ejemplo, si un campo se representa por un <input type="text">, los datos estarán en el atributo value. Si un campo se representa por un <input type="checkbox">, entonces ese HTML incluirá checked si corresponde:
>>> data = {
... "subject": "hello",
... "message": "Hi there",
... "sender": "foo@example.com",
... "cc_myself": True,
... }
>>> f = ContactForm(data)
>>> print(f)
<div><label for="id_subject">Subject:</label><input type="text" name="subject" value="hello" maxlength="100" required id="id_subject"></div>
<div><label for="id_message">Message:</label><input type="text" name="message" value="Hi there" required id="id_message"></div>
<div><label for="id_sender">Sender:</label><input type="email" name="sender" value="foo@example.com" required id="id_sender"></div>
<div><label for="id_cc_myself">Cc myself:</label><input type="checkbox" name="cc_myself" id="id_cc_myself" checked></div>
Este es el texto traducido:
Por flexibilidad, el output no incluye las etiquetas <form> y </form> ni una etiqueta <input type="submit">. Es tu trabajo hacerlo.
Cada tipo de campo tiene una representación HTML predeterminada. El campo CharField se representa con un <input type="text"> y el campo EmailField con un <input type="email">. El campo BooleanField(null=False) se representa con un <input type="checkbox">. Toma nota de que estos son solo valores predeterminados sensatos; puedes especificar qué HTML utilizar para un campo dado utilizando widgets, lo que explicaremos más adelante.
El nombre HTML del etiqueta se toma directamente del nombre de atributo en la clase ContactForm.
El texto de etiqueta para cada campo – por ejemplo, 'Asunto:', 'Mensaje:' y 'Copia a mí mismo:' se genera desde el nombre del campo convirtiendo todos los guiones bajos a espacios y mayusculando la primera letra. Toma nota nuevamente de que estos son solo valores predeterminados sensatos; también puedes especificar etiquetas manualmente.
Cada texto de etiqueta está rodeado por una etiqueta HTML <label>, que apunta al campo de formulario correspondiente a través de su id. Su id, a su vez, se genera prestando 'id_' al nombre del campo. Los atributos id y las etiquetas <label> están incluidos en el output por defecto para seguir las mejores prácticas, pero puedes cambiar ese comportamiento.
El output utiliza la sintaxis HTML5, dirigida a <!DOCTYPE html>. Por ejemplo, utiliza atributos booleanos como checked en lugar del estilo XHTML de checked='checked'.
Aunque el output <div> es el estilo de salida predeterminado cuando imprimes un formulario puedes personalizar el output utilizando tu propio template de formulario que puede ser configurado a nivel de sitio, por formulario o por instancia. Consulta Plantillas de formulario reutilizables.
El renderizado predeterminado cuando imprimes un formulario utiliza los siguientes métodos y atributos.
template_name¶El nombre del template renderizado si el formulario se convierte en una cadena, por ejemplo, mediante print(form) o en un template mediante {{ form }}.
Por defecto, una propiedad que devuelve el valor de la plantilla del renderer form_template_name. Puedes establecerlo como nombre de plantilla de cadena para superponer ese valor para una clase de formulario en particular.
render()¶El método render se llama tanto por __str__ como los métodos Form.as_div(), Form.as_table(), Form.as_p() y Form.as_ul(). Todos los argumentos son opcionales y tienen el valor predeterminado:
template_name: Form.template_name
context: Valor devuelto por Form.get_context()
renderer: Valor devuelto por Form.default_renderer
Al pasar template_name puedes personalizar la plantilla utilizada para una sola llamada.
get_context()¶Return the contexto de la plantilla para renderizar el formulario.
El contexto disponible es:
form: El formulario vinculado.
fields: Todos los campos vinculados, excepto los campos ocultos.
hidden_fields: Todos los campos vinculados ocultos.
errors: Todas las errores del formulario no relacionados con el campo o ocultos.
template_name_label¶La plantilla utilizada para renderizar un campo <label>, utilizado cuando se llama a BoundField.label_tag()/legend_tag(). Puede cambiarse por formulario mediante la sobrescritura de esta propiedad o más generalmente mediante la sobrescripción de la plantilla predeterminada, consulte también Sobreescribiendo plantillas de formulario predeterminadas.
La forma recomendada para cambiar el estilo de salida del formulario es establecer una plantilla personalizada para el formulario, ya sea a nivel de sitio, por formulario o por instancia. Consulte Plantillas de formulario reutilizables para ejemplos.
Los siguientes textos traducidos manteniendo todas sus etiquetas intactas:
Nota
De las plantillas y estilos de salida proporcionados por el framework, se recomienda el as_div() por defecto sobre las versiones as_p(), as_table(), y as_ul() ya que la plantilla implementa <fieldset> y <legend> para agrupar inputs relacionados y es más fácil de navegar para usuarios de lectores de pantalla.
Cada par de ayuda asocia un método de formulario con una atributo dando el nombre de plantilla apropiado.
as_div()¶La plantilla utilizada por as_div(). Por defecto: 'django/forms/div.html'.
as_div() renderiza el formulario como una serie de elementos <div>, con cada <div> conteniendo un campo, tal que:
>>> f = ContactForm()
>>> f.as_div()
… da HTML como:
<div>
<label for="id_subject">Subject:</label>
<input type="text" name="subject" maxlength="100" required id="id_subject">
</div>
<div>
<label for="id_message">Message:</label>
<input type="text" name="message" required id="id_message">
</div>
<div>
<label for="id_sender">Sender:</label>
<input type="email" name="sender" required id="id_sender">
</div>
<div>
<label for="id_cc_myself">Cc myself:</label>
<input type="checkbox" name="cc_myself" id="id_cc_myself">
</div>
as_p()¶La plantilla utilizada por as_p(). Por defecto: 'django/forms/p.html'.
as_p() renderiza el formulario como una serie de etiquetas <p>, con cada <p> conteniendo un campo:
>>> f = ContactForm()
>>> f.as_p()
'<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></p>\n<p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></p>\n<p><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" required></p>\n<p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></p>'
>>> print(f.as_p())
<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></p>
<p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></p>
<p><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></p>
<p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></p>
as_ul()¶El template utilizado por as_ul(). Por defecto: 'django/forms/ul.html'.
as_ul() renderiza la forma como una serie de etiquetas <li> , con cada <li> conteniendo un campo. No incluye las etiquetas <ul> o </ul>, para que puedas especificar cualquier atributo HTML en la etiqueta <ul> para mayor flexibilidad:
>>> f = ContactForm()
>>> f.as_ul()
'<li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></li>\n<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></li>\n<li><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></li>\n<li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></li>'
>>> print(f.as_ul())
<li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></li>
<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></li>
<li><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></li>
<li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></li>
as_table()¶El template utilizado por as_table(). Por defecto: 'django/forms/table.html'.
as_table() renderiza la forma como una tabla HTML <table>:
>>> f = ContactForm()
>>> f.as_table()
'<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" required></td></tr>\n<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" required></td></tr>\n<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" required></td></tr>\n<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself"></td></tr>'
>>> print(f.as_table())
<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" required></td></tr>
<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" required></td></tr>
<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" required></td></tr>
<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself"></td></tr>
Es bastante común estilar las filas y campos formularios que son requeridos o tienen errores. Por ejemplo, podrías querer presentar las filas formularios requeridas en negrita y destacar los errores en rojo.
La clase Form tiene un par de trampas que puedes usar para agregar atributos class a las filas requeridas o a las filas con errores: establece las atributos Form.error_css_class y/o Form.required_css_class:
from django import forms
class ContactForm(forms.Form):
error_css_class = "error"
required_css_class = "required"
# ... and the rest of your fields here
Una vez que hayas hecho eso, las filas recibirán clases "error" y/o "required" , según sea necesario. El HTML será algo como:
>>> f = ContactForm(data)
>>> print(f)
<div class="required"><label for="id_subject" class="required">Subject:</label> ...
<div class="required"><label for="id_message" class="required">Message:</label> ...
<div class="required"><label for="id_sender" class="required">Sender:</label> ...
<div><label for="id_cc_myself">Cc myself:</label> ...
>>> f["subject"].label_tag()
<label class="required" for="id_subject">Subject:</label>
>>> f["subject"].legend_tag()
<legend class="required" for="id_subject">Subject:</legend>
>>> f["subject"].label_tag(attrs={"class": "foo"})
<label for="id_subject" class="foo required">Subject:</label>
>>> f["subject"].legend_tag(attrs={"class": "foo"})
<legend for="id_subject" class="foo required">Subject:</legend>
Puedes personalizar la representación de las filas del formulario utilizando un campo BoundField personalizado.
Specifica el renderer a utilizar para la forma. Por defecto, None que significa utilizar el renderer por defecto especificado por la FORM_RENDERER configuración.
Puedes establecer esto como un atributo de clase cuando declares tu formulario o utiliza el argumento renderer en Form.__init__(). Por ejemplo:
from django import forms
class MyForm(forms.Form):
default_renderer = MyRenderer()
o:
form = MyForm(renderer=MyRenderer())
En las atajadas as_p(), as_ul() y as_table(), se muestran los campos en el orden en que los defines en tu clase de formulario. Por ejemplo, en el ejemplo del formulario ContactForm, los campos están definidos en el orden subject, message, sender, cc_myself. Para reordenar la salida HTML, cambia el orden en que se enumeran esos campos en la clase.
Hay varias otras formas de personalizar la orden:
Por defecto Form.field_order=None, lo que mantiene el orden en que defines los campos en tu clase de formulario. Si field_order es una lista de nombres de campos, se ordenan los campos según la lista especificada y los campos restantes se agregan según el orden por defecto. Los nombres de campo desconocidos en la lista se ignoran. Esto permite deshabilitar un campo en una subclase estableciéndolo a None sin tener que redefinir la ordenación.
También puedes utilizar el argumento Form.field_order para sobreescribir la orden de los campos al instanciar el formulario. Si un formulario Form define field_order y incluyes field_order cuando instancias el formulario, entonces el último field_order tendrá prioridad.
Puedes reorganizar los campos en cualquier momento utilizando order_fields() con una lista de nombres de campo como en field_order.
Si renderizas un objeto de formulario vinculado, el acto de renderizado ejecutará automáticamente la validación del formulario si no ha sucedido ya y la salida HTML incluirá los errores de validación como <ul class="errorlist">.
El siguiente:
>>> data = {
... "subject": "",
... "message": "Hi there",
... "sender": "invalid email address",
... "cc_myself": True,
... }
>>> ContactForm(data).as_div()
… da HTML como:
<div>
<label for="id_subject">Subject:</label>
<ul class="errorlist" id="id_subject_error"><li>This field is required.</li></ul>
<input type="text" name="subject" maxlength="100" required aria-invalid="true" aria-describedby="id_subject_error" id="id_subject">
</div>
<div>
<label for="id_message">Message:</label>
<textarea name="message" cols="40" rows="10" required id="id_message">Hi there</textarea>
</div>
<div>
<label for="id_sender">Sender:</label>
<ul class="errorlist" id="id_sender_error"><li>Enter a valid email address.</li></ul>
<input type="email" name="sender" value="invalid email address" maxlength="320" required aria-invalid="true" aria-describedby="id_sender_error" id="id_sender">
</div>
<div>
<label for="id_cc_myself">Cc myself:</label>
<input type="checkbox" name="cc_myself" id="id_cc_myself" checked>
</div>
Los plantillas de formularios predeterminadas de Django asociarán errores de validación con su entrada utilizando la atributo HTML aria-describedby cuando el campo tenga un auto_id y no se proporcione una descripción personalizada aria-describedby. Si se establece una descripción personalizada aria-describedby al definir el widget, esto sobrescribirá el valor predeterminado.
Si el widget se renderiza en un <fieldset> entonces aria-describedby se agrega a este elemento, de lo contrario se agrega al elemento HTML del widget (por ejemplo, <input>).
Se agregó aria-describedby para asociar errores con su entrada.
Por defecto, las formas utilizan django.forms.utils.ErrorList para formatear los errores de validación. ErrorList es un objeto similar a una lista donde initlist es la lista de errores. Además, esta clase tiene las siguientes atributos y métodos.
La argumento field_id se agregó.
Las clases CSS a utilizar cuando se renderiza la lista de errores. Cualquier clase proporcionada se agrega a la clase por defecto errorlist.
Especifica el revisor a utilizar para ErrorList. Por defecto, es None, lo que significa utilizar el revisor por defecto especificado por la configuración de la FORM_RENDERER configuración.
Un id para el campo al que se relacionan los errores. Esto permite agregar un atributo id HTML en la plantilla de error y es útil para asociar los errores con el campo. La plantilla predeterminada utiliza el formato id="{{ field_id }}_error" y un valor se proporciona mediante Form.add_error() utilizando el auto_id del campo.
El nombre del template utilizado cuando se llama a __str__ o render(). Por defecto, este es 'django/forms/errors/list/default.html' que es un proxy para el template 'ul.html'.
El nombre del template utilizado cuando se llama a as_text(). Por defecto, este es 'django/forms/errors/list/text.html'. Este template renderiza los errores como una lista de puntos suspensivos.
El nombre del template utilizado cuando se llama a as_ul(). Por defecto, este es 'django/forms/errors/list/ul.html'. Este template renderiza los errores en etiquetas <li> con un contenedor <ul> y las clases CSS definidas por error_class.
Devuelve contexto para la renderización de errores en un template.
El contexto disponible es:
errors: Una lista de los errores.
error_class: Un string de clases CSS.
El método render se llama tanto por __str__ como por el método as_ul().
Todos los argumentos son opcionales y se establecerán en:
template_name: Valor devuelto por template_name
context: Valor devuelto por get_context()
renderer: Valor devuelto por renderer
Rendere la lista de errores utilizando el template definido por template_name_text.
Rendere la lista de errores utilizando el template definido por template_name_ul.
Si deseas personalizar la representación de los errores, esto se puede lograr sobrescribiendo la atributo template_name o más generalmente sobrescribiendo el template predeterminado, consulte también Sobreescribiendo plantillas de formulario predeterminadas.
Los métodos as_p(), as_ul() y as_table() son atajos – no son la única forma en que un objeto de formulario puede ser mostrado.
Se utiliza para mostrar HTML o acceder a atributos para un campo individual de una instancia Form.
El método __str__() de este objeto muestra el HTML para este campo.
Puedes utilizar Form.bound_field_class y Field.bound_field_class para especificar una clase diferente BoundField por formulario o por campo, respectivamente.
Consulte Customizando BoundField para ejemplos de sobrescritura de un BoundField.
Para recuperar un solo BoundField, utiliza la sintaxis de búsqueda en el diccionario sobre tu formulario utilizando el nombre del campo como clave:
>>> form = ContactForm()
>>> print(form["subject"])
<input id="id_subject" type="text" name="subject" maxlength="100" required>
Para recuperar todos los objetos BoundField, itera sobre el formulario:
>>> form = ContactForm()
>>> for boundfield in form:
... print(boundfield)
...
<input id="id_subject" type="text" name="subject" maxlength="100" required>
<input type="text" name="message" id="id_message" required>
<input type="email" name="sender" id="id_sender" required>
<input type="checkbox" name="cc_myself" id="id_cc_myself">
El output específico del campo respeta la configuración auto_id del objeto de formulario:
>>> f = ContactForm(auto_id=False)
>>> print(f["message"])
<input type="text" name="message" required>
>>> f = ContactForm(auto_id="id_%s")
>>> print(f["message"])
<input type="text" name="message" id="id_message" required>
BoundField¶Devuelve una referencia aria-describedby para asociar un campo con su texto de ayuda y errores. Devuelve None si aria-describedby está establecido en Widget.attrs para preservar la atributo definido por el usuario cuando se renderiza el formulario.
El atributo ID HTML para este BoundField. Devuelve una cadena vacía si Form.auto_id es False.
Esta propiedad devuelve los datos para este BoundField extraídos por el método value_from_datadict() del widget, o None si no se le dio:
>>> unbound_form = ContactForm()
>>> print(unbound_form["subject"].data)
None
>>> bound_form = ContactForm(data={"subject": "My Subject"})
>>> print(bound_form["subject"].data)
My Subject
Un objeto que admite listas como la de formato de lista de errores que se muestra como un <ul class="errorlist"> HTML cuando se imprime:
>>> data = {"subject": "hi", "message": "", "sender": "", "cc_myself": ""}
>>> f = ContactForm(data, auto_id=False)
>>> print(f["message"])
<input type="text" name="message" required aria-invalid="true">
>>> f["message"].errors
['This field is required.']
>>> print(f["message"].errors)
<ul class="errorlist"><li>This field is required.</li></ul>
>>> f["subject"].errors
[]
>>> print(f["subject"].errors)
>>> str(f["subject"].errors)
''
Cuando se renderiza un campo con errores, se establecerá aria-invalid="true" en el widget del campo para indicar a los usuarios de lectores de pantalla que hay un error.
El instante de la clase Field del formulario que este BoundField envuelve.
La instancia de Form a la que está ligada esta BoundField.
El nombre que se utilizará en el atributo name HTML del widget. Considera la forma prefix.
Utiliza esta propiedad para renderizar el ID de este campo. Por ejemplo, si estás construyendo manualmente un <label> en tu plantilla (a pesar de que label_tag()/legend_tag() lo harán por ti):
<label for="{{ form.my_field.id_for_label }}">...</label>{{ my_field }}
Por defecto, esto será el nombre del campo prefijado con id_ (»id_my_field» para el ejemplo anterior). Puedes modificar el ID estableciendo attrs en el widget del campo. Por ejemplo, declarando un campo de esta forma:
my_field = forms.CharField(widget=forms.TextInput(attrs={"id": "myFIELD"}))
y utilizando la plantilla anterior, se renderizaría algo como:
<label for="myFIELD">...</label><input id="myFIELD" type="text" name="my_field" required>
Utiliza BoundField.initial para recuperar los datos iniciales de un campo de formulario. Recupera los datos del atributo Form.initial si están presentes, o intenta con el atributo Field.initial. Los valores llamables se evalúan. Consulta la referencia Los valores iniciales de la forma para más ejemplos.
BoundField.initial almacena su valor de retorno, lo cual es útil especialmente cuando se tratan con llamables cuyos valores pueden cambiar (por ejemplo datetime.now o uuid.uuid4):
>>> from datetime import datetime
>>> class DatedCommentForm(CommentForm):
... created = forms.DateTimeField(initial=datetime.now)
...
>>> f = DatedCommentForm()
>>> f["created"].initial
datetime.datetime(2021, 7, 27, 9, 5, 54)
>>> f["created"].initial
datetime.datetime(2021, 7, 27, 9, 5, 54)
Se recomienda utilizar BoundField.initial en lugar de get_initial_for_field().
Devuelve True si el widget de esta BoundField está oculto.
El nombre de la etiqueta del campo: label de este campo. Se utiliza en label_tag()/legend_tag().
El nombre de este campo en el formulario:
>>> f = ContactForm()
>>> print(f["subject"].name)
subject
>>> print(f["message"].name)
message
El nombre del template renderizado con BoundField.as_field_group().
Una propiedad que devuelve el valor de la template_name si está configurado, o la field_template_name.
Devuelve el valor del atributo use_fieldset de este widget de BoundField.
Devuelve el nombre de clase en minúsculas del widget del campo envuelto, eliminando cualquier tramo de input o widget. Esto puede usarse cuando se están creando formularios donde la disposición depende del tipo de widget. Por ejemplo:
{% for field in form %}
{% if field.widget_type == 'checkbox' %}
# render one way
{% else %}
# render another way
{% endif %}
{% endfor %}
BoundField¶Renderea el campo utilizando BoundField.render() con valores por defecto que renderizan el BoundField, incluyendo su etiqueta, texto de ayuda y errores usando el template’s template_name si está configurado o la field_template_name.
Devuelve una cadena de HTML para representar esto como un <input type="hidden">.
Se pasan **kwargs a as_widget().
Este método se utiliza principalmente internamente. Debes utilizar un widget en su lugar.
Rendere el campo mediante la renderización del widget pasado, agregando cualquier atributo HTML pasado como attrs. Si no se especifica ningún widget, se utilizará el widget predeterminado del campo.
Se utiliza only_initial internamente en Django y no debe establecerse explícitamente.
Cuando utilizas los atajos de renderizado de Django, se utilizan clases CSS para indicar campos de formulario requeridos o que contienen errores. Si estás renderizando manualmente un formulario, puedes acceder a estas clases CSS utilizando el método css_classes:
>>> f = ContactForm(data={"message": ""})
>>> f["message"].css_classes()
'required'
Si deseas proporcionar algunas clases adicionales además de las clases de error y requeridas que pueden ser necesarias, puedes proporcionar esas clases como argumento.
>>> f = ContactForm(data={"message": ""})
>>> f["message"].css_classes("foo bar")
'foo bar required'
Devuelve el contexto del template para renderizar el campo. El contexto disponible es field siendo la instancia del campo vinculado.
Rendere una etiqueta de label para el campo de formulario utilizando el template especificado por Form.template_name_label.
El contexto disponible es:
field: Esta instancia de la clase BoundField.
contents: Por defecto, una cadena concatenada de BoundField.label y Form.label_suffix (o Field.label_suffix, si está establecido). Esto se puede sobrescribir mediante los argumentos contents y label_suffix.
attrs: Un diccionario que contiene for, Form.required_css_class, y id. id se genera por el widget del campo attrs o BoundField.auto_id. Se pueden proporcionar atributos adicionales mediante el argumento ``attrs””.
use_tag: Un booleano que es True si la etiqueta tiene un id. Si False el template por defecto omite el tag.
tag: Una cadena opcional para personalizar el tag, con valor por defecto de label.
Truco
En tu plantilla field es la instancia de la clase BoundField. Por lo tanto field.field accede a la propiedad BoundField.field que es el campo que declaras, por ejemplo forms.CharField.
Para renderizar separadamente la etiqueta del tag de un campo de formulario, puedes llamar al método label_tag():
>>> f = ContactForm(data={"message": ""})
>>> print(f["message"].label_tag())
<label for="id_message">Message:</label>
Si deseas personalizar la renderización esto se puede lograr sobreescribiendo la propiedad Form.template_name_label o más generalmente sobreescribiendo el template por defecto, ver también Sobreescribiendo plantillas de formulario predeterminadas.
Llama al método label_tag() con tag='legend' para renderizar la etiqueta con etiquetas <legend>. Esto es útil cuando se están renderizando widgets de radio y múltiples casillas de verificación donde <legend> puede ser más apropiado que una <label>.
El método render es llamado por as_field_group. Todos los argumentos son opcionales y tienen valor por defecto:
template_name: BoundField.template_name
context: Valor devuelto por el método BoundField.get_context()
renderer: Valor devuelto por Form.default_renderer
Al pasar template_name puedes personalizar la plantilla utilizada para una sola llamada.
Utiliza este método para renderizar el valor bruto de este campo como lo haría un Widget:
>>> initial = {"subject": "welcome"}
>>> unbound_form = ContactForm(initial=initial)
>>> bound_form = ContactForm(data={"subject": "hi"}, initial=initial)
>>> print(unbound_form["subject"].value())
welcome
>>> print(bound_form["subject"].value())
hi
BoundField¶Define una clase personalizada BoundField para utilizar al renderizar el formulario. Esto tiene prioridad sobre la clase de campo definida en el proyecto (BaseRenderer.bound_field_class, junto con un FORM_RENDERER personalizado), pero puede ser sobrescrita por la clase de campo individual (Field.bound_field_class).
Si no se define como variable de clase, bound_field_class se puede establecer mediante el argumento bound_field_class en el constructor de Form o Field.
Por razones de compatibilidad, un campo de formulario personalizado todavía puede sobrescribir la método Field.get_bound_field() para utilizar una clase personalizada, aunque cualquier de las opciones anteriores es preferible.
Es posible que desee utilizar una clase personalizada BoundField si necesita acceder a información adicional sobre un campo de formulario en una plantilla y utilizar una subclase de Field no es suficiente.
Por ejemplo, si tiene un GPSCoordinatesField, y quiere poder acceder a información adicional sobre las coordenadas en una plantilla, esto podría implementarse de la siguiente manera:
class GPSCoordinatesBoundField(BoundField):
@property
def country(self):
"""
Return the country the coordinates lie in or None if it can't be
determined.
"""
value = self.value()
if value:
return get_country_from_coordinates(value)
else:
return None
class GPSCoordinatesField(Field):
bound_field_class = GPSCoordinatesBoundField
Ahora puede acceder al país en una plantilla con {{ form.coordinates.country }}.
También puede querer personalizar el renderizado predeterminado del campo de formulario. Por ejemplo, puede sobrescribir BoundField.label_tag() para agregar un clase CSS:
class StyledLabelBoundField(BoundField):
def label_tag(self, contents=None, attrs=None, label_suffix=None, tag=None):
attrs = attrs or {}
attrs["class"] = "wide"
return super().label_tag(contents, attrs, label_suffix, tag)
class UserForm(forms.Form):
bound_field_class = StyledLabelBoundField
name = CharField()
Esto actualizaría el renderizado de formularios por defecto:
>>> f = UserForm()
>>> print(f["name"].label_tag)
<label for="id_name" class="wide">Name:</label>
Para agregar una clase CSS al elemento HTML que envuelve a todos los campos, se puede sobrescribir la clase BoundField para devolver una colección diferente de clases CSS:
class WrappedBoundField(BoundField):
def css_classes(self, extra_classes=None):
parent_css_classes = super().css_classes(extra_classes)
return f"field-class {parent_css_classes}".strip()
class UserForm(forms.Form):
bound_field_class = WrappedBoundField
name = CharField()
Esta es la traducción de los textos originales:
>>> f = UserForm()
>>> print(f)
<div class="field-class"><label for="id_name">Name:</label><input type="text" name="name" required id="id_name"></div>
Alternativamente, para sobreescribir la clase BoundField a nivel de proyecto, se puede definir BaseRenderer.bound_field_class en un FORM_RENDERER personalizado:
mysite/renderers.py¶from django.forms.renderers import DjangoTemplates
from .forms import CustomBoundField
class CustomRenderer(DjangoTemplates):
bound_field_class = CustomBoundField
settings.py¶FORM_RENDERER = "mysite.renderers.CustomRenderer"
Trabajar con formularios que tienen campos FileField y ImageField es ligeramente más complicado que un formulario normal.
Primero, para poder subir archivos, debes asegurarte de que el elemento <form> defina correctamente la propiedad enctype como "multipart/form-data"
<form enctype="multipart/form-data" method="post" action="/foo/">
Segundo, cuando utilices el formulario, necesitarás vincular los datos de archivo. Los datos de archivo se manejan por separado a los datos del formulario normal, por lo que cuando tu formulario contenga un campo FileField y ImageField, deberás especificar un segundo argumento al vincular tu formulario. Así que si extendemos nuestro ContactForm para incluir un campo ImageField llamado mugshot, necesitarás vincular los datos de archivo que contienen la imagen del mugshot:
# Bound form with an image field
>>> from django.core.files.uploadedfile import SimpleUploadedFile
>>> data = {
... "subject": "hello",
... "message": "Hi there",
... "sender": "foo@example.com",
... "cc_myself": True,
... }
>>> file_data = {"mugshot": SimpleUploadedFile("face.jpg", b"file data")}
>>> f = ContactFormWithMugshot(data, file_data)
En la práctica, generalmente especificarás request.FILES como fuente de datos de archivo (al igual que utilizas request.POST como fuente de datos del formulario):
# Bound form with an image field, data from the request
>>> f = ContactFormWithMugshot(request.POST, request.FILES)
La construcción de un formulario no vinculado es lo mismo que siempre – omite tanto los datos del formulario como los datos de archivo:
# Unbound form with an image field
>>> f = ContactFormWithMugshot()
Si estás escribiendo vistas o plantillas reutilizables, puede que no sepas con anticipación si tu formulario es un formulario multipartito o no. El método is_multipart() te dice si el formulario requiere codificación multipartita para la presentación:
>>> f = ContactFormWithMugshot()
>>> f.is_multipart()
True
Aquí tienes un ejemplo de cómo podrías utilizar esto en una plantilla:
{% if form.is_multipart %}
<form enctype="multipart/form-data" method="post" action="/foo/">
{% else %}
<form method="post" action="/foo/">
{% endif %}
{{ form }}
</form>
Si tienes varios clases Form que comparten campos, puedes usar sobrecarga para eliminar la redundancia.
Cuando sobrescribes una clase de formulario personalizada, la clase derivada resultante incluirá todos los campos de la clase padre(s), seguidos de los campos que defines en la clase derivada.
En este ejemplo, ContactFormWithPriority contiene todos los campos de ContactForm, más un campo adicional, priority. Los campos de ContactForm están ordenados primero:
>>> class ContactFormWithPriority(ContactForm):
... priority = forms.CharField()
...
>>> f = ContactFormWithPriority(auto_id=False)
>>> print(f)
<div>Subject:<input type="text" name="subject" maxlength="100" required></div>
<div>Message:<textarea name="message" cols="40" rows="10" required></textarea></div>
<div>Sender:<input type="email" name="sender" required></div>
<div>Cc myself:<input type="checkbox" name="cc_myself"></div>
<div>Priority:<input type="text" name="priority" required></div>
Es posible sobrescribir múltiples formularios, tratando a los formularios como mixins. En este ejemplo, BeatleForm sobrescribe tanto PersonForm como InstrumentForm (en ese orden), y su lista de campos incluye los campos de las clases padre:
>>> from django import forms
>>> class PersonForm(forms.Form):
... first_name = forms.CharField()
... last_name = forms.CharField()
...
>>> class InstrumentForm(forms.Form):
... instrument = forms.CharField()
...
>>> class BeatleForm(InstrumentForm, PersonForm):
... haircut_type = forms.CharField()
...
>>> b = BeatleForm(auto_id=False)
>>> print(b)
<div>First name:<input type="text" name="first_name" required></div>
<div>Last name:<input type="text" name="last_name" required></div>
<div>Instrument:<input type="text" name="instrument" required></div>
<div>Haircut type:<input type="text" name="haircut_type" required></div>
Es posible declarar con claridad eliminar un campo Field heredado de una clase padre estableciendo el nombre del campo a None en la clase derivada. Por ejemplo:
>>> from django import forms
>>> class ParentForm(forms.Form):
... name = forms.CharField()
... age = forms.IntegerField()
...
>>> class ChildForm(ParentForm):
... name = None
...
>>> list(ChildForm().fields)
['age']
Puedes poner varios formularios Django dentro de uno solo <form> etiqueta. Para dar a cada formulario su propio espacio de nombres, utiliza el argumento de palabra clave prefix:
>>> mother = PersonForm(prefix="mother")
>>> father = PersonForm(prefix="father")
>>> print(mother)
<div><label for="id_mother-first_name">First name:</label><input type="text" name="mother-first_name" required id="id_mother-first_name"></div>
<div><label for="id_mother-last_name">Last name:</label><input type="text" name="mother-last_name" required id="id_mother-last_name"></div>
>>> print(father)
<div><label for="id_father-first_name">First name:</label><input type="text" name="father-first_name" required id="id_father-first_name"></div>
<div><label for="id_father-last_name">Last name:</label><input type="text" name="father-last_name" required id="id_father-last_name"></div>
El prefijo también se puede especificar en la clase de formulario:
>>> class PersonForm(forms.Form):
... ...
... prefix = "person"
...
may 31, 2026