Curso Python DGA 2011/django/forms

De WikiEducator
Saltar a: navegación, buscar

Formularios

Primer formulario

Vamos a actualizar la plantilla del detalle (encuesta/detalle.html) para que tenga una etiqueta <form>

<h1>{{ encuesta.pregunta }}</h1>
 
{% if mensaje_error %}<p><strong>{{ mensaje_error }}</strong></p>{% endif %}
 
<form action="/encuestas/{{ encuesta.id }}/votar/" method="post">
{% csrf_token %}
{% for opcion in encuesta.opcion_set.all %}
    <input type="radio" name="opcion" id="opcion{{ forloop.counter }}" value="{{ opcion.id }}" />
    <label for="opcion{{ forloop.counter }}">{{ opcion.opcion }}</label><br />
{% endfor %}
<input type="submit" value="Votar" />
</form>

Mirando el código:

  • Se usa un radio button para cada opción de la encuesta. El valor de cada opción está asociado al de su id. El nombre de los radio es opcion, así cuando un usuario selecciona una opción y pulsa el botón de votar, envia por POST la información opcion=3 (con el número de id elegido)
  • La acción del formulario es /encuestas/Plantilla:Encuesta.id/votar. Los datos se envían por POST. Muy importante porque enviar datos al servidor puede alterar el estado (a diferencia de GET)
  • el forloop.counter indica cuántas veces ha entrado en el bucle.
  • Se usa {% csrf_token %} para evitar Cross Site Request Forgeries. Importante usarlo siempre con POST. Para permitir el trabajo de csrf hay que añadir un método a la vista detalle.
  1. from django.template import RequestContext
  2. # ...
  3. def detalle(request, encuesta_id):
  4.     enc = get_object_or_404(Encuesta, pk=encuesta_id)
  5.     return render_to_response('encuestas/detalle.html',
  6.         {'encuesta': enc},
  7.         context_instance=RequestContext(request))


Acción del formulario

Según la entrada de URLconf, cuando ejecutamos la acción de votar, se llama a la función votar, que gestiona los datos enviados y hace algo con ellos:

(r'^(?P<encuesta_id>\d+)/votar/$', 'votar')

La acción puede quedar así:

  1. from django.shortcuts import render_to_response, get_object_or_404
  2. from django.http import HttpResponseRedirect, HttpResponse
  3. from django.core.urlresolvers import reverse
  4. from django.template import RequestContext
  5. from encuestas.models import Encuesta, Opcion
  6.  
  7. # ...
  8.  
  9. def votar(request, encuesta_id):
  10.     enc = get_object_or_404(Encuesta, pk=encuesta_id)
  11.     try:
  12.         opcion_seleccionada = enc.opcion_set.get(pk=request.POST['opcion'])
  13.     except (KeyError, Opcion.DoesNotExist):
  14.         # Vuelve a mostrar el formulario de votación
  15.         return render_to_response('encuestas/detalle.html', {
  16.             'encuesta': enc,
  17.             'mensaje_error': "No has seleccionado ninguna opción.",
  18.         }, context_instance=RequestContext(request))
  19.     else:
  20.         opcion_seleccionada.votos += 1
  21.         opcion_seleccionada.save()
  22.         # Siempre devuelve un HttpResponseRedirect después de tratar con éxito
  23.         # un formulario POST. Así se evita enviar dos veces la información cuando
  24.         # el usuario pulsa el botón retroceder.
  25.         return HttpResponseRedirect(reverse('encuestas.views.resultado', args=(enc.id,)))
  26.     return HttpResponse(u"Estás votando la encuesta %s." % encuesta_id)

Algunas cosas que todavía no hemos visto:

  • request.POST es un objeto tipo diccionario que permite acceder a los datos enviados por su clave (request.POST['opcion'] devuelve el ID de la opción seleccionada como una cadena) Los valores de request.POST son siempre cadenas.
  • Django también proporciona request.GET para acceder a los datos pasados por GET.
  • request.POST['opcion'] lanza una excepción KeyError si no se envía esa información en POST. Por eso se trata la excepción.
  • Después de incrementar el contador de la opción, el código devuelve un HttpResponseRedirect en lugar de un HttpResponse como hace normalmente.
  • Se usa la función reverse() para evitar escribir a mano la URL en la vista. Parámetros: vista y argumentos.

La vista resultado puede ser así:

  1. def resultado(request, encuesta_id):
  2.     enc = get_object_or_404(Encuesta, pk=encuesta_id)
  3.     return render_to_response('encuestas/resultado.html', {'encuesta': enc})

Y la plantilla resultado.html:

<h1>{{ encuesta.pregunta}}</h1>
<ul>
{% for opcion in encuesta.opcion_set.all %}
    <li>{{ opcion.opcion }} -- {{ opcion.votos }} voto{{ opcion.votos|pluralize }}</li>
{% endfor %}
</ul>
<a href="/encuestas/{{ encuesta.id }}/">¿Votar otra vez?</a>

Vistas genéricas: ¡todavía menos código!!

Hay tareas muy repetitivas en el desarrollo web: obtener datos de la base de datos, según unos parámetros de una URL, cargar una plantilla y devolver la plantilla procesada. Django proporciona un atajo llamado sistema de generic views. Para convertir nuestra aplicación al uso de vistas genéricas hay que:

  • Convertir el URLconf.
  • Eliminar algunas vistas innecesarias.
  • Corregir la gestión de las URLs por la nuevas vistas

En un proyecto decidiremos antes si es conveniente el uso de vistas genéricas (mejor que refactorizar)

Modificar encuestas/urls.py URLconf

  1. from django.conf.urls.defaults import *
  2. from django.views.generic import DetailView, ListView
  3. from encuestas.models import Encuesta
  4.  
  5. urlpatterns = patterns('',
  6.     (r'^$',
  7.         ListView.as_view(
  8.             queryset=Encuesta.objects.order_by('-fecha_pub')[:5],
  9.             context_object_name='listado_ultimas',
  10.             template_name='encuestas/index.html')),
  11.     (r'^(?P<pk>\d+)/$',
  12.         DetailView.as_view(
  13.             model=Encuesta,
  14.             template_name='encuestas/detalle.html')),
  15.     url(r'^(?P<pk>\d+)/resultado/$',
  16.         DetailView.as_view(
  17.             model=Encuesta,
  18.             template_name='encuestas/resultado.html'),
  19.         name='resultado_encuesta'),
  20.     (r'^(?P<encuesta_id>\d+)/votar/$', 'encuestas.views.votar'),
  21. )


Vamos a usar dos vistas genéricas: ListView y DetailView. Cada vista tiene que conocer:

  • el modelo sobre el que actúa (model)
  • DetailView necesita la clave primaria, llamada pk.(hay que cambiar encuesta_id a pk)
  • El nombre resultado_encuesta de la vista resultado sirve para referirse a esta URL después.
  • Por defecto la plantilla de DetailView se llama <nombre aplic.>/<nombre modelo>_detail.html. en nuestro caso será: encuestas/encuesta_detail.html Se puede usar el argumento template_name
  • La plantilla de ListView se llama por defecto <nombre_modelo>_list.html
  • El contexto lo gestiona de forma autmática generando encuesta_list. La copción context_object_name permite especificar ultimas_encuestas.
  • Podemos eliminar ya las vistas creadas: index, detalle, resultado.
  • Sobre la redicrección:
return HttpResponseRedirect(reverse('resultado_encuesta', args=(enc.id,)))