======================= Capítulo 7: Formularios =======================
Los formularios en HTML, son la columna vertebral de los sitios Web
interactivos, los encontramos como simples caja de búsquedas como los que usa
Google o como los inconfundibles formularios para subir comentarios siempre
presentes en la mayoría de blogs, pero también existen complejas interfaces
que permiten la entradas de datos interactivamente y de forma personalizada,
y es que los formularios son una necesidad básica en la mayoria de aplicaciones
Web modernas, que necesitan recopilar datos a través de internet. Este
capítulo cubre la manera de usar Django para recopilar datos a través de
formularios, validarlos y hacer algo útil con ellos. A lo largo de este
capítulo nos enfocaremos en conocer los objetos HttpRequest
y los objetos
Form
Comenzaremos creando un simple formulario de búsquedas "a mano", observando cómo manejar los datos suministrados al navegador. Y a partir de ahí, pasaremos al uso del framework de formularios que viene incluido en Django.
Introducimos los objetos HttpRequest
en el :doc:`capítulo 3<chapter03>`,
cuando cubrimos las funciones vista, pero no tuvimos mucho que decir acerca de
ellos en aquel momento. Recuerdas que cada función de vista toma un objeto
HttpRequest
como primer parámetro, tal como en la vista hola()
que
construimos:
from django.http import HttpResponse def hola(request): return HttpResponse("Hola mundo")
Los objetos HttpRequest
, tal como la variable request
, contienen un numero
de atributos y métodos interesantes con los cuales deberías familiarizarte, a
fin de saber cómo utilizarlos lo mejor posible. Puedes utilizar estos atributos
para conseguir información acerca de las peticiones que recibes (por ejemplo: el
usuario o el navegador que está cargando la pagina en tu sitio creado con
Django), al mismo tiempo que la función vista es ejecutada.
Los objetos HttpRequest
contienen algunas piezas de información acerca de
la URL requerida.
Atributos o Métodos | Descripción | Ejemplo |
---|---|---|
request.path |
La ruta completa, no incluye el dominio pero incluye, la barra inclinada. | "/hola/" |
request.get_host() |
El host (ejemplo: tu "dominio," en lenguaje común). | "127.0.0.1:8000" o
"www.example.com" |
request.get_full_path() |
La ruta (path), mas una cadena
de consulta (si está disponible). |
"/hola/?print=true" |
request.is_secure() |
True si la petición fue hecha
vía HTTPS. Si no, False . |
True o False |
Siempre usa estos atributos/métodos en lugar de codificar en crudo tus URLs en tu vistas. Esto hace más flexible tu código y mas fácil de usar en otros lugares. Un simple ejemplo:
# MAL! def vista_actual_url(request): return HttpResponse("Bienvenido a mi pagina en /pagina_actual/") # BIEN def vista_actual_url(request): return HttpResponse("bienvenido a mi pagina en %s" % request.path)
request.META
es un diccionario Python , que contiene todas las cabeceras
HTTP disponibles para la petición dada --Incluyendo la dirección IP y el
agente-- Generalmente el nombre y la versión del navegador Web. Observa que
la lista completa de cabeceras disponibles depende de las cabeceras que el
usuario envía y las cabeceras que el servidor Web seleccione. Algunas de las
claves mas comúnes están disponibles como diccionarios y son:
HTTP_REFERER
-- La respectiva URL, en dado caso. (Observa la forma en que se escribeREFERER
.)HTTP_USER_AGENT
-- El navegador del usuario en forma de cadena, cualquiera que sea. Esta es algo si:"Mozilla/5.0 (X11; U; Linux i686; fr-FR; rv:1.8.1.17) Gecko/20080829 Firefox/2.0.0.17"
.REMOTE_ADDR
-- La dirección IP de el cliente, por ejemplo:"12.345.67.89"
. (Si la petición ha pasado a través de un proxi, entonces puede retornar una lista separada por comas, conteniendo las direcciones IP, por ejemplo:"12.345.67.89,23.456.78.90"
.)
Observa que request.META
es básicamente un diccionario Python, que contiene
todas las cabeceras HTTP disponibles, las que dependen del navegador y del
servidor que se este usando en ese determinado momento, por lo que obtendrás una
excepción KeyError
si intentas acceder a una clave que no existe. (Además las
cabeceras HTTP son datos externos -- que son subidos por los navegadores de los
usuarios, por lo que no deberías confiar en ellos, así que siempre diseña tus
aplicaciónes para que fallen airadamente, si una cabecera en particular esta
vacía o no existe.) Aprende a usar las clausulas try
/except
o el método
get()
para manejar los casos en que alguna de estas claves estén indefinidas,
para evitar errores:
# MAL! def mostrar_navegador(request): ua = request.META['HTTP_USER_AGENT'] # ¡Podría lanzar: KeyError! return HttpResponse("Tu navegador es %s" % ua) # BIEN (VERSION 1) def mostrar_navegador(request): try: ua = request.META['HTTP_USER_AGENT'] except KeyError: ua = 'unknown' return HttpResponse("Tu navegador es %s" % ua) # BIEN (VERSION 2) def mostrar_navegador2(request): ua = request.META.get('HTTP_USER_AGENT', 'unknown') return HttpResponse("Tu navegador es %s" % ua)
Te animamos a escribir pequeñas vistas como estas, que muestren todos los datos
request.META
, que puedas conseguir para familiarizarte con los conceptos.
La vista puede empezar así:
def atributos_meta(request): valor = request.META.items() valor.sort() html = [] for k, v in valor: html.append('<tr><td>%s</td><td>%s</td></tr>' % (k, v)) return HttpResponse('<table>%s</table>' % '\n'.join(html))
Como ejercicio trata de convertir la vista anterior, para que use el sistema de
plantillas en lugar de incrustar el código HTML en la vista. También trata de
agregar request.path
y los otros métodos HttpRequest
que vimos en la
sección anterior.
Mas allá de los metadatos básicos obtenidos de las peticiones Web, los objetos
HttpRequest
poseen dos atributos mas, que contienen información recibida de los
usuarios: request.GET
y request.POST
. Ambos atributos son como objetos
tipo diccionarios que permiten el acceso a datos GET
y POST
¿Objetos como diccionarios?
Cuando decimos que request.GET
y request.POST
son como objetos
tipo diccionario ("dictionary-like"), lo que tratamos de decirte es que se
comportan como diccionarios estándar de Python, pero técnicamente en el
fondo no son diccionarios. Por ejemplo request.GET
y request.POST
contienen ambos, métodos como get()
, keys()
y values()
, por
lo que puede iterarse sobre sus claves usando for key in request.GET
.
¿Entonces porque la distinción? bueno, porque ambos request.GET
y
request.POST
contienen métodos adicionales, que los diccionarios
normales no, llegaremos a eso dentro de poco.
Puede ser que encuentres el termino similar a "file-like objects" -- Objetos
Python que contienen algunos métodos básicos como read()
, lo que
le permite actuar a un determinado archivo como un "objeto".
Los datos POST
generalmente son recibidos de formularios (<form>
) HTML,
mientras que los datos GET
son enviados a los formularios (<form>
) o
mediante una cadena de consulta a una página URL.
Continuando con el ejemplo en curso sobre: libros, autores y editores, vamos a crear un formulario, mediante una vista muy simple que permita a los usuarios buscar libros en la base de datos mediante el titulo.
Generalmente, se necesitan dos partes para desarrollar un formulario: la interfaz de usuario en HTML y la vista que procesa los datos obtenidos o subidos por los usuarios. La primera parte es sencilla; solo necesitamos crear una vista que muestre el formulario de busqueda:
def formulario_buscar(request): return render(request, 'formulario_buscar.html')
Tal como aprendimos en el :doc:`capítulo 3<chapter03>`, la vista puede estar
en cualquier lugar de la ruta de búsqueda de Python. Pero por convensión esta
deve de ir en una vista, por lo que la colocamos en biblioteca/views.py
.
Acompañada de una plantilla formulario_buscar.html
, que deve ubicarse en
un directorio llamado templates
, dentro del directorio de la aplicación
biblioteca
, en el mismo nivel que el directorio migrations
:
<html>
<head>
<title>Buscar</title>
</head>
<body>
<form action="/buscar/" method="get">
<input type="text" name="q">
<input type="submit" value="Buscar">
</form>
</body>
</html>
El patrón para la URL deve de ir en el archivo biblioteca/urls.py
asi:
from biblioteca import views urlpatterns = [ # ... url(r'^formulario-buscar/$', views.formulario_buscar), # ... ]
(Observa que hemos importando el modulo views
directamente, en lugar de
hacer algo como esto: from biblioteca.views import buscar
, porque lo
anterior es mas elegante y entendible. Veremos la forma de aprovechar este
tipo de importaciónes en más detalle, en el :doc:`capítulo 8<chapter08>`.)
Ahora, ejecuta manage runserver
o recarga la pagina y visita:
http://127.0.0.1:8000/formulario-buscar/, donde veras una interfaz de búsqueda,
bastante simple, construida mediante un sencillo formulario:
Trata de subir el formulario, y solo conseguirás un error 404. El formulario
que apunta a la URL /buscar/
, aun no ha sido implementado. Reparemos eso
con una segunda funcion de vista y su respectiva URLconf:
.. snippet:: :filename: urls.py urlpatterns = [ # ... url(r'^formulario-buscar/$', views.formulario_buscar), url(r'^buscar/$', views.buscar), # ... ]
.. snippet:: :filename: views.py def buscar(request): if 'q' in request.GET: mensaje = 'Estas buscando: %r' % request.GET['q'] else: mensaje = 'Haz subido un formulario vacio.' return HttpResponse(mensaje)
Por el momento, esto exhibe meramente el término de búsqueda del usuario, así que podemos estar seguros, de que los datos están siendo enviado a Django correctamente, y puede darnos una ligera percepción sobre la manera en que el término de búsqueda atraviesa el sistema.
En resumen:
- El formulario HTML (
<form>
) define una variableq
. Cuando esta es subida el valor deq
es enviado mediante el metodoGET
a la URL/buscar/
. - La vista de Django maneja la URL
/buscar/
y tiene acceso al valor deq
en la peticiónrequest.GET
.
Una cosa que es importante precisar aquí, es que explícitamente verificamos
que 'q'
exista en request.GET
. Como precisamos en la sección anterior
con las peticiones request.META
, no deberías confiar en nada que sea subido
por los usuarios o incluso asume que no subieron nada en primer lugar. Si no
agregas esta verificación, los formularios vacios lanzaran un error del tipo
KeyError
en la vista:
# MAL! def buscar_no_hagas_esto(request): #¡Las siguientes lineas lanzan un error "KeyError" si no se envia 'q'! mensaje = 'Estas buscando: %r' % request.GET['q'] return HttpResponse(mensaje)
Parámetros de cadenas de consulta.
Porque los datos GET
se pasan en cadenas de consultas (por ejemplo:
/buscar/?q=django
) puedes usar request.GET
para acceder a las
variables de las cadenas de consulta. En el capítulo 3, "Introducción
a los patrones URLconfs de Django", comparamos las URL bonitas contra las
tradicionales URLs de PHP/Java tal como /tiempo/mas?horas=3
y dijimos
que te mostraríamos como hacerlo más adelante en el capítulo 7. Ahora
ya sabes cómo acceder a cadenas de parámetros en las vistas (tal como el
ejemplo horas=3
) --Solo usa request.GET
.
Los datos POST
trabajan de la misma forma que lo datos GET
-- solo usa
request.POST
en lugar de request.GET
. ¿Entonces cual es la diferencia
entre GET
y POST
? Usa GET
cuando el acto de subir un formulario
solo sea para "pedir" datos. En cambio usa POST
siempre que el acto de
subir el formulario tenga efectos secundarios -- cambiando datos, enviando
un e-mail o algo que vaya más allá de simplemente mostrar datos. En el
ejemplo de búsquedas de el ejemplo, estamos usando GET
porque la consulta no
cambia ningún dato en nuestro servidor. (Consulta
http://www.w3.org/2001/tag/doc/whenToUseGet.html si quieres aprender más sobre
las peticiones GET
and POST
.)
Ahora que hemos verificado que el metodo request.GET
es pasado apropiadamente,
anclemos la consulta de búsquedas a la base de datos.(otra vez en views.py
y
rescribamos la funcion buscar):
from django.http import HttpResponse from django.shortcuts import render from biblioteca.models import Libro def buscar(request): if 'q' in request.GET and request.GET['q']: q = request.GET['q'] libros = Libro.objects.filter(titulo__icontains=q) return render(request, 'resultados.html', {'libros': libros, 'query': q}) else: return HttpResponse('Por favor introduce un termino de búsqueda.')
Un par de notas sobre lo que hicimos:
Aparte de checar que
'q'
exista enrequest.GET
, nos aseguramos querequest.GET['q']
no sea una cadena vacía antes de pasarle la consulta a la base de datos.Estamos usando
Libro.objects.filter(titulo__icontains=q)
para consultar en la tabla libros, todos los libros que incluyan en el titulo los datos proporcionados en la consulta,icontains
es un tipo de búsqueda (como explicamos en el capítulo 5 y el apéndice B) en la que no se distinguen mayúsculas de minúsculas (case-insensitive), y que internamente usa el operadorLIKE
de SQL en la base de datos. La declaración puede ser traducida como "Obtener todos los libros que contenganq
"Esta es una forma muy simple para buscar libros. No recomendamos usar una simple consulta
icontains
en bases de datos muy grandes en producción ya que esto puede ser muy lento. (En el mundo real, es mejor usar un sistema de búsqueda personalizado de cierto tipo. Busca en la web proyectos libres de sistemas de búsquedas de texto para que te des una idea de las posibilidades.)Pasamos
libros
, como una lista de objetosLibro
a la plantilla. El código de la plantillaresultados.html
debe incluir algo como esto:
<p>Estas buscado: <strong>{{ query }}</strong></p>
{% if libros %}
<p>Libros encontrados: {{ libros|length }} libro{{ libros|pluralize }}.</p>
<ul>
{% for libros in libros %}
<li>{{ libros.titulo }}</li>
{% endfor %}
</ul>
{% else %}
<p>Ningun libro coincide con el criterio de busqueda.</p>
{% endif %}
Observa que estamos usando el filtro de plantillas pluralize
, el cual
apropiadamente agrega en la salida la "s", basado en el número de libros
encontrados.
En los capítulos anteriores te mostramos la forma más simple en la que podría trabajar un formulario. Ahora te mostraremos algunos problemas que pueden surgir y la manera de solucionarlos.
Primero, la forma en que la vista buscar()
maneja las consultas vacías es
pobre --solo mostramos un mensaje "Por favor introduce un termino de búsqueda."
lo que requiere que el usuario, tenga que dar clic de nuevo en el botón para
regresar su navegador a la pagina de busquedas. Esto es horrible y poco profesional,
si implementas algo así, tus privilegios Django serán revocados.
Sería mucho mejor volver a mostrar el formulario con los errores resaltados, para que el usuario pueda intentar nuevamente rellenarlo. La forma más fácil de hacer esto, es renderizando la plantilla otra vez, así:
from django.http import HttpResponse from django.shortcuts import render from biblioteca.models import Libro def formulario_buscar(request): return render(request, 'formulario_buscar.html') def buscar(request): if 'q' in request.GET and request.GET['q']: q = request.GET['q'] libros = Libro.objects.filter(titulo__icontains=q) return render(request, 'resultados.html', {'libros': libros, 'query': q}) else: return render(request, 'resultados.html', {'error': True})
(Observa que hemos incluido la vista formulario_buscar()
, para que puedas observar
ambas vistas en un solo lugar.)
También hemos mejorado la vista buscar()
para renderizar la plantilla
resultados.html
otra vez, si la consulta está vacía. Ya que necesitamos
mostrar los mensajes de errores en la plantilla, hemos pasado la variable error
a la plantilla. Ahora edita formulario_buscar.html
y checa la variable
error
que hemos agregado:
<html> <head> <title>Buscar</title> </head> <body> {% if error %} <p style="color: red;">Por favor introduce un termino de busqueda.</p> {% endif %} <form action="/buscar/" method="get"> <input type="text" name="q"> <input type="submit" value="Buscar"> </form> </body> </html>
Con este cambio en su lugar, tenemos una mejor aplicación, pero ahora nos
preguntamos ¿Es realmente necesaria una vista dedicada formulario_buscar()
? Tal
y como están las cosas, una petición a una URL /buscar/
(sin parámetros
GET
) mostrara un formulario vacio (pero sin errores). Podemos remover la
vista formulario_buscar()
, junto con los patrones URL asociados, a si como cambiar
la vista buscar()
para que esconda el mensaje de error cuando alguien visite
/buscar/
sin parameters GET
:
def buscar(request): error = False if 'q' in request.GET: q = request.GET['q'] if not q: error = True else: libros = Libro.objects.filter(titulo__icontains=q) return render(request, 'resultados.html', {'libros': libros, 'query': q}) return render(request, 'formulario_buscar.html', {'error': error})
Con esta vista actualizada, cuando un usuario visita /buscar/
sin
parámetros GET
, el formulario de búsqueda no mostrara los mensajes de error.
Si un usuario trata de subir un formulario con un valor vacio para 'q'
,
el formulario de búsqueda mostrara el mensaje de error en letras rojas. Y
finalmente si un usuario sube un formulario con una cadena de consulta -- es decir
con valores no vacios para 'q'
, se mostraran los resultados de la búsqueda.
Podemos realizar una mejora final para nuestra aplicación, removiendo un poco de
redundancia. Ahora que hemos comenzado a refinar las dos vistas y las URLs en
una sola llamada /buscar/
, que manejara el despliegue y el formulario de
búsquedas y tambien mostrara los resultados, por lo que el formulario en HTML en
la plantilla formulario_buscar.html
no necesita que se incruste el código en
la URL, en lugar de hacer esto:
<form action="/buscar/" method="get">
Podemos cambiarlo por esto:
<form action="" method="get">
action=""
significa "Envía el formulario con la misma URL a la pagina
actual". Con este cambio realizado, no tienes que recordar cambiar action
cada vez que necesites enlazar la vista buscar()
a otra URL.
Nuestro ejemplo de búsquedas es razonablemente simple, especificamente hablando en términos de validación de datos, ya que solamente nos aseguramos que las consultas en las búsquedas no estén vacías. Muchos formularios en HTML incluyen niveles de validación más complejos, que van más allá de asegurarse que los valores no estén vacios. Todos hemos visto mensajes de error en sitios Web como estos.
- "Por favor introduce una dirección de correo electrónica valida". 'foo' no es una dirección de correo electrónica.
- "Por favor introduce un código postal valido de 5 dígitos". '123 no es un código postal valido'
- "Por favor introduce una fecha valida en el formato YYYY-MM-DD."
- "Por favor introduce una contraseña que contenga al menos 8 caracteres y que contenga al menos un numero".
Una nota sobre validación usando Java Script
Este tema va mas allá del alcance de este libro, pero puedes usar Java Script para validar datos del lado del cliente, directamente en el navegador. Pero ten cuidado: incluso si haces esto, también debes de validar los datos de el lado del servidor. Algunas personas pueden tener Java Script desactivado y algunos usuarios maliciosos pueden subir en crudo, datos no validos directamente al manejador de formularios, para ver que daños pueden causar sus travesuras.
No hay nada que hacer en estos casos, con excepción de siempre validar los datos enviados por los usuarios de el lado del servidor (Por ejemplo en las vista de Django). Debes pensar en usar la validación en Java Script como una característica extra de usabilidad, no como la única manera de validación.
Bien, ajustemos la vista buscar()
para que valide términos de búsqueda que
contengan como máximo 20 caracteres o menos. (Para efectos del ejemplo, digamos
que los términos largos pueden hacer las consultas muy lentas.) ¿Cómo podemos
hacer eso? La cosa más simple posible seria pensar en incrustar directamente la
lógica en la vista, más o menos así:
def buscar(request): error = False if 'q' in request.GET: q = request.GET['q'] if not q: error = True elif len(q) > 20: error = True else: libros = Libro.objects.filter(titulo__icontains=q) return render(request, 'buscar_results.html', {'libros': libros, 'query': q}) return render(request, 'buscar_form.html', {'error': error})
Ahora, si tratamos de enviar una consulta que contenga más de 20 caracteres de
longitud, no nos permitirá buscar, solo obtendremos un mensaje de error. Pero el
mensaje de error actual en buscar_form.html
dice: "Por favor introduce un
termino de busqueda."
Por lo que tendremos que cambiarlo para ser mas precisos
en ambos casos.
<html> <head> <title>Buscar</title> </head> <body> {% if error %} <p style="color: red;">Por favor introduce un termino de busqueda menor a 20 caracteres.</p> {% endif %} <form action="/buscar/" method="get"> <input type="text" name="q"> <input type="submit" value="Buscar"> </form> </body> </html>
Hay algo muy raro en esto. El único mensaje de error es potencialmente confuso. ¿Por qué el mensaje para el envió de un formulario vacio menciona un límite de 20 caracteres? Los mensajes de error deben ser específicos, no deben dar lugar a ambigüedades y no deben ser confusos.
El problema está en el hecho de que estamos usando un simple valor booleano para
error
, mientras que deberíamos usar una lista de cadenas para mostrar
el mensaje de error. Esta es la forma en que podemos arreglarlo:
def buscar(request): errors = [] if 'q' in request.GET: q = request.GET['q'] if not q: errors.append('Por favor introduce un termino de busqueda.') elif len(q) > 20: errors.append('Por favor introduce un termino de busqueda menor a 20 caracteres.') else: libros = Libro.objects.filter(titulo__icontains=q) return render(request, 'resultados.html', {'libros': libros, 'query': q}) return render(request, 'formulario_buscar.html', {'errors': errors})
Entonces, necesitamos hacer algunos pequeños cambios a la plantilla
formulario_buscar.html
para que refleje ahora la forma en que estamos
pasando la lista de errors
en lugar de un valor booleano error
.
<html> <head> <title>Buscar</title> </head> <body> {% if errors %} <ul> {% for error in errors %} <li style="color: red;">{{ error }}</li> {% endfor %} </ul> {% endif %} <form action="/buscar/" method="get"> <input type="text" name="q"> <input type="submit" value="Buscar"> </form> </body> </html>
De esta forma podemos validar de forma simple las cadenas de consultas:
A pesar de que buscamos mejorar el formulario de busquedas en el ejemplo pasado,
varias veces y lo mejoramos de forma elegante, sigue siendo fundamentalmente
simple; ya que contiene únicamente un campo 'q'
. Debido a que era tan simple,
no utilizamos la librería de formularios de Django, para tratar con ello. Pero los
formularios más complejos, necesitan un tratamiento más complicado -- y ahora
desarrollaremos algo más complicado: un formulario para un sitio de contactos.
El formulario de contactos permitirá a los visitantes del sitio enviar un poco de retroalimentación junto con una dirección e-mail opcional. Después de que el formulario sea enviado y los datos validados, automáticamente enviara un mensaje vía e-email al personal del sitio.
Empezamos con la plantilla, formulario-contactos.html
.
<html>
<head>
<title>Contactanos</title>
</head>
<body>
<h1>Contactanos</h1>
{% if errors %}
<ul>
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
<form action="/contactos/" method="post">{% csrf_token %}
<p>Asunto: <input type="text" name="asunto"></p>
<p>E-mail (opcional): <input type="text" name="email"></p>
<p>Mensaje: <textarea name="mensaje" rows="10" cols="50"></textarea></p>
<input type="submit" value="Enviar">
</form>
</body>
</html>
Definimos tres campos en la plantilla: el asunto, la dirección de correo
electrónico y el mensaje. El segundo es opcional, pero los otros dos campos son
obligatorios. Observa que estamos usando el metodo="post"
en lugar de el
metodo="get"
porque al enviar el formulario, este tiene efectos secundarios
--envía un e-mail. También observa que copiamos la forma de mostrar los errores
de el código, de la anterior plantilla formulario_buscar.html
.
Si continuamos siguiendo el camino, que establecimos al crear la vista buscar()
de la sección anterior, una versión preliminar de la vista contactos()
podría
verse así:
from django.core.mail import send_mail from django.http import HttpResponseRedirect from django.shortcuts import render def contactos(request): errors = [] if request.method == 'POST': if not request.POST.get('asunto', ''): errors.append('Por favor introduce el asunto.') if not request.POST.get('mensaje', ''): errors.append('Por favor introduce un mensaje.') if request.POST.get('email') and '@' not in request.POST['email']: errors.append('Por favor introduce una direccion de e-mail valida.') if not errors: send_mail( request.POST['asunto'], request.POST['mensaje'], request.POST.get('email', '[email protected]'), ['[email protected]'], ) return HttpResponseRedirect('/contactos/gracias/') return render(request, 'formulario-contactos.html', {'errors': errors})
(Si continuas siguiendo los ejemplos, tal vez te preguntes, ¿Si debes poner la
vista en el archivo libros/views.py
. Aunque este no tenga nada que ver con
la aplicación libros, o debes de ponerla en otro lugar? Esta decisión es tuya;
a Django no le importa, con tal de que la vista apunte a la URLconf. Aunque
personalmente deberías crear un directorio separado, algo así como: contactos
,
en el mismo nivel en el que esta libros
en el árbol de directorios,
conteniendo un archivo vacio __init__.py
y una views.py
.
Algunas novedades que pasan aquí:
Estamos comprobando que
request.method
sea'POST'
. Esto únicamente será verdadero en los casos en que se envié el formulario y no en el caso de que alguien simplemente mira el formulario de contactos (En este ultimo casorequest.method será fijado como 'GET'
, porque los navegadores Web normalmente exploran usandoGET
, noPOST
.) Esto hace más agradable aislar "El formulario para mostrar" de los casos en que se necesite presentar el "Procesamiento de formularios".En lugar de usar
request.GET
, estamos usandorequest.POST
para acceder a los datos del formulario de envió. Esto es necesario porque el formulario HTML enformulario-contactos.html
usamethod="post"
. Si la vista es accedida víaPOST
, en lugar derequest.GET
estara vacía.Esta vez tenemos dos campos requeridos,
asunto
ymensaje
, así que tenemos que validar ambos. Observa que utilizamosrequest.POST.get()
y proveemos una cadena en blanco como el valor por omisión para el tercer campo; esta es una manera agradable y corta de manejar ambos casos, para evitar que falten claves y se pierdan datos.Aunque el campo de
email
no es requerido, todavía podemos validarlo si nos lo envían. Nuestro algoritmo de validación es frágil -- solo comprobamos que la cadena contenga un carácter@
. En el mundo real, necesitamos una validación más robusta (y Django nos la proveerá, en breve te mostraremos como.)Estamos usando la funcion
django.core.mail.send_mail
para enviar un e-mail. Esta funcion tiene cuatro argumentos obligatorios: el asunto y el cuerpo del mensaje, la dirección del emisor, y una lista de direcciones del destino.send_mail
es un wrapper en torno a la claseEmailMessage
de Django, la cual provee características avanzadas tales como archivos adjuntos, mensajes multiparte y un control completo sobre los encabezados del mensaje.Ten en cuenta que para usar el envió de e-mail usando
send_mail()
, tu servidor debe de estar configurado para enviar emails, y Django debe de informar al servidor sobre la salida de e-mails. Mas adelante configuraremos un servidor de correo que nos permitira enviar emails en el proceso de desarrollo.Después de que el e-mail es enviado con "éxito", redirige al usuario a otra página retornando un objeto
HttpResponseRedirect
. Te dejaremos la implementación de la pagina "enviado con éxito" (ya que es una simple vista/URLconf/plantilla) pero es necesario explicar que debes usar un redirecionamiento para dirigir al usuario a otro lugar, en vez de por ejemplo, simplemente llamar arender()
con una plantilla allí mismo.Esta es la razón: Si un usuario selecciona actualizar sobre una página que muestra una consulta
POST
, la consulta se repetirá. Esto probablemente lleve a un comportamiento no deseado, por ejemplo, que el registro se agregue dos veces a la base de datos. Redirigir luego del POST es un patrón útil que puede ayudar a prevenir este escenario. Así que luego de que se haya procesado elPOST
con éxito, siempre redirige al usuario a otra página en lugar de retornar el HTML directamente. Esta es una buena práctica de desarrollo Web
La vista trabaja, pero algunas funciones de validación son "repetitivas" Imagina
procesar un formulario con una docena de campos, ¿Realmente quieres escribir
todas esas declaraciones if
?
Otro problema es si el usuario ha cometido algún error, el formulario debería
volver a mostrarse, junto a los mensajes de error resaltados. Los campos
deberían rellenarse con los datos previamente suministrados, para evitarle al
usuario tener que volver a tipear todo nuevamente.(El formulario debería volver
a mostrarse una y otra vez, hasta que todos los campos se hayan rellenado
correctamente.) Podemos devolver manualmente los datos POST
a la
plantilla, pero tendríamos que corregir los campos en HTML para insertar el
valor apropiado en el lugar apropiado.
Finalmente esta es la vista y la plantilla final:
.. snippet:: :filename: views.py def contactos(request): errors = [] if request.method == 'POST': if not request.POST.get('asunto', ''): errors.append('Por favor introduce el asunto.') if not request.POST.get('mensaje', ''): errors.append('Por favor introduce un mensaje.') if request.POST.get('email') and '@' not in request.POST['email']: errors.append('Por favor introduce una direccion de e-mail valida.') if not errors: send_mail( request.POST['asunto'], request.POST['mensaje'], request.POST.get('email', '[email protected]'), ['[email protected]'], ) return HttpResponseRedirect('/contactos/gracias/') return render(request, 'formulario-contactos.html', { 'errors': errors, 'asunto': request.POST.get('asunto', ''), 'mensaje': request.POST.get('mensaje', ''), 'email': request.POST.get('email', ''), })
.. snippet:: html+django :filename: formulario-contactos.html <html> <head> <title>Contactanos</title> </head> <body> <h1>Contactanos</h1> {% if errors %} <ul> {% for error in errors %} <li>{{ error }}</li> {% endfor %} </ul> {% endif %} <form action="/contactos/" method="post">{% csrf_token %} <p>Asunto: <input type="text" name="asunto" value="{{ asunto }}"></p> <p>E-mail (opcional): <input type="text" name="email" value="{{ email }}"></p> <p>Mensaje: <textarea name="mensaje" rows="10" cols="50">{{ mensaje }}</textarea></p> <input type="submit" value="Enviar"> </form> </body> </html>
Como seguramente te habras dado cuenta, el proceso de validacion de datos, no es una tarea sencilla, ya que introduce una buena cantidad de oportunidades para cometer errores humanos. Esperamos que comiences a ver la oportunidad que ofrecen algunas bibliotecas de alto nivel en Django, que manejan los trabajos relacionados con la validación, por ti.
Django posee una librería llamada django.forms
, que maneja muchos tipos de
temas que hemos explorado en este capítulo --Formularios para validar y
mostrar HTML --. Sumerjámonos dentro de nuestra aplicación para formularios de
contactos, usando ahora los formularios del framework Django .
La librería "forms" de Django
A través de la comunidad de Django, puedes haber escuchado alguna
conversación sobre algo llamado django.newforms
. Cuando la gente
pregunta sobre django.newforms
, ellos se refieren ahora a django.forms
-- la librería que cubriremos en este capítulo --.
La razón del cambio de nombre es histórico. Cuando Django fue lanzado al
público por primera vez, poseía un sistema de formularios complicado y
confuso, django.forms
. Como hacía muy dificultosa la producción de
formularios, fue rescrito y llevo por nombre django.newforms
. Sin
embargo algunas personas siguieron usando el viejo sistema. Cuando Django
1.0 fue lanzado, el viejo django.forms
se fue y django.newforms
se
convirtió en django.forms
.
Lo primero que necesitas para usar el framework de formularios es definir una
clase Form
para cada formulario
HTML que quieras crear, En este caso
únicamente queremos un formulario
, así que solo necesitamos tener una clase
Form
. Esta clase puede localizarse en cualquier lugar -- incluso podemos
ponerla directamente en el archivo de vistas views.py
-- pero entre la
comunidad la convención es ponerla en un archivo separado llamado forms.py
.
Crea este archivo en el mismo directorio que views.py
e introduce lo
siguiente:
from django import forms class FormularioContactos(forms.Form): asunto = forms.CharField() email = forms.EmailField(required=False) mensaje = forms.CharField()
Esto es bastante intuitivo y muy similar a la sintaxis de modelos de Django.
Cada campo en el formulario es representado por un tipo de clase Field
--
CharField
y EmailField
son únicamente los tipos de campos usados como
atributos de la clase Form
. Cada campo es requerido por defecto, para
hacer que el campos email
sea opcional, necesitas especificar
required=False
.
Saltemos al intérprete interactivo de Python y veamos lo que esta clase puede
hacer (usando manage.py shell
).
La primer cosa que puede hacer es mostrarse a sí misma como HTML:
>>> from contactos.forms import FormularioContactos >>> f = FormularioContactos() >>> print (f) <tr><th><label for="id_asunto">Asunto:</label></th><td><input type="text" name="asunto" id="id_asunto" /></td></tr> <tr><th><label for="id_email">Email:</label></th><td><input type="text" name="email" id="id_email" /></td></tr> <tr><th><label for="id_mensaje">Mensaje:</label></th><td><input type="text" name="mensaje" id="id_mensaje" /></td></tr>
Para mayor accesibilidad, Django agrega una etiqueta <label>
a cada campo,
La ideas es hacer el comportamiento predeterminado tan optimo como sea posible.
La salida predeterminada se da en forma de tabla, usando una <table>
en
formato HTML, pero existen algunas otros tipos de salidas, por ejemplo:
>>> print f.as_ul() <li><label for="id_asunto">Subject:</label> <input type="text" name="asunto" id="id_asunto" /></li> <li><label for="id_email">Email:</label> <input type="text" name="email" id="id_email" /></li> <li><label for="id_mensaje">Message:</label> <input type="text" name="mensaje" id="id_mensaje" /></li> >>> print f.as_p() <p><label for="id_asunto">Subject:</label> <input type="text" name="asunto" id="id_asunto" /></p> <p><label for="id_email">Email:</label> <input type="text" name="email" id="id_email" /></p> <p><label for="id_mensaje">Mensaje:</label> <input type="text" name="mensaje" id="id_mensaje" /></p>
Observa que las etiquetas <table>
, <ul>
and <form>
no se han
incluido; debes definirlas por tu cuenta en la plantilla. Esto te da control
sobre el comportamiento del formulario al ser suministrado. Los elementos
label
sí se incluyen, y proveen a los formularios de accesibilidad
“desde fábrica”.
Estos métodos son solo atajos para los casos comunes, en los que es necesario "mostrar el formulario completo". Sin embargo también puedes mostrar un campo en particular:
>>> print f['asunto'] <input type="text" name="asunto" id="id_asunto" /> >>> print f['mensaje'] <input type="text" name="mensaje" id="id_mensaje" />
La segunda cuestion sobre los objetos Form
es como hacer algo de validación de
datos. Para validar datos, crea un nuevo objeto Form
y pásale un diccionario
de datos que vincule los nombres de los campos con los datos:
>>> f = FormularioContactos({'asunto': 'Hola', 'email': '[email protected]', 'mensaje': '¡Buen sitio!'})
Una vez que asocias datos, con una instancia de Form
, haz creado un
formulario "vinculado"
>>> f.is_bound True
Una instancia de formulario puede estar en uno de dos estados: bound (vinculado) o unbound (no vinculado). Una instancia bound se construye con un diccionario (o un objeto que funcione como un diccionario) y sabe cómo validar y volver a representar sus datos. Un formulario no vinculado (unbound) no tiene datos asociados y simplemente sabe cómo representarse a sí mismo.
Llama al método is_valid()
en un cualquier formulario vinculado para saber
si lo datos son validos. En el ejemplo hemos pasado un valor valido a cada campo
de la clase Form
, así que es completamente valido.:
>>> f.is_valid() True
Aun si no pasamos el campo email
, el formulario sigue siendo válido, porque
hemos especificado que el campo sea opcional con required=False
>>> f = FormularioContactos({'asunto': 'Hola', 'mensaje': '¡Buen sitio!'}) >>> f.is_valid() True
Pero si dejamos ya sea asunto
o mensaje
, el Formulario
ya no será
válido:
>>> f = FormularioContactos({'asunto': 'Hola'}) >>> f.is_valid() False >>> f = FormularioContactos({'asunto': 'Hola', 'mensaje': ''}) >>> f.is_valid() False
También puedes obtener un mensaje más especifico sobre los errores de un campo en particular, de esta forma:
>>> f = FormularioContactos({'asunto': 'Hola', 'mensaje': ''}) >>> f['mensaje'].errors [u'Este campo es obligatorio.'] >>> f['asunto'].errors [] >>> f['email'].errors []
Cada Formulario
vinculado que instanciemos contiene un atributo con una
variable llamada errors
en forma de diccionario que asocia los nombres de
los campos con la lista de mensajes de errores a mostrar.:
>>> f = FormularioContactos({'asunto': 'Hola', 'mensaje': ''}) >>> f.errors {'mensaje': [u'Este campo es obligatorio.']}
Finalmente, cuando instanciemos un Formulario
que contenga datos que han
sido encontrados validos, tendremos disponible un atributo llamado cleaned_data
.
El cual es un diccionario de datos enviados "Limpiamente". Sin embargo el framework
de formularios hace más que validar los datos, también los convierte a tipos de
datos Python.:
>>> f = FormularioContactos({'asunto': 'Hola', 'email':'[email protected]', 'mensaje': 'Buen sitio!'}) >>> f.is_valid() True >>> f.cleaned_data {'mensaje': u'Buen sitio!', 'email': u'[email protected]', 'asunto': u'Hola'}
Nuestro formulario de contactos únicamente trata con cadena, las cuales
convierte "limpiamente" en objetos Unicode --pero si utilizáramos
IntegerField
o DateField
, el framework de formularios se aseguraría
de usar cleaned_data
apropiadamente para tratar con enteros en Python o con
objetos datetime.date
para los campos dados.
Con un básico conocimiento sobre la forma en que trabajan las clases Form
,
ahora puedes ver cómo usar esta infraestructura para remplazar algo de código
repetitivo en la vista contactos()
que creamos anteriormente. Esta es
la forma en que podemos rescribir la vista contactos()
, para usar el
framework de formularios de Django:
.. snippet:: :filename: views.py from django.shortcuts import render from contactos.forms import FormularioContactos def contactos(request): if request.method == 'POST': form = FormularioContactos(request.POST) if form.is_valid(): cd = form.cleaned_data send_mail( cd['asunto'], cd['mensaje'], cd.get('email', '[email protected]'), ['[email protected]'], ) return HttpResponseRedirect('/contactos/gracias/') else: form = FormularioContactos() return render(request, 'formulario-contactos.html', {'form': form})
.. snippet:: html+django :filename: formulario-contactos.html <html> <head> <title>Contactanos</title> </head> <body> <h1>Contactanos</h1> {% if form.errors %} <p style="color: red;"> Por favor corrige lo siguiente: </p> {% endif %} <form action="" method="post">{% csrf_token %} <table> {{ form.as_table }} </table> <input type="submit" value="Enviar"> </form> </body> </html>
Trata de ejecutar esto localmente. Carga el formulario, envíalo sin datos, ahora introduce una dirección de correo electrónica no valida, y finalmente envíalo con los datos correctamente.
¡Observa cuanto código repetitivo hemos quitado!. El framework de formularios maneja la forma de mostrar el HTML, la validación, la limpieza de datos y se encarga de volver a mostrar el formulario con los errores.
Warning
Aunque dependiendo de la configuración de tu servidor de email, puedes
obtener un mensaje de error cuando llames a send_mail()
, pero ese es
otro asunto.
Aunque Python hace relativamente fácil el envió de email, usando el módulo
smtplib
, Django ofrece un par de envolturas livianas sobre él. Estos
wrappers hacen que el envió de emails se extraordinariamente rápido,
especialmente en el desarrollo de tu proyecto, cuando necesitas probar el
envió correcto de emails, por lo que Django provee soporte a plataformas que no
pueden utilizar SMPT.
El código se localiza en el modulo django.core.mail
.
En Django puedes enviar un email en dos líneas, usando el metodo send_mail()
:
Primero inicia el interprete interactivo con el comando `manage.py shell
:
from django.core.mail import send_mail send_mail('Este es el argumento', 'Aquí va el mensaje.', '[email protected]', ['[email protected]'], fail_silently=False)
El correo se envía usando el servidor SMPT, con el puerto y el host especificado
en el archivo de configuración setting.py
, mediante EMAIL_HOST
y
EMAIL_PORT
, mientras que las variables EMAIL_HOST_USER
y
EMAIL_HOST_PASSWORD
se usan para autentificarte con el servidor SMPT si así
se requiere, por otra parte EMAIL_USE_TLS
y EMAIL_USE_SSL
se utilizan
para controlar las conexiones seguras y por ultimo EMAIL_BACKEND
se utiliza
para configurar el servidor de correo a utilizar.
Por omisión Django utiliza SMTP, como la configuración por defecto. Si quieres especificarla explícitamente usa lo siguiente en el archivo de configuraciones:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
La manera más fácil de configurar el envió de email de forma local es utilizando la consola. De esta forma el servidor de correo dirige todo el email a la salida estándar, permitiéndote revisar el contenido del correo en la terminal.
Solo necesitas especificar el manejador de correo, usando la siguiente configuration:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Des esta forma los emails son redirigidos a la salida estandar de la terminal, para que los puedas visualizar.
Así como se pueden recibir correos mediante la terminal, es posible también configurar un servidor de correo "bobo", que como su nombre sugiere solo simula el envió de correos de verdad.
Para especificarlo, solo usa la siguiente configuración:
EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'
Este servidor de correo, no está diseñado para su uso en producción --ya que solo provee una forma conveniente de enviar emails que puede ser usado en desarrollo de forma local.
Otra forma de utilizar un servidor SMPT "tonto" que reciba los emails localmente y puedas visualizarlos en la terminal, es usando una clase incorporada en Python que inicia un servidor de correo con un solo comando:
python -m smtpd -n -c DebuggingServer localhost:1025
Este comando inicia el servidor de correo en el puerto 1025, en el host
localhost
. Simplemente imprime en la salida estándar las cabeceras y el
cuerpo de los emails, así que solo necesitas configurar EMAIL_HOST
y
EMAIL_PORT
respectivamente en el archivo```settings.py``.
Probablemente la primer cosa que notaras cuando renderices el formulario
localmente sea que el campo mensaje
se visualiza como un
<input type="text">
, y quisieras que fuera un <textarea>
. Podemos
arreglar esto, configurando el widget del campo.
from django import forms class ContactForm(forms.Form): asunto = forms.CharField() email = forms.EmailField(required=False) mensaje = forms.CharField(widget=forms.Textarea)
El framework de formularios separa la lógica de la presentación, para cada campo en un conjunto de widgets. Cada tipo de campo tiene un widget por defecto, pero puedes sobrescribirlo fácilmente, o proporcionar uno nuevo de tu creación.
Piensa en las clases Field como las encargadas de la lógica de validación, mientras que los widgets se encargan de la lógica de presentación.
Una de las necesidades mas comunes en cuanto a validación es comprobar que un
campo tenga un cierto tamaño, es decir que acepte un maximo de caracteres. Como
seguimos perfeccionando nuestro Formulario de contactos
, seria bueno limitar el
asunto
a 100 caracteres. Todo lo que tenemos que hacer es agregarle un valor
maximo a la opcion max_length
de un campo CharField
así:
from django import forms class FormularioContactos(forms.Form): asunto = forms.CharField(max_length=100) email = forms.EmailField(required=False) mensaje = forms.CharField(widget=forms.Textarea)
Un argumento opcional min_length
(mínimo requerido), también está disponible.
Como una mejora mas para nuestro formulario, vamos a agregarle valores iníciales
a el campo asunto
algo asi como; "¡Adoro este sitio!"
(Un poco de sugestión
mental, no le hará daño a nadie.) Para hacer esto, usamos un argumento initial
en la definición misma de la instancia del formulario
;
def contactos(request): if request.method == 'POST': form = FormularioContactos(request.POST) if form.is_valid(): cd = form.cleaned_data send_mail( cd['asunto'], cd['mensaje'], cd.get('email', '[email protected]'), ['[email protected]'], ) return HttpResponseRedirect('/contactos/gracias/') else: form = FormularioContactos(initial={'asunto': '¡Adoro tu sitio!'}) return render(request, 'formulario-contactos.html', {'form': form})
Ahora el formulario inicial mostrara un mensaje en el campo asunto
, cada vez
que el formulario sea cargado.
Observa que existe una diferencia entre pasar datos iníciales y pasar datos vinculados a un formulario. La gran diferencia es que solo estamos pasando datos iníciales , entonces el formulario no está vinculado, lo cual da a entender que no existen mensajes de error.
Imagina que hemos lanzado al público nuestro formulario de comentarios, y los correos electrónicos han empezado a llegar a montones, y nos encontramos con un problema: algunos mensajes vienen con sólo una o dos palabras, es poco probable que tengan algo interesante que decir. Por lo que decidimos adoptar una nueva politica de validación: cuatro palabras o más, por favor.
Hay varias formas de insertar nuestras propias reglas de validacion en un
formulario de Django. Si vamos a usar nuestra regla una y otra vez, podemos
crear un nuevo tipo de campo. Sin embargo, la mayoría de las validaciones que
agreguemos serán de un solo uso, y pueden agregarse directamente en el propio
formulario
.
En este caso, necesitamos validación adicional sobre el campo mensaje
, así
que debemos agregar un método clean_mensaje()
a nuestro formulario:
from django import forms class ContactForm(forms.Form): asunto = forms.CharField(max_length=100) email = forms.EmailField(required=False) mensaje = forms.CharField(widget=forms.Textarea) def clean_mensaje(self): mensaje = self.cleaned_data['mensaje'] num_palabras = len(mensaje.split()) if num_palabras < 4: raise forms.ValidationError("¡Se requieren minimo 4 palabras!") return mensaje
El sistema de formularios de Django, automáticamente busca cualquier método que
empiece con clean_
y termine con el nombre del campo. Si cualquiera de estos
métodos existe, este será llamado durante la validación.
Específicamente, el método clean_mensaje()
es llamado después de
la validación lógica para el campo por defecto (en este caso, la validación
lógica de el campo CharField
es obligatoria). Dado que los datos del campo
ya han sido parcialmente procesados, necesitamos obtenerlos desde el diccionario
self.cleaned_data
del formulario. Por lo que no debemos preocuparnos
por comprobar que el valor exista y que no esté vacio; ya que el validador
se encargara de realizar este trabajo por defecto.
Usamos una combinación de len() y split() para contar la cantidad de palabras.
Si el usuario ha ingresado pocas palabras (menos de 4), lanzamos un error
forms.ValidationError
. El texto que lleva esta excepción se mostrará al
usuario como un elemento mas de la lista de errores.
Es importante que retornemos explícitamente el valor limpio del campo al final del
método. Esto nos permite modificar el valor (o convertirlo a otro tipo de Python)
dentro de nuestro método de validación. Si nos olvidamos de retornarlo,
se retornará None
y el valor original ser perdera.
Por defecto las etiquetas HTML autogeneradas por los formularios, son creadas
para remplazar los guiones bajos con espacios y con mayúsculas la primera letra.
-- Así por ejemplo la etiqueta para el campo email
es "Email"
(¿Te suena
familiar? es el mismo algoritmo que Django usa en los modelos, para calcular los
valores predeterminados de cada campo usando verbose_name
. Los cuales
cubrimos en el :doc:`capítulo 5<chapter05>` )
Así como personalizamos los modelos en Django, tambien podemos personalizar las
etiquetas para un campo determinado. Usando para ello la etiqueta label
, así:
class ContactForm(forms.Form): asunto = forms.CharField(max_length=100) email = forms.EmailField(required=False, label='E-mail) mensaje = forms.CharField(widget=forms.Textarea)
La plantilla que usamos formulario-contactos.html
usa el
formato para tablas {{ form.as_table }}
para mostrar el formulario, pero podemos
visualizar el formulario de otras maneras, para tener un control más especifico
sobre la presentacion.
La forma más rápida de personalizar la presentación de un formulario es usando
CSS. En particular, la lista de errores puede dotarse de mejoras visuales, y el
elemento <ul class="errorlist">
tiene asignada una clase para ese propósito.
El CSS a continuación hace que nuestros errores salten a la vista, agrega lo siguiente
a formulario-contactos.html
:
<style type="text/css">
ul.errorlist {
margin: 0;
padding: 0;
}
.errorlist li {
background-color: red;
color: white;
display: block;
font-size: 10px;
margin: 0 0 3px;
padding: 4px 5px;
}
</style>
Si bien es conveniente que el HTML del formulario sea generado por nosotros, en
muchos casos la disposición por defecto no queda bien en nuestra aplicación.
{{ form.as_table }}
y similares son atajos útiles que podemos usar mientras
desarrollamos nuestra aplicación, pero todo lo que concierne a la forma en que
nuestro formulario es representado puede ser sobreescrito, casi siempre desde
la plantilla misma.
Cada widget de un campo (<input type="text">
, <select>
, <textarea>
,
o similares) puede generarse individualmente accediendo a {{ form.fieldname }}
en la plantilla y cualquier error asociado con un campo está disponible mediante
{{ form.fieldname.errors }}
. Con esto en mente, podemos construir nuestra
propia plantillas personalizada para nuestro formulario de contactos, con el
siguiente código:
<html>
<head>
<title>Contactanos</title>
</head>
<body>
<h1>Contactanos</h1>
{% if form.errors %}
<p style="color: red;">
Por favor corrige lo siguiente:
</p>
{% endif %}
<form action="" method="post">{% csrf_token %}
<div class="field">
{{ form.asunto.errors }}
<label for="id_asunto">Asunto:</label>
{{ form.asunto }}
</div>
<div class="field">
{{ form.email.errors }}
<label for="id_email">E-mail:</label>
{{ form.email }}
</div>
<div class="field">
{{ form.mensaje.errors }}
<label for="id_mensaje">Mensaje:</label>
{{ form.mensaje }}
</div>
<input type="submit" value="Enviar">
</form>
</body>
</html>
{{ form.mensaje.errors }}
muestra un <ul class="errorlist">
si se
presentan errores y muestra una cadena de caracteres en blanco si el campo
es válido (o si el formulario no está vinculado). También podemos tratar a la
variable form.mensaje.errors
como a un booleano o incluso iterar sobre el
mismo como una lista, por ejemplo:
<div class="field{% if form.mensaje.errors %} errors{% endif %}">
{% if form.mensaje.errors %}
<ul>
{% for error in form.mensaje.errors %}
<li><strong>{{ error }}</strong></li>
{% endfor %}
</ul>
{% endif %}
<label for="id_mensaje">Mensaje:</label>
{{ form.mensaje }}
</div>
En caso de errores de validación, esto agrega a la clase “errors” el
contenedor <div>
y muestra los errores en una lista ordenada.
Este capítulo concluye con el material introductorio de este libro. -- el también denominado "curriculum base". La siguiente sección del libro, en especial los capítulos 8 al 12 tratan con más detalle sobre el uso avanzado de Django, incluyendo como desplegar una aplicación Django. (:doc:`capítulo 12<chapter12>`).
Luego de estos primeros siete capítulos, deberías saber lo suficiente como para comenzar a escribir tus propios proyectos en Django. El resto del material de este libro te ayudará a completar las piezas faltantes a medida que las vayas necesitando.
Comenzaremos el :doc:`capítulo 8<chapter08>` retrocediendo un poco, volviendo para darle una mirada más de cerca a las vistas y a los URLconfs (introducidos por primera vez en el :doc:`capítulo 3<chapter03>`.