Curso Python DGA 2011/django/contenidos/vistas

De WikiEducator
Saltar a: navegación, buscar

Creación de la interfaz pública

En la filosofía de Django una vista es un tipo de página web que sirve para una función específica y tiene una plantilla específica. Por ejemplo en un blog podemos tener:

  • La página inicial del Blog (muestra las últimas entradas)
  • El detalle de una entada
  • Página de las entradas por ffechas (día, mes, año)
  • Comentarios

En la aplicación de las encuestas:

  • Página índice de Encuestas, que muestre las últimas encuestas
  • Página de detalle de encuestas: Muestra una pregunta con el formulario para votar
  • Página de resultados de una encuesta.

Philosophy A view is a “type” of Web page in your Django application that generally serves a specific function and has a specific template. For example, in a Weblog application, you might have the following views:

Blog homepage – displays the latest few entries. Entry “detail” page – permalink page for a single entry. Year-based archive page – displays all months with entries in the given year. Month-based archive page – displays all days with entries in the given month. Day-based archive page – displays all entries in the given day. Comment action – handles posting comments to a given entry. In our poll application, we’ll have the following four views:

En Django cada vista la gestiona una fución.

Diseño de URLs

El primer paso para escribir una vista es diseñar la estructura de la URL. Django asocia una URL con código Python mediante el módulo URLconf.

Las urls se controlan con la variable urlpatterns, que es una secuencia de tuplas con el siguiente formato:

(expresión regular, function python [, diccionario opcional])

Django busca por orden y lanza la primera función que cumple el patrón. Cuando Django la encuentra, llama a la función Python con un objeto HttpRequest como primer argumento, valores capturados de la url como argumentos por clave y otros argumentos opcionales (tercer elemento de la tupla)

Al crear el proyecto se crea un URLconf por defecto en urls.py Esto se configura en settings.py (ver variable ROOT_URLCONF)

ROOT_URLCONF = 'mysite.urls'

Ejemplo. Edita urls.py como el siguiente ejemplo:

  1. from django.conf.urls.defaults import patterns, include, url
  2.  
  3. from django.contrib import admin
  4. admin.autodiscover()
  5.  
  6. urlpatterns = patterns('',
  7.     (r'^encuestas/$', 'encuestas.views.index'),
  8.     (r'^encuestas/(?P<encuesta_id>\d+)/$', 'encuestas.views.detalle'),
  9.     (r'^encuestas/(?P<encuesta_id>\d+)/resultado/$', 'encuestas.views.resultado'),
  10.     (r'^encuestas/(?P<encuesta_id>\d+)/votar/$', 'encuestas.views.votar'),
  11.     url(r'^admin/', include(admin.site.urls)),
  12. )

Según nuestro ejemplo si el usuario pide

/encuestas/23

Django ejecutará

detalle((request=<HttpRequest object>, encuesta_id='23')
 

Los paréntesis de la expresión regular capturan el texto que coincide con el patrón.

El módulo URLconf compila las expresiones regulares para que se ejecuten más rápidas.

Primera vista

Todavía no hemos creado ninguna vista. Vamos a comprobar que el URLconf funciona bien.

python manage.py runserver

Ve a "http://localhost:8000/encuestas/" con el navegador. Tenemos que ver el error de que la vista no existe.

 Exception Type: 	ViewDoesNotExist

Escribimos las vistas en encuestas/views.py

  1. from django.http import HttpResponse
  2.  
  3. def index(request):
  4.     return HttpResponse(u"Hola, este es el índice de las encuestas.")
  5.  
  6. def detalle(request, encuesta_id):
  7.     return HttpResponse(u"Estás viendo la encuesta %s." % encuesta_id)
  8.  
  9. def resultado(request, encuesta_id):
  10.     return HttpResponse(u"Estás viendo el resultado de la encuesta %s." % encuesta_id)
  11.  
  12. def votar(request, encuesta_id):
  13.     return HttpResponse(u"Estás votando la encuesta %s." % encuesta_id)

Vistas que hacen algo

Ahora vamos a escribir vistas que hagan algo realmente Las vistas tienen que devolver:

  • El objeto HttpResponse con el contenido de la página solicitada o
  • Una excepción (por ej. Http404)

index

  1. def index(request):
  2.     # Extrae 5 últimas encuestas de la bbdd. Orden: la última primero.
  3.     listado_ultimas = Encuesta.objects.all().order_by('-fecha_pub')[:5]
  4.     # prepara el resultado para mostrarlo
  5.     output = ', '.join([p.pregunta for p in listado_ultimas])
  6.     return HttpResponse(output)

Problema: el diseño de la página está integrado en la vista. Si queremos cambiar el aspecto de la página, hay que editar código python. Idea: separar código y presentación usando el sistema de plantillas de Django:

  1. from django.http import HttpResponse
  2. from django.template import Context, loader
  3. from encuestas.models import Encuesta
  4.  
  5. def index(request):
  6.     # Extrae 5 últimas encuestas de la bbdd. Orden: la última primero.
  7.     listado_ultimas = Encuesta.objects.all().order_by('-fecha_pub')[:5]
  8.     t = loader.get_template('encuestas/index.html')
  9.     c = Context({
  10.         'listado_ultimas': listado_ultimas,
  11.         })
  12.     return HttpResponse(t.render(c))

Primera plantilla

Es necesario crear la plantilla en una ruta incluida en TEMPLATE_DIRS. Editamos [directorio_plantillas]/encuestas/index.html" Por> seguridad no se extraen las plantillas de la raíz del proyecto.

{% if listado_ultimas %}
    <ul>
    {% for encuesta in listado_ultimas %}
        <li><a href="/encuestas/{{ encuesta.id}}/">{{ encuesta.pregunta }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No hay encuestas disponibles.</p>
{% endif %}

Atajo: render_to_response()

¿Para qué repetir tanto? Ya no es necesario importar loader, Context y HttpResponse.

  1. from django.shortcuts import render_to_response
  2. from encuestas.models import Encuesta
  3.  
  4. def index(request):
  5.     listado_ultimas = Encuesta.objects.all().order_by('-fecha_pub')[:5]
  6.     return render_to_response('encuestas/index.html',
  7.         {'listado_ultimas': listado_ultimas,})

Lanzando una respuesta 404 (no encontrado)

  1. from django.shortcuts import render_to_response
  2. from django.http import Http404
  3. from encuestas.models import Encuesta
  4. # ...
  5. def detalle(request, encuesta_id):
  6.     try:
  7.         enc = Encuesta.objects.get(pk=encuesta_id)
  8.     except Encuesta.DoesNotExist:
  9.         raise Http404  # Si la encuesta no existe, lanza un httpresponse: 404
  10.     return render_to_response('encuestas/detalle.html', {'encuesta': enc})

Creamos una plantilla para encuestas/detalle.html:

{{encuesta}}

Resultado:

$ curl -I http://127.0.0.1:8000/encuestas/12/
HTTP/1.0 404 NOT FOUND
Date: Wed, 24 Aug 2011 05:27:20 GMT
Server: WSGIServer/0.1 Python/2.7.1+
Content-Type: text/html

Otro atajo: get_object_or_404()

  1. from django.shortcuts import render_to_response, get_object_or_404
  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', {'encuesta': enc})

La función get_object_or_404() toma como primer argumento un modelo de Django y un número no determinado de argumentos por clave. Si el objeto no existe, lanza un Http404.

Si buscamos una secuencia de objetos, podemos usar get_list_or_404(), que usa filter() en lugar de get(). Lanza Http404 si la lista está vacía.

Escribir una vista 404 (página no encontrada)

Cuando DEBUG está activado, la vista 404 no se usa y Django muestra el error. Cuando DEBUG está a False, va a buscar un template 404.html en la raíz del directorio de templates. Si no lo encuentra, lanza un Http500. También hay que escribir una plantilla para 500.html.

Plantilla de detalle.html

<h1>{{ encuesta.pregunta }}</h1>
<ul>
{% for opcion in encuesta.opcion_set.all %}
    <li>{{ opcion.opcion }}</li>
{% endfor %}
</ul>

El sistema de plantillas usa la sintaxis del punto para accceder a los atributos. Fíjate en como usa el {% for %} y como accede a las opciones desde el objeto encuestas

encuesta.opcion_set.all

equivale a

encuesta.opcion_set.all()

Que devuelve el iterable que recorre el for.

Más información en la guía de plantillas

Simplificando URLconfs

Podemos usar patrones para evitar repeticiones:

  1. urlpatterns = patterns('encuestas.views',
  2.     (r'^encuestas/$', 'index'),
  3.     (r'^encuestas/(?P<encuesta_id>\d+)/$', 'detalle'),
  4.     (r'^encuestas/(?P<encuesta_id>\d+)/resultado/$', 'resultado'),
  5.     (r'^encuestas/(?P<encuesta_id>\d+)/votar/$', 'votar'),
  6.     )
  7.  
  8. urlpatterns += patterns('',
  9.     (r'^admin/', include(admin.site.urls)),)


También se pueden desacoplar, para hacer más reutilizables las aplicaciones (aplicaciones pluggables) Pasos:

  • Movemos sitio_encuestas/urls.py a encuestas/urls.py
  • Cambiamos sitio_encuestas/urls.py para eliminar las urls de encuestas e insertamos un include():
  1. from django.conf.urls.defaults import *
  2.  
  3. from django.contrib import admin
  4. admin.autodiscover()
  5.  
  6. urlpatterns = patterns('',
  7.     (r'^encuestas/', include('encuestas.urls')),
  8.     (r'^admin/', include(admin.site.urls)),
  9. )

Así quedará encuestas/urls.py

  1. from django.conf.urls.defaults import *
  2.  
  3. urlpatterns = patterns('encuestas.views',
  4.     (r'^$', 'index'),
  5.     (r'^(?P<encuesta_id>\d+)/$', 'detalle'),
  6.     (r'^(?P<encuesta_id>\d+)/resultado/$', 'resultado'),
  7.     (r'^(?P<encuesta_id>\d+)/votar/$', 'votar'),
  8.     )

Como ahora tienen una ruta relativa, podemos instalar la aplicación en distintas URLs: /encuestas, /encuestas_alumnos, /encuestas_corporativas y la aplicación seguirá funcionando.