Curso Python DGA 2011/django/contenidos/vistas
Contenido
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:
from django.conf.urls.defaults import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
(r'^encuestas/$', 'encuestas.views.index'),
(r'^encuestas/(?P<encuesta_id>\d+)/$', 'encuestas.views.detalle'),
(r'^encuestas/(?P<encuesta_id>\d+)/resultado/$', 'encuestas.views.resultado'),
(r'^encuestas/(?P<encuesta_id>\d+)/votar/$', 'encuestas.views.votar'),
url(r'^admin/', include(admin.site.urls)),
)
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
from django.http import HttpResponse
def index(request):
return HttpResponse(u"Hola, este es el índice de las encuestas.")
def detalle(request, encuesta_id):
return HttpResponse(u"Estás viendo la encuesta %s." % encuesta_id)
def resultado(request, encuesta_id):
return HttpResponse(u"Estás viendo el resultado de la encuesta %s." % encuesta_id)
def votar(request, encuesta_id):
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
def index(request):
# Extrae 5 últimas encuestas de la bbdd. Orden: la última primero.
listado_ultimas = Encuesta.objects.all().order_by('-fecha_pub')[:5]
# prepara el resultado para mostrarlo
output = ', '.join([p.pregunta for p in listado_ultimas])
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:
from django.http import HttpResponse
from django.template import Context, loader
from encuestas.models import Encuesta
def index(request):
# Extrae 5 últimas encuestas de la bbdd. Orden: la última primero.
listado_ultimas = Encuesta.objects.all().order_by('-fecha_pub')[:5]
t = loader.get_template('encuestas/index.html')
c = Context({
'listado_ultimas': listado_ultimas,
})
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.
from django.shortcuts import render_to_response
from encuestas.models import Encuesta
def index(request):
listado_ultimas = Encuesta.objects.all().order_by('-fecha_pub')[:5]
return render_to_response('encuestas/index.html',
{'listado_ultimas': listado_ultimas,})
Lanzando una respuesta 404 (no encontrado)
from django.shortcuts import render_to_response
from django.http import Http404
from encuestas.models import Encuesta
# ...
def detalle(request, encuesta_id):
try:
enc = Encuesta.objects.get(pk=encuesta_id)
except Encuesta.DoesNotExist:
raise Http404 # Si la encuesta no existe, lanza un httpresponse: 404
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()
from django.shortcuts import render_to_response, get_object_or_404
# ...
def detalle(request, encuesta_id):
enc = get_object_or_404(Encuesta, pk=encuesta_id)
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:
urlpatterns = patterns('encuestas.views',
(r'^encuestas/$', 'index'),
(r'^encuestas/(?P<encuesta_id>\d+)/$', 'detalle'),
(r'^encuestas/(?P<encuesta_id>\d+)/resultado/$', 'resultado'),
(r'^encuestas/(?P<encuesta_id>\d+)/votar/$', 'votar'),
)
urlpatterns += patterns('',
(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():
from django.conf.urls.defaults import *
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
(r'^encuestas/', include('encuestas.urls')),
(r'^admin/', include(admin.site.urls)),
)
Así quedará encuestas/urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('encuestas.views',
(r'^$', 'index'),
(r'^(?P<encuesta_id>\d+)/$', 'detalle'),
(r'^(?P<encuesta_id>\d+)/resultado/$', 'resultado'),
(r'^(?P<encuesta_id>\d+)/votar/$', 'votar'),
)
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.