Una view es un “tipo” de página web en nuestra aplicación Django que generalmente provee una función específica y tiene un template particular.
Por ejemplo, en una aplicación de blog, uno podría tener las siguientes vistas:
En nuestra aplicación de encuestas, vamos a tener las siguientes cuatro views:
En Django, cada view se representa mediante una simple función Python (o método, en el caso de las vistas basadas en clases). Django elegirá la vista analizando la url solicitada
URLs del tipo “ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B” no son necesarias en django, ya que se pueden escribir patrones de URL más elegantes.
Un patrón de URL es la forma general de una url, por ejemplo: /archivados/<anio>/<mes>/.
Para obtener una vista desde una URL, Django utiliza ‘URLconfs’.
Una URLconf mapea patrones de URL patterns (como expresiones regulares) a vistas.
Para más info ver django.core.urlresolvers.
Abrimos el archivo encuestas/views.py y ponemos el siguiente código Python:
# -*- coding: utf-8 -*-
from django.http import HttpResponse
def index(request):
return HttpResponse(u"Hola mundo. Estás en el index de encuestas.")
Esta es la view más simple posible en Django. Para llamar a la view, necesitamos mapearla a una URL, y para esto necesitamos el URLconf.
En la carpeta encuestas, crear un archivo llamado urls.py. El directorio de tu aplicación debería quedar así:
encuestas/
__init__.py
admin.py
models.py
tests.py
urls.py
views.py
En nuestro nuevo archivo, escribimos:
from django.conf.urls import patterns, url
from encuestas import views
urlpatterns = patterns('',
url(r'^$', views.index, name='index'),
)
Apuntar el URLconf raíz al módulo encuestas.urls. En curso/urls.py insertamos un include(), con lo que nos quedaría:
from django.conf.urls import patterns, include, url
from django.contrib import admin
urlpatterns = patterns('',
url(r'^encuestas/', include('encuestas.urls')),
url(r'^admin/', include(admin.site.urls)),
)
Hemos conectado una view index en el URLconf. Si vamos a la dirección http://localhost:8000/encuestas/ en nuestro browser, deberíamos ver el texto “Hola mundo. Estás en el index de encuestas.”, que definimos la view index.
La función url() toma cuatro argumentos, dos requeridos: regex y view, y dos opcionales:kwargs, y name. En este punto vale la pena revisar para que es cada uno de ellos.
Django comienza en la primera expresión regular y va recorriendo la lista hacia abajo, comparando la URL solicitada contra cada expresión regular hasta encontrar una cuyo patrón coincida.
Notar que estas expresiones regulares no chequean parámetros de GET o POST, o el nombre de dominio. Por ejemplo, en un pedido por http://www.example.com/myapp/, el URLconf buscará por myapp/. En un pedido por http://www.example.com/myapp/?page=3, el URLconf también buscará por myapp/.
Se pueden pasar argumentos arbitrarios en un diccionario a la view de destino. No vamos a usar esta opción en el tutorial.
Nombrar las URL nos permite referirnos a ellas de forma unívoca desde distintas partes del código, especialmente en los templates.
Esta característica nos permite hacer cambios globales a los patrones de url del proyecto cambiando tan sólo un archivo (ya que el nombre permanece sin cambios, y nos referimos a una URL por su nombre).
Ahora agreguemos algunas views más a encuestas/views.py. Estas views van a ser un poco diferentes porque van a tomar un argumento:
# -*- coding: utf-8 -*-
def detalle(request, pregunta_id):
return HttpResponse(u"Estás viendo la pregunta %s." % pregunta_id)
def resultados(request, pregunta_id):
response = u"Estás viendo los resultados de la pregunta %s."
return HttpResponse(response % pregunta_id)
def votar(request, pregunta_id):
return HttpResponse(u"Estás votando en la pregunta %s." % pregunta_id)
Conectemos estas nuevas views en el módulo encuestas.urls agregando las siguientes llamadas a url():
# -*- coding: utf-8 -*-
from django.conf.urls import patterns, url
from encuestas import views
urlpatterns = patterns('',
# ej: /encuestas/
url(r'^$', views.index, name='index'),
# ej: /encuestas/5/
url(r'^(?P<pregunta_id>\d+)/$', views.detalle, name='detalle'),
# ej: /encuestas/5/resultadoss/
url(r'^(?P<pregunta_id>\d+)/resultados/$', views.resultados, name='resultados'),
# ej: /encuestas/5/votar/
url(r'^(?P<pregunta_id>\d+)/votar/$', views.votar, name='votar'),
)
La idea detrás de include() es hacer fácil tener URLs plug-and-play. Como encuestas tiene su propio URLconf (encuestas/urls.py), las URLs de la app se pueden poner bajo “/encuestas/”, o bajo “/encuestas_locas/”, o bajo “/contenidos/encuestas/”, o cualquier otro camino, y la app seguirá funcionando.
Esto es lo que pasa si un usuario va a “/encuestas/34/” en este sistema:
Django encontrará coincidencia en '^encuestas/'
Entonces, Django va a recortar el texto que coincide ("encuestas/") y enviar el texto restante –"34/" – al URLconf ‘encuestas.urls’ para seguir el proceso, donde coincidirá con r'^(?P<pregunta_id>\d+)/$', resultando en una llamada a la view detalle() de la forma:
detalle(request=<HttpRequest object>, pregunta_id='34')
Como los patrones de URL son expresiones regulares, no hay realmente un límite de lo que se puede hacer con ellos. Y no hay necesidad de agregar cosas como .html – a menos que uno quisiera, en cuyo caso nos quedaría algo como:
(r'^encuestas/ultimas\.html$', 'encuestas.views.index'),
Pero no hagan esto. No tiene sentido.
Cada view es responsable de hacer una de dos cosas:
El resto depende de uno.
Todo lo que Django espera es un HttpResponse. O una excepción.
Utilizando nuestra API de base de datos, haremos un nuevo index, que muestre las últimas 5 preguntas:
from django.http import HttpResponse
from encuestas.models import Pregunta
def index(request):
lista_ultimas_preguntas = Pregunta.objects.order_by('-pub_date')[:5]
salida = ', '.join([p.texto_pregunta for p in lista_ultimas_preguntas])
return HttpResponse(salida)
# Deja las otras vistas tal como estaban
Hay un problema!: el diseño de la página está escrito explícitamente en la view.
Vamos a usar el sistema de templates de Django para separar el diseño del código Python.
Crear un directorio llamado templates en tu directorio encuestas.
Django Buscará las plantillas ahí.
Dentro de ese directorio, crear otro llamado encuestas y ahí dentro crea un archivo llamado index.html. Debería verse: encuestas/templates/encuestas/index.html. Puedes referiste a esta plantilla como encuestas/index.html.
Escribir lo siguiente en nuestra plantilla index.html
{% if lista_ultimas_preguntas %}
<ul>
{% for pregunta in lista_ultimas_preguntas %}
<li>
<a href="/encuestas/{{ pregunta.id }}/">{{ pregunta.texto_pregunta }}</a>
</li>
{% endfor %}
</ul>
{% else %}
<p>No hay encuestas disponibles.</p>
{% endif %}
Actualizamos la función index de encuestas/views.py:
from django.http import HttpResponse
from django.template import RequestContext, loader
from encuestas.models import Pregunta
def index(request):
lista_ultimas_preguntas = Pregunta.objects.order_by('-pub_date')[:5]
template = loader.get_template('encuestas/index.html')
context = RequestContext(request, {
'lista_ultimas_preguntas': lista_ultimas_preguntas,
})
return HttpResponse(template.render(context))
Context es un diccionario de variables pasados a la plantilla.
Las acciones anteriores son algo muy común. Django provee un atajo. Aquí está la view index() reescrita:
from django.shortcuts import render
from encuestas.models import Pregunta
def index(request):
lista_ultimas_preguntas = Pregunta.objects.all().order_by('-pub_date')[:5]
context = {'lista_ultimas_preguntas': lista_ultimas_preguntas}
return render(request, 'encuestas/index.html', context)
La función render() toma un objeto request como primer un argumento, un nombre de template como segundo argumento y un diccionario como tercer argumento opcional. Devuelve un objeto HttpResponse del template renderizado con el contexto dado.
Ahora veamos la view de detalle de una encuesta – la página que muestra la pregunta de una encuesta:
from django.http import Http404
from django.shortcuts import render
from encuestas.models import Pregunta
# ...
def detalle(request, pregunta_id):
try:
pregunta = Pregunta.objects.get(pk=pregunta_id)
except Pregunta.DoesNotExist:
raise Http404
return render(request, 'encuestas/detalle.html', {'pregunta': pregunta})
Por ahora pongamos lo siguiente en encuestas/detalle.html:
{{ pregunta }}
from django.shortcuts import get_object_or_404, render
from encuestas.models import Pregunta
# ...
def detalle(request, pregunta_id):
pregunta = get_object_or_404(Pregunta, pk=pregunta_id)
return render(request, 'encuestas/detalle.html', {'pregunta': pregunta})
La función get_object_or_404() toma un modelo Django como primer argumento y un número arbitrario de argumentos nombrados, que se pasan a la función get() del manager del modelo. Levanta un Http404 si el objeto no existe.
Volvamos a la view detalle() de nuestra aplicación de encuestas. Dada la variable de contexto pregunta, veamos como podría lucir el template encuestas/detalle.html:
<h1>{{ pregunta.texto_pregunta }}</h1>
<ul>
{% for eleccion in pregunta.eleccion_set.all %}
<li>{{ eleccion.texto_opcion}}</li>
{% endfor %}
</ul>
El sistema de templates usa sintaxis de punto para acceder a los atributos de variable.
En {{ pregunta.texto_pregunta }}, Django primero hace una búsqueda de diccionario sobre pregunta. Si eso falla, intenta una búsqueda de atributo – que en este caso, funciona. Si hubiera fallado, se hubiera intentado una búsqueda por índice de lista.
Una llamada de método se da en el bucle {% for %}: pregunta.eleccion_set.all se interpreta como el código Python pregunta.eleccion_set.all(), que devuelve un iterable de objeto Eleccion y usable para el tag {% for %}.
Para más detalles sobre templates, se puede ver template guide.
Recordemos que cuando escribimos el link a una encuesta en el template encuestas/index.html, el link estaba parcialmente escrito “a mano”.
Con esto es un desafío cambiar las URLs en un proyecto con muchos templates. Sin embargo, como definimos el argumento name en las llamadas a url() en el módulo encuestas.urls, podemos eliminar la dependencia de URLs fijas usando el template tag {% url %}:
<li><a href="{% url 'detalle' pregunta.id %}">{{ pregunta.texto_pregunta }}</a></li>
En proyectos Django reales, podría haber cinco, diez, veinte o más apps.
¿Cómo Django distingue los nombres de las URLs entre todas las apps?
La respuesta es agregar espacios de nombres al URLconf raíz. Cambiamos el archivo curso/urls.py para incluir espacios de nombres:
from django.conf.urls import patterns, include, url
from django.contrib import admin
urlpatterns = patterns('',
url(r'^encuestas/', include('encuestas.urls', namespace="encuestas")),
url(r'^admin/', include(admin.site.urls)),
)
Ahora, en encuestas/index.html:
<li><a href="{% url 'encuestas:detalle' pregunta.id %}">{{ pregunta.texto_pregunta }}</a></li>