Formularios
Los formularios HTML son la columna vertebral de sitios Web interactivos. Este capítulo comprende cómo se puede utilizar Django para acceder a los datos de formulario emitidos por el usuario, validarlos, y hacer algo con ellos. En el camino, cubriremos los objetos HttpRequest y Form.
Obtener datos del objeto Request
Los objetos HttpRequest, tienen una serie de atributos y de métodos que son interesantes y debería familiarizarse con ellos para saber lo que es posible hacer con ellos.
Puede usar estos atributos para obtener información acerca de la petición actual (por ejemplo el usuario / navegador web que está cargando la página actual en su sitio Django) en el momento en que se ejecuta la la función de la vista.
Información sobre la URL
Los objetos HttpRequest contienen varias piezas de información sobre la URL solicitada actualmente:
Usar siempre los atributos / métodos en lugar de codificar las direcciones URL en sus vista. Esto hace que el código sea más flexible y que pueda ser reutilizado en otros lugares. He aquí un simple ejemplo:
# BAD!
def current_url_view_bad(request):return HttpResponse(«Welcome to the page at /current/»)
# GOOD
def current_url_view_good(request):return HttpResponse(«Welcome to the page at %s» % request.path)
Otra información sobre la petición
request.META es un diccionario Python que contiene todas las cabeceras HTTP disponibles para la solicitud dada, incluyendo la dirección IP del usuario y el agente de usuario (generalmente el nombre y la versión del navegador Web). Las siguientes son algunas claves comunes en este diccionario:
- HTTP_REFERER: La URL de referencia, si la hubiese.
- HTTP_USER_AGENT: La cadena de agente de usuario (si existe) del navegador del usuario. Esto se parecerá a algo como lo siguiente: «Mozilla 5.0 (X11; U; Linux i686) Gecko/20080829 Firefox/2.0.0.17»
- REMOTE_ADDR: La dirección IP del cliente.
Usted debe usar una cláusula try / except, o un método get() para manejar el caso de claves indefinidas, como en este ejemplo:
# BAD!
def ua_display_bad(request):ua = request.META[‘HTTP_USER_AGENT’] # Might raise KeyError!
return HttpResponse(«Your browser is %s» % ua)# GOOD (VERSION 1)
def ua_display_good1(request):try:
ua = request.META[‘HTTP_USER_AGENT’]
except KeyError:
ua = ‘unknown’
return HttpResponse(«Your browser is %s» % ua)
# GOOD (VERSION 2)
def ua_display_good2(request):ua = request.META.get(‘HTTP_USER_AGENT’, ‘unknown’)
return HttpResponse(«Your browser is %s» % ua)
Le animamos a escribir una pequeña vista que muestra todos los datos de request.META para que pueda conocer lo que está disponible. Se podría parecer a esto:
def display_meta(request):
values = request.META.items()
values.sort()
html = []
for k, v in values:html.append(‘<tr><td>%s</td><td>%s</td></tr>’ % (k, v))
return HttpResponse(‘<table>%s</table>’ % ‘n’.join(html))
Información sobre datos emitidos
Más allá de los metadatos básicos acerca de la solicitud, los objetos HttpRequest tienen dos atributos que contienen la información que envía el usuario: request.GET y request.POST. Ambas son objetos como diccionarios que le dan acceso a los datos GET y POST.
Los datos POST generalmente son emitidos desde un formulario HTML, mientras que los datos GET pueden provenir de un formulario o del querystring de la URL de la página.
Un ejemplo simple de gestión de formulario
Creemos una vista simple que permita a los usuarios buscar en nuestra base de datos de libros por el título.
En general, hay dos partes para el desarrollo de un formulario: la interfaz de usuario HTML y el código de la vista que procesa los datos presentados. La primera parte es fácil, vamos a crear una vista que muestre un formulario de búsqueda:
from django.shortcuts import render_to_response
def search_form(request):
return render_to_response(‘search_form.html’)
Esta vista puede residir en cualquier parte de su ruta de acceso Python. Para este ejemplo, colocarla en books/views.py.
La plantilla de acompañamiento, search_form.html, podría tener este aspecto:
<html>
<head><title>Search</title>
</head>
<body><form action=»/search/» method=»get»>
<input type=»text» name=»q»>
<input type=»submit» value=»Search»></form>
</body>
</html>
El patrón URL en urls.py podría ser algo como esto:
urlpatterns = patterns(»,
# …
(r’^search-form/$’, views.search_form),
# …)
Ahora, si ejecuta el runserver y visita http://127.0.0.1:8000/search-form/, verá la interfaz de búsqueda. Bastante simple.
Intente emitir el formulario, sin embargo, y obtendrá un error 404 de Django. El formulario apunta a la URL /search/, que aún no ha sido implementada. Vamos a arreglar eso con una segunda función de vista:
# urls.py
urlpatterns = patterns(»,# …
(r’^search-form/$’, views.search_form),
(r’^search/$’, views.search),
# …)
# views.py
def search(request):if ‘q’ in request.GET:
message = ‘You searched for: %r’ % request.GET[‘q’]
else:
message = ‘You submitted an empty form.’
return HttpResponse(message)
Por el momento, esto sólo muestra el término de búsqueda del usuario de modo que pueda asegurarse que los datos se presentan a Django correctamente y para que pueda tener una idea de cómo los términos de la búsqueda fluyen a través del sistema. En resumen, esto es lo que sucede:
- El formulario HTML define una variable q. Cuando se emite, el valor de q se envía a través de GET (method = «get») a la URL /search/.
- La vista Django que se encarga de la dirección /search/ tiene acceso al valor de q en request.GET.
Tenga en cuenta que comprobamos explícitamente que ‘q’ existe en request.GET. Como hemos señalado, usted no debe confiar en nada presentado por los usuarios. Si no ha añadido esta verificación, cualquier emisión de un formulario vacío lanzaría KeyError en la vista:
# BAD!
def bad_search(request):# The following line will raise KeyError if ‘q’ hasn’t
# been submitted!
message = ‘You searched for: %r’ % request.GET[‘q’]
return HttpResponse(message)
Los datos POST funcionan de la misma manera que los datos GET. ¿Cuál es la diferencia entre GET y POST? Utilice GET cuando el acto de emitir el formulario es sólo una solicitud para «obtener» datos. Utilice POST siempre que el acto de emitir el formulario tenga algunos efectos secundarios de actualización de datos o enviar un e-mail, o algo más de la simple exhibición de los datos. En nuestro ejemplo de búsqueda de libro, estamos utilizando GET porque la consulta no cambia ningún dato en nuestro servidor. Ahora que hemos verificado que request.GET se está pasando correctamente, vamos a conectar la consulta de búsqueda del usuario con nuestra base de datos de libros (de nuevo, en views.py):
from django.http import HttpResponse
from django.shortcuts import render_to_response
from mysite.books.models import Bookdef search(request):
if ‘q’ in request.GET and request.GET[‘q’]:
q = request.GET[‘q’]
books = Book.objects.filter(title__icontains=q)
return render_to_response(‘search_results.html’, {‘books’: books, ‘query’: q})else:
return HttpResponse(‘Please submit a search term.’)
El código de la plantilla search_results.html podría ser algo como esto:
<p>You searched for: <strong>{{ query }}</strong></p>
{% if books %}
<p>Found {{ books|length }} book{{ books|pluralize }}.</p>
<ul>{% for book in books %}
<li>{{ book.title }}</li>
{% endfor %}</ul>
{% else %}
<p>No books matched your search criteria.</p>
{% endif %}
Mejorar nuestro ejemplo de gestión de formulario simple
En primer lugar, nuestra gestión de la vista search() de una consulta vacía es pobre – estamos mostrando solo un mensaje «Por favor, envíe un término de búsqueda», que requiere que el usuario pulse el botón Atrás del navegador. Esto es horrible y poco profesional.
Sería mucho mejor volver a mostrar el formulario, con un error sobre él, de modo que el usuario pueda volver a intentarlo inmediatamente. La forma más sencilla de hacerlo sería la de renderizar la plantilla de nuevo, de esta forma:
from django.http import HttpResponse
from django.shortcuts import render_to_response
from mysite.books.models import Bookdef search_form(request):
return render_to_response(‘search_form.html’)
def search(request):
if ‘q’ in request.GET and request.GET[‘q’]:
q = request.GET[‘q’]
books = Book.objects.filter(title__icontains=q)
return render_to_response(‘search_results.html’, {‘books’: books, ‘query’: q})else:
return render_to_response(‘search_form.html’, {‘error’: True})
Hemos mejorado search() renderizando la plantilla search_form.html de nuevo, si la consulta está vacía. Y ya que tenemos que mostrar un mensaje de error en esa plantilla, pasamos una variable de plantilla. Ahora podemos editar search_form.html para comprobar la variable de error:
<html>
<head><title>Search</title>
</head>
<body>{% if error %}
<p style=»color: red;»>Please submit a search term.</p>
{% endif %}
<form action=»/search/» method=»get»><input type=»text» name=»q»>
<input type=»submit» value=»Search»></form>
</body>
</html>
Todavía podemos utilizar esta plantilla desde nuestra vista original, search_form(), ya que search_form() no pasa el error a la plantilla – el mensaje de error no se mostrará en ese caso.
Con este cambio es una aplicación mejor, pero ahora surge la pregunta: ¿es una vista search_form() dedicada realmente necesaria? Tal como está, una solicitud a la dirección URL /search/ (sin los parámetros GET) mostrará el formulario vacío (pero con un error). Podemos eliminar la vista search_form(), junto con su patrón URL asociado, siempre y cuando cambiemos search() para ocultar el mensaje de error cuando alguien visite /search/ sin parámetros GET:
def search(request):
error = False
if ‘q’ in request.GET:q = request.GET[‘q’]
if not q:error = True
else:
books = Book.objects.filter(title__icontains=q)
return render_to_response(‘search_results.html’, {‘books’: books, ‘query’: q})return render_to_response(‘search_form.html’, {‘error’: error})
En esta vista actualizada, si un usuario visita /search/ sin parámetros GET, verá el formulario de búsqueda sin mensaje de error. Si un usuario envía el formulario con un valor vacío para ‘q’, verá el formulario de búsqueda con un mensaje de error. Y, por último, si un usuario envía el formulario con un valor no vacío de ‘q’, verá los resultados de búsqueda.
Podemos hacer una mejora final a esta aplicación, para quitar un poco de redundancia. Ahora que hemos mezclado las dos vistas y URLs en una sola y /search/ maneja tanto la pantalla del formulario de búsqueda como la del resultado, el formulario HTML en search_form.html no tiene que codificar una URL a pelo. En lugar de esto:
puede cambiarse a:
El action = «» significa «Enviar el formulario a la misma URL que la página actual.» Con este cambio, usted no tendrá que acordarse de cambiar la acción, incluso si alguna vez la vista search() apunta a otra URL.
Validación simple
Nuestro ejemplo de búsqueda es todavía bastante simple, especialmente en términos de validación de sus datos, hacemos una mera comprobación para asegurar que la consulta de búsqueda no está vacía. Muchos de los formularios HTML incluyen un nivel de validación que es más complejo que asegurar que el valor no está vacío. Todos hemos visto la siguientes mensajes de error en los sitios Web:
«Por favor introduzca una dirección de correo electrónico».
«Por favor, introduzca un código postal de cinco dígitos válido EE.UU.»
«Por favor, introduzca una fecha válida con formato AAAA-MM-DD».
«Por favor, introduzca una contraseña que sea por lo menos de 8 caracteres de longitud y contenga al menos un número».
Vamos a afinar la vista search() para validar que el término de búsqueda sea menor o igual a 20 caracteres de largo. ¿Cómo podemos hacerlo? Lo más sencillo sería integrar la lógica directamente en la vista, así:
def search(request):
error = False
if ‘q’ in request.GET:q = request.GET[‘q’]
if not q:error = True
elif len(q) > 20:
error = True
else:
books = Book.objects.filter(title__icontains=q)
return render_to_response(‘search_results.html’,{‘books’: books, ‘query’: q})return render_to_response(‘search_form.html’, {‘error’: error})
Ahora bien, si se intenta emitir una consulta de búsqueda mayor de 20 caracteres de largo, obtendrá un mensaje de error. Pero ese mensaje de error en search_form.html actualmente dice: «Por favor, introduzca un término de búsqueda.», así que tendremos que cambiarlo para ser exactos en ambos casos (una búsqueda vacía o un término de búsqueda demasiado largo).
<html>
<head><title>Search</title>
</head>
<body>{% if error %}
<p style=»color: red;»>Please submit a search term
20 characters or shorter.</p>
{% endif %}
<form action=»/search/» method=»get»><input type=»text» name=»q»>
<input type=»submit» value=»Search»></form>
</body>
</html>
Hay algo mal en esto. Nuestro único mensaje de error es potencialmente confuso. ¿Por qué el mensaje de error para un valor vacío menciona nada sobre un límite de 20 caracteres? Los mensajes de error deben ser específicos y claros.
El problema es que estamos utilizando un solo valor boolean para el error, cuando habría que utilizar una lista de cadenas de mensajes de error. He aquí cómo podemos solucionarlo:
def search(request):
errors = []
if ‘q’ in request.GET:q = request.GET[‘q’]
if not q:errors.append(‘Enter a search term.’)
elif len(q) > 20:
errors.append(‘Please enter at most 20 characters.’)
else:
books = Book.objects.filter(title__icontains=q)
return render_to_response(‘search_results.html’, {‘books’: books, ‘query’: q})return render_to_response(‘search_form.html’, {‘errors’: errors})
Entonces tenemos que hacer un pequeño ajuste en la plantilla search_form.html para reflejar que se pasó una lista de errores, en lugar de un error boolean:
<html>
<head><title>Search</title>
</head>
<body>{% if errors %}
<ul>{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
<form action=»/search/» method=»get»><input type=»text» name=»q»>
<input type=»submit» value=»Search»></form>
</body>
</html>
Hacer un formulario de contacto
Aunque hemos reiterado en el ejemplo del formulario de búsqueda de libros varias veces y lo hemos mejorado, sigue siendo muy sencillo: basta con un solo campo, ‘q’. Al ser tan simple, ni siquiera usamos la librería de formularios de Django para tratar con él. Pero las formas más complejas requieren tratamientos más complejos, y ahora vamos a desarrollar algo más complejo: un formulario de contacto de la web que permite a los usuarios del sitio envíar comentarios, junto con un e-mail de retorno. Después que el formulario es emitido y los datos son validados, automáticamente le enviaremos un mensaje por correo electrónico al personal del sitio.
Empezaremos con nuestra plantilla, contact_form.html:
<html>
<head><title>Contact us</title>
</head>
<body><h1>Contact us</h1>
{% if errors %}
<ul>{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}</ul>
{% endif %}<form action=»/contact/» method=»post»>
<p>Subject: <input type=»text» name=»subject»></p>
<p>Your e-mail (optional): <input type=»text» name=»e-mail»></p>
<p>Message: <textarea name=»message» rows=»10″ cols=»50″></textarea></p>
<input type=»submit» value=»Submit»></form>
</body>
</html>
Hemos definido tres campos: el asunto, la dirección de correo electrónico y el mensaje. El segundo es opcional, pero los otros dos campos son obligatorios. Tenga en cuenta que estamos usando method = «post» aquí en lugar de method = «get» ya que este formulario de emisión tiene un efecto secundario – que envía un e-mail. Si seguimos el camino establecido por nuestra vista search() de la sección anterior, una versión de nuestra vista contact() podría tener este aspecto:
from django.core.mail import send_mail
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_responsedef contact(request):
errors = []
if request.method == ‘POST’:if not request.POST.get(‘subject’, »):
errors.append(‘Enter a subject.’)
if not request.POST.get(‘message’, »):
errors.append(‘Enter a message.’)
if request.POST.get(‘e-mail’) and ‘@’ not in request.POST[‘e-mail’]:
errors.append(‘Enter a valid e-mail address.’)
if not errors:
send_mail(request.POST[‘subject’], request.POST[‘message’],
request.POST.get(‘e-mail’, ‘noreply@example.com’),
[‘siteowner@example.com’],)
return HttpResponseRedirect(‘/contact/thanks/’)return render_to_response(‘contact_form.html’,{‘errors’: errors})
Varias cosas nuevas están sucediendo aquí:
- Comprobamos que request.method es ‘POST’. Esto será cierto sólo en el caso de una emisión del formulario, no será cierto si alguien está solamente viendo el formulario de contacto. Esto hace que sea una buena forma de aislar el caso de «pantalla del formulario» del caso de»transformación del formulario».
- En lugar de request.GET, estamos usando request.POST para acceder a los datos del formulario emitido. Esto es necesario porque el código de contact_form.html usa method = «post».
- Contamos con dos campos obligatorios, asunto y mensaje, así que tenemos que validar ambos. Notar que estamos utilizando request.POST.get() y proporcionando una cadena en blanco como el valor por defecto.
- Aunque el campo de correo electrónico no es obligatorio, debemos aún validarlo si es emitido. Nuestro algoritmo de validación aquí es frágil – estamos comprobando que la cadena contiene un carácter @. En el mundo real, necesitaríamos una validación más robusta.
- Estamos usando la función django.core.mail.send_mail para enviar un e-mail. Esta función tiene cuatro argumentos necesarios: el asunto del e-mail, el cuerpo del correo electrónico, la dirección del emisor, y una lista de direcciones de los destinatarios. send_mail está contenida en la clase de Django E-mailMessage, que proporciona características avanzadas tales como archivos adjuntos, emails multiplart, y el control total de los encabezados del correo electrónico.
- Después de enviar el e-mail, redirigimos a una página de «éxito» devolviendo un objeto HttpResponseRedirect. Usted siempre debe enviar una redirección para el éxito de las peticiones POST. Es una de las mejores prácticas del desarrollo web.
Esto vista funciona, pero las funciones de validación son enrevesadas. Imagine la tramitación de un formulario con una docena de campos, ¿de verdad quieres tener que escribir todas esas sentencias if?
Otro problema es volver a mostrar el formulario. En el caso de errores de validación, es mejor práctica volver a mostrar el formulario con los datos presentados anteriormente ya rellenados para que el usuario puede ver lo que hizo mal (y no tener que volver a introducir los datos en los campos que ha emitido correctamente). Nosotros manualmente podríamos pasar los datos POST de nuevo a la plantilla, pero habría que editar cada campo HTML para insertar el valor adecuado en el lugar adecuado:
# views.py
def contact(request):errors = []
if request.method == ‘POST’:if not request.POST.get(‘subject’, »):
errors.append(‘Enter a subject.’)
if not request.POST.get(‘message’, »):
errors.append(‘Enter a message.’)
if request.POST.get(‘e-mail’) and ‘@’ not in request.POST[‘e-mail’]:
errors.append(‘Enter a valid e-mail address.’)
if not errors:
send_mail(
request.POST[‘subject’],
request.POST[‘message’],
request.POST.get(‘e-mail’, ‘noreply@example.com’),
[‘siteowner@example.com’],
)return HttpResponseRedirect(‘/contact/thanks/’)
return render_to_response(‘contact_form.html’, {‘errors’: errors,’subject’: request.POST.get(‘subject’, »),
‘message’: request.POST.get(‘message’, »),’e-mail’: request.POST.get(‘e-mail’, »),
})
# contact_form.html
<html>
<head><title>Contact us</title>
</head>
<body><h1>Contact us</h1>
{% if errors %}
<ul>{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}</ul>
{% endif %}
<form action=»/contact/» method=»post»><p>Subject: <input type=»text» name=»subject» value=»{{ subject }}»></p>
<p>Your e-mail (optional):<input type=»text» name=»e-mail» value=»{{ e-mail }}»>
</p>
<p>Message:<textarea name=»message» rows=»10″ cols=»50″>
**{{ message }}**
</textarea></p>
<input type=»submit» value=»Submit»></form>
</body>
</html>
Se trata de una gran cantidad de código, e introduce un montón de posibilidades de error humano. Veremos alguna librería de alto nivel que gestione las tareas relacionadas con los formularios y la validación.
Su primera clase de formulario
Django viene con una librería de formularios, llamada django.forms, que se encarga de muchas de las cuestiones que hemos estado viendo en este capítulo – desde la muestra de formularios HTML a la validación. Volvamos a hacer nuestro formulario de contacto utilizando el marco de formularios de Django.
La principal manera de utilizar el marco de formularios es definir una clase Form por cada
Deja tu comentario