McM
mcm@noway.es
Contact me: noway.es
Contact me

First Django Simple Application(Page5)

URLs y templates

Como hemos comentado Django está pensado para reescribir el menor número de lineas posible. Con tal objeto se creo ese sistema de plantillas y urls. Básicamente cada url ha de estar definida en urls.py donde se especifica que vista atenderá la petición. La vista interactuará con el framework según se requiera y devolverá una template renderizado en un HTML.

Los pasos a seguir son:

  • definir las url con expresiones regulares
  • definir cada una de las vistas que atienden a esas url

En la aplicación ejemplo tendremos 4 páginas:

  • Poll “archive” page – displays the latest few polls.
  • Poll “detail” page – displays a poll question, with no results but with a form to vote.
  • Poll “results” page – displays results for a particular poll.
  • Vote action – handles voting for a particular choice in a particular poll.

Definimos a acontinuación la URL. El sitema de mapeo de URLs se basa en expresiones regulares, esto hace posible no tener que rescribir cada una de las vistas. Dentro de urls.py cada linea responderá a la siguiente expresión:

(regular expression, Python callback function [, optional dictionary])

Al crear un proyecto, inmediatamente se configurá la siguiente propiedad:

ROOT_URLCONF = 'prueba.urls'

Indica donde tiene que acudir Django para ver el mapeo de las URL.

Así pues añadiremos los siguientes patrones:

from django.conf.urls.defaults import *

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    (r'^polls/$', 'prueba.polls.views.index'),
    (r'^polls/(?P<poll_id>\d+)/$', 'prueba.polls.views.detail'),
    (r'^polls/(?P<poll_id>\d+)/results/$', 'prueba.polls.views.results'),
    (r'^polls/(?P<poll_id>\d+)/vote/$', 'prueba.polls.views.vote'),

    (r'^admin/', include(admin.site.urls)),
)

Nos falta definir pues las vistas index, detail, results y vote. Escribiremos de momento una vista simple para el index:

mcm@McM:/tmp/prueba$ vim polls/views.py

from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world. You're at the poll index.")

Si arrancamos el servidor de aplicaciones

y visualizamos la siguiente url http://localhost:8000/polls/

Mostrará una simple pantalla con el texto:

"Hello, world. You're at the poll index."

Todas las vistas han de devolver un objeto HTTPResponse o una plantilla a renderizar.

Vamos a complicar ahora la vista y creamos la primera plantilla. Para empezar a usar plantillas hemos de seguir los siguientes pasos:

  • Editar el archivo de configuración del proyecto settings.py e indicar el path donde van a ir las plantillas:

    TEMPLATE_DIRS = (
    "/tmp/prueba/templates/"
    )

  • crear una plantilla, por ejemplo, index.html

    mcm@McM:/tmp/prueba$ mkdir templates
    mcm@McM:/tmp/prueba$ mkdir templates/polls

    mcm@McM:/tmp/prueba$ vim templates/polls/index.html

    {% if latest_poll_list %}
        <ul>
        {% for poll in latest_poll_list %}
            <li>{{ poll.question }}</li>
        {% endfor %}
        </ul>
    {% else %}
        <p>No polls are available.</p>
    {% endif %}

    Observamos aquí las etiquetas típicas de Django, siempre van entre los caracteres {% %}. Se pueden realizar comparaciones como aqui if, ifequal ifnotequal, for y pintar listas que se pasan en las vistas, como en este caso latest_poll_list. Este latest_poll_list se pasara como se muestra a continuación en la vista que despacha la plantilla index.html.

  • Modificar la vista para que empiece a utilizar la plantilla en cuestión:

    mcm@McM:/tmp/prueba$ vim polls/views.py

    from django.template import Context, loader
    from prueba.polls.models import Poll
    from django.http import HttpResponse

    def index(request):
        latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
        t = loader.get_template('polls/index.html')
        c = Context({
            'latest_poll_list': latest_poll_list,
        })
        return HttpResponse(t.render(c))

Con ello le estamos diciendo que cargue la plantilla index.html dentro del directorio polls dentro del directorio de las plantillas y le estamos pasando variables de contexto, una en concreto: latest_poll_list que es una lista de todas las encuestas creadas ordenadas por fecha de publicación: latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]

Arrancando de nuevo el servidor de aplicaciones:

mcm@McM:/tmp/prueba$ python manage.py runserver

y accediendo a http://localhost:8000/polls/
observaremos que muestran todas las encuestas creadas.

Es altamente recomendable en lugar de usar lo anterior, usar lo que Django llama accesos directos (shortcut). Es muy comun usar el render_to_response donde se pasa la plantilla y el contexto directamente:

from django.shortcuts import render_to_response
from mysite.polls.models import Poll

def index(request):
    latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
    return render_to_response('polls/index.html', {'latest_poll_list': latest_poll_list})

Otro shortcut típico es el de página de error. Para ello definimos una nueva vista detail:

from django.shortcuts import render_to_response, get_object_or_404
# ...
def detail(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)
    return render_to_response('polls/detail.html', {'poll': p})

Aunque normalmente se suelen personalizar las páginas de error. Vemos que en este caso se utiliza la plantilla detail.html que todavia no hemos creado y se le pasa el objeto poll seleccionado si existe. En caso de no existir devuelve una página de error genérica.

Para terminar de definir detail, podremos definir una simple plantilla:

mcm@McM:/tmp/prueba$ vim templates/polls/detail.html


{{ poll }}

Esto mostrará el objeto poll. No es de gran utilidad pero para el ejemplo en cuestión vale. Podríamos mostar sus opciones con esta otra plantilla:

<h1>{{ poll.question }}</h1>
<ul>
{% for choice in poll.choice_set.all %}
    <li>{{ choice.choice }}</li>
{% endfor %}
</ul>

Recordad que al pasarnos el objeto poll, tenemos acceso a todos sus parametros y métodos. De esta manerá arrancando el servidor de aplicaciones:

mcm@McM:/tmp/prueba$ python manage.py runserver

y accediendo a la página:

http://localhost:8000/polls/1/

Obtendremos

"What's up?

* Not much
* The sky
* Just hacking again
"

Según se vaya avanzando en el diseño se podrá introducir estilos, javascripts, imagenes que hagan más atractiva las plantillas.

Si accedemos a una encuesta que no existe, mostrará una página genérica de error:

http://localhost:8000/polls/2/

"
Page not found (404)
Request Method: GET
Request URL: http://localhost:8000/polls/2/
"

Se puede hacer muchisimas cosas con las plantillas y merece la pena detenerse un rato a leer la guia: http://docs.djangoproject.com/en/dev/topics/templates/#topics-templates

Para no centralizar todas las url en el archivo urls.py del proyecto se puede hacer lo siguiente:

- En el urls.py del proyecto:

urlpatterns = patterns('',
    (r'^polls/', include('prueba.polls.urls')),

y en la aplicación polls creamos un archivo que se llame urls.py donde incluimos las urls:

urlpatterns = patterns('prueba.polls.views',
    (r'^$', 'index'),
    (r'^(?P<poll_id>\d+)/$', 'detail'),
    (r'^(?P<poll_id>\d+)/results/$', 'results'),
    (r'^(?P<poll_id>\d+)/vote/$', 'vote'),
)

De esta manera queda mucho más organizado y limpio el proyecto.

Para terminar realizaremos la página de votar y ver resultados. Como siempre necesitamos las tres cosas fundamentales:

  • configurar urls.py (ya lo tenemos)

    (r'^(?P
    \d+)/vote/$', 'vote'),
    (r'^(?P
    \d+)/results/$', 'results'),

  • crear las vistas vote y results

    mcm@McM:/tmp/prueba$ vim polls/views.py


    from django.template import Context, loader
    from prueba.polls.models import Poll, Choice
    from django.http import HttpResponse
    from django.http import Http404
    from django.shortcuts import get_object_or_404, render_to_response
    from django.http import HttpResponseRedirect
    from django.core.urlresolvers import reverse

    [...]
    def vote(request, poll_id):
        p = get_object_or_404(Poll, pk=poll_id)
        try:
            selected_choice = p.choice_set.get(pk=request.POST['choice'])
        except (KeyError, Choice.DoesNotExist):
            # Redisplay the poll voting form.
            return render_to_response('polls/detail2.html', {
                'poll': 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('prueba.polls.views.results', args=(p.id,)))


    def results(request, poll_id):
        p = get_object_or_404(Poll, pk=poll_id)
        return render_to_response('polls/results.html', {'poll': p})

Estas vistas encierran algún detalle que hay que destacar:
This code includes a few things we haven't covered yet in this tutorial:

  • request.POST es como un diccionario que te permite acceder a los datos que se han enviado por el nombre como key. En este caso, request.POST['choice'] devuelve el ID de la opción elegida como un string. request. Los valores de request.POST son siempre Strings. Esto es algo muy común y se usa mucho.

    Ojo, de manera análoga tenemos el request.GET para acceder a los valores que venga por get. Funciona igual que el post.

  • Si no se recupera una opción correcta del modelo a través de request.POST['choice'] se vuelve a mostrar la misma página mostrando ademas un error. Podeis ver la plantilla como en caso de error lo muestra.
  • Despues de incrementar la opción elegida, se puede ver en el código que devuelve un HttpResponseRedirect en lugar del ya visto HttpResponse. HttpResponseRedirect sólo tiene un argumento: la URL donde será redireccionado el usuario. A continuación veremos la peculiaridad de reverse.
  • Como se puede observar se utiliza la función reverse() en el HttpResponseRedirect en este ejemplo. Con esto evitamos tener URL a fuego en el código. Esto es una buena práctica a seguir ya que hace el código fácilmente escalable. Se le pasa como argumento el nombre de la vista a la cual se le quiere pasar el control y la variable que se le quiere pasar. Esta función devuelve un tipo string que será la plantilla que se renderiza.

Para finalizar mostramos las plantillas detail2.html y result.html

mcm@McM:/tmp/prueba$ vim templates/polls/detail2.html

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

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="/polls/{{ poll.id }}/vote/" method="post">
{% for choice in poll.choice_set.all %}
    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
    <label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br />
{% endfor %}
<input type="submit" value="Vote" />
</form>

mcm@McM:/tmp/prueba$ vim templates/polls/results.html

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

<ul>
{% for choice in poll.choice_set.all %}
    <li>{{ choice.choice }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>

Share it!

Twitter Gmail Delicious Google Bookmarks Hotmail Yahoo Mail Share/Bookmark