Curso Python. Volumen XIX: Framework Django. Parte XI

Escrito por Javier Ceballos Fernández

Bienvenidos un día más al curso de Python, en este capítulo os vamos a enseñar a utilizar los formularios y las vistas genéricas dentro de nuestra aplicación Django. Para ello, seguiremos con el ejemplo que empezamos, es decir, nuestra aplicación de encuestas. Así que pongámonos manos a la obra.

Crear un formulario simple

Vamos a trabajar con la plantilla de detalle que creamos en “polls/detail.html”. A esta plantilla le vamos a agregar un elemento HTML <form> como os mostramos a continuación:

polls/templates/polls/detail.html</pre>
<h1>{{ question.question_text }}</h1>

{% if error_message %}
<strong>{{ error_message }}</strong>
{% endif %}
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% for choice in question.choice_set.all %}
    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
    <label for="choice{{ forloop.counter }}">{{ choice.texto_opcion }}</label>
{% endfor %}
<input type="submit" value="Vote" />
</form>

Esta plantilla ahora va a mostrar “radio button” para cada opción de la pregunta. El “value” de cada botón es el ID asociado a cada opción de la pregunta. El nombre de este elemento es “choice”. Esto significa que cuando alguien elige una de las opciones y envía el “form”, se envía “choice=#” como data del POST, donde “# “es el ID de la opción elegida.

Asignamos como acción del formulario {% url ‘polls:vote’ question.id %}, y como método “post”. Utilizar el formulario con el método “post” es muy importante, porque la acción de enviar el formulario va a modificar datos del lado del servidor. Cada vez que se crean formularios que alteren datos del lado del servidor, se debería de utilizar como método del formulario el “post”.

“forloop.counter” indica cuántas veces se ha realizado el bucle “for”.

Como estamos creando un formulario “Post”, necesitamos preocuparnos por “Cross Site Request Forgeries (CSRF)”. “Django” ya se ha preocupado por nosotros y nos ofrece un sistema muy sencillo de utilizar para poder protegernos, se trata de utilizar en todos los formularios hagan su método “Post” contra una URL interna que utilice en su plantilla la etiqueta {% csrf_token %}.

Ahora vamos a crear una vista que utilice los datos enviados. Recordaros que en los tutoriales anteriores hemos creado un “configurador de URL” que contenía las siguientes líneas:

polls/urls.py</pre>

url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),

Vamos a modificar la función “vote()” que ya hicimos en capítulos anteriores, por lo que abrimos el fichero “polls/view.py” y agregamos el siguiente código. polls/views.py.

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse

from .models import Opcion, Pregunta

# ...

def vote(request, question_id):
    p = get_object_or_404(Pregunta, pk=question_id)
    try:
        selected_choice = p.opcion_set.get(pk=request.POST['choice'])
    except (KeyError, Opcion.DoesNotExist):
    # Redisplay the question voting form.
    return render(request, 'polls/detail.html', {
        'question': p,
        'error_message': "You didn't select a choice.",
    })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # Always return an HttpResponseRedirect after successfully dealing
        # with POST data. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse('polls:results', args=(p.id,)))

A continuación vamos a explicaros ciertos elementos del código que no hemos utilizado hasta ahora:

“Request.POST” es un objeto que nos permite acceder a los datos enviados usando los nombres como clave. En este caso request.POST[‘choice’] nos devuelve el ID de la opción elegida, como “string”. Los valores que nos devuelve este objeto son siempre strings.

También comentaros que “Django” también provee “request.GET” que funciona del mismo modo que “request.POST” y se utiliza cuando se haga una llamada del tipo “GET”.

Comentaros que si la función “request.POST[‘choice’]” lanzase una excepción “KeyError” es porque los de “choice” no se encuentran en los datos del POST. El código que hemos escrito comprueba si esta excepción es lanzada, y en caso afirmativo se vuelve a mostrar el formulario con un mensaje de error.

Después de incrementar el contador de la opción, el código devuelve un “HttpResponseRedirect” en lugar de un “HttpResponse”. Esto es debido a que “HttpResponseRedirect” lo usamos para redirigir al usuario a la URL que le hemos proporcionado a la función.

Utilizamos la función “reverse()” en el constructor de “HttpResponseRedirect”. Debido a que esta función nos ayuda a no escribir explícitamente una URL en la función de la vista. Esta función se le pasa como argumentos la vista a la que queremos dirigirnos y las variables que necesitemos. En este caso, usando el “configurador de URL” que configuramos en los capítulos anteriores, esta llamada “reverse()” devolverá un “string” como el siguiente:

'/polls/3/results/'

Donde 3 es el valor de p.id. Esta URL nos va a redirigir, llamando a la vista “results” para mostrar la página final. Después de que alguien vota en una encuesta, la vista “vote()” lo redirige a la página de resultados de la pregunta. Por lo que vamos a crear esta vista:

polls/views.py

from django.shortcuts import get_object_or_404, render

def results(request, question_id):
    question = get_object_or_404(Pregunta, pk=question_id)
    return render(request, 'polls/results.html', {'question': question})

Como comprobaréis, esta vista es casi idéntica a la vista “detail()”. La única diferencia es el nombre de la plantilla que utilizamos. Más adelante solucionaremos esta redundancia.

Ahora, creamos la plantilla para “polls/results.html”:

polls/templates/polls/results.html

<h1>{{ question.texto_pregunta }}</h1>

<ul>
{% for choice in question.opcion_set.all %}
    <li>{{ choice.texto_opcion }} -- {{ choice.votos }} vote{{ choice.votos|pluralize }}</li>
{% endfor %}
</ul>
<a href="{% url 'polls:detail' question.id %}">Vote again?</a>

Después de esto nos dirigimos a “/polls/1/” en nuestro navegador y votamos en la encuesta. Deberíamos ver una página de resultados que se actualiza cada vez que uno vota. Si se envía el formulario sin elegir una opción, se debería mostrar un mensaje de error.

Usando las vistas genéricas (generic views): Menos código es mejor. Las vistas “detail()” y “results()” son muy sencillas, y además son redundantes. La vista “index()” que muestra una lista de preguntas, también es muy similar.

Estas vistas representan un caso común en el desarrollo web básico: obtener datos de una base de datos de acuerdo a un parámetro pasado en la URL, cargar una plantilla y devolver la plantilla renderizada. Por ser algo tan común, “Django” una vez más nos proporciona un atajo y es el sistema “generic views” (vistas genéricas). Las vistas genéricas abstraen patrones comunes, al punto en que uno no necesita prácticamente escribir código Python en una aplicación.

Vamos a convertir nuestra aplicación para usar vistas genéricas, y poder borrar parte de nuestro código original. Son solamente unos pocos pasos:

  1. Convertir el “configurador de URL”.
  2. Borrar algunas de las vistas que teníamos, ya que no van a ser necesarias.
  3. Arreglar el manejo de URL para las nuevas vistas.

Comentaros que este no es un paso que haya que hacer siempre, cuando uno diseña una aplicación web deberá pensar en un principio si usa o no esta solución, en el tutorial lo realizamos de este modo ya que queríamos que aprendiérais a crear vistas.

Modificando el “configurador de URL”

Primero, abrimos el “configurador de URL” que está en el fichero “polls/urls.py” y lo modificamos del siguiente modo:

polls/urls.py

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$', views.IndexView.as_view(), name='index'),
    url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'),
    url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'),
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

Fijaros que el nombre del patrón que se busca en la segunda y tercera expresión regular cambió de <question_id> a <pk>.

Modificando las vistas

A continuación, vamos a borrar nuestras viejas vistas “index”, “detail”, y “results” para usar las “generic views” de “Django”. Para ello, abrimos el archivo “polls/views.py” y lo modificamos del siguiente modo:

polls/views.py</pre>

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.views import generic
from .models import Opcion, Pregunta

class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

def get_queryset(self):
    """Return the last five published questions."""
    return Pregunta.objects.order_by('-fecha_publi')[:5]

class DetailView(generic.DetailView):
    model = Pregunta
    template_name = 'polls/detail.html'

class ResultsView(generic.DetailView):
    model = Pregunta
    template_name = 'polls/results.html'

def vote(request, question_id):
... # same as above

Estamos usando dos vistas genéricas: “ListView” y “DetailView”. Estas dos vistas nos abstraen de los conceptos de “mostrar una lista de objetos” y “mostrar el detalle de un objeto particular”, respectivamente. Cada vista genérica necesita saber sobre qué modelo actuar. Esto se define usando el atributo “model”. La vista genérica “DetailView” espera el valor de clave primaria capturado de la URL con nombre “\pk\”, entonces cambiamos “question_id” a “pk”.

Por defecto, la vista genérica “DetailView” usa una plantilla llamada “<app name>/<model name>_detail.html”. En nuestro caso, usará la plantilla “polls/question_detail.html”. El argumento “template_name” es usado para indicarle a “Django” que utilice un nombre específico en la plantilla y no el nombre de autogenerado por defecto. También especificamos “template_name” para la vista results, de este modo, nos aseguramos que la vista de resultados y la de detalle tienen un aspecto diferente al renderizarse, aunque ambas estén usando “DetailView” por detrás.

Del mismo modo, la vista genérica “ListView” usa una plantilla por defecto llamada “<app name>/<model name>_list.html”; usamos “template_name” para indicarle a “ListView” que use la plantilla ya existente “polls/index.html”.

Las plantillas recibían un contexto que contenía las variables “question” y “latest_question_list”. Para “DetailView” la variable “question” es proporcionada de manera automática, esto es debido a que utilizamos un modelo “Django”. Al utilizar este modelo, “Django” puede determinar un nombre adecuado para la variable de contexto. Sin embargo, para “ListView”, el nombre de variable de contexto generado automáticamente es “question_list”. Para sobreescribir este valor, pasamos la opción “context_object_name”, especificando que queremos usar “latest_question_list” como nombre. Otra alternativa sería cambiar las plantillas para adaptarlos a los nombres por defecto, pero creemos que es mucho más sencillo indicar a “Django” el nombre que queremos que utilice para las variables.

Una vez realizado estos cambios ya tenemos lista la aplicación, si la ejecutáis veréis que obtendremos el mismo resultado.

Esto es todo por hoy. Como podéis comprobar el framework “Django” siempre nos aporta herramientas para hacernos más fácil la tarea de programar. Os invitamos como siempre, a que sigáis explorando este framework y probando. Y para todos los que se acaban de incorporar indicarles que tenemos un índice con todos los capítulos del curso, ya que nunca es tarde para empezar.

Continúa leyendo
  • DAIRO Galeano

    He venido siguiendo el curso con el ejemplo al pie de la letra y todo iba bien hasta ahora que al modificar view.py me da este error IndexView is missing a QuerySet. Define IndexView.model, IndexView.queryset, or override IndexView.get_queryset().

Últimos análisis

Valoración RZ
9
Valoración RZ
8
Valoración RZ
8
Valoración RZ
8
Valoración RZ
8
Valoración RZ
10
Valoración RZ
8