Escribiendo su primera aplicación en Django, parte 3

Este tutorial comienza donde quedó el Tutorial 2. Vamos a continuar con la aplicación Web para encuestas y vamos a enfocarnos en crear la interfaz pública –»vistas»

Información general

Una vista es un «tipo» de página Web de su aplicación Django, que generalmente cumple una función específica y tiene una plantilla específica. Por ejemplo, en una aplicación de blog, usted puede tener las siguientes vistas:

  • La página de inicio del blog – muestra las últimas pocas entradas.
  • Página de entrada de «detail» - página de enlace permanente para una entrada individual.
  • Página de archivo basado en el año – muestra todos los meses con entradas en un año determinado.
  • Página de archivo basado en el mes – muestra todos los días con las entradas en un mes determinado.
  • Página de archivo basado en el día– muestra todas las entradas de un día determinado.
  • Comentar – gestiona la publicación de comentarios en una entrada determinada.

En nuestra aplicación encuestas, vamos a tener las siguientes cuatro vistas:

  • La página «índice» de preguntas – muestra las últimas preguntas.
  • La página «de detalle» de preguntas– muestra un texto de pregunta sin resultados, pero con un formulario para votar.
  • Página «resultados» de preguntas – muestra los resultados de una pregunta en particular.
  • Votar – gestiona la votación para una elección concreta en una pregunta específica.

En Django, las páginas Web y otros contenidos se entregan por medio de vistas. Cada vista está representada por una sencilla función Python (o método, en el caso de las vistas basadas en clases). Django escogerá una vista examinando la URL que se solicita (para ser preciso, la parte de la URL que se encuentra después del nombre del dominio).

En todo el tiempo que ha pasado en la web es posible que haya encontrado este tipo de bellezas como «ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B». Usted estará encantado de saber que Django nos ofrece patrones de URL mucho más elegantes que ese.

Un patrón de URL no es más que el formulario general de una URL, por ejemplo:/newsarchive/<year>/<month>/.

Para llegar desde una URL a una vista, Django usa lo que se conoce como “URLconfs”. Un URLconf mapea patrones de URL (descritos como expresiones regulares) a las vistas.

This tutorial provides basic instruction in the use of URLconfs, and you can refer to django.urls for more information.

Escribiendo más vistas

A continuación vamos a agregar más vistas a polls/views.py. Estas vistas son un poco diferentes porque toman un argumento:

polls/views.py
def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

Envíe estas nuevas vistas al módulo polls.urls añadiendo las siguientes llamadas url():

polls/urls.py
from django.conf.urls import url

from . import views

urlpatterns = [
    # ex: /polls/
    url(r'^$', views.index, name='index'),
    # ex: /polls/5/
    url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    # ex: /polls/5/results/
    url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
    # ex: /polls/5/vote/
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

Eche un vistazo en su navegador a «/polls/34/». Va a ejecutar el método detail() y mostrar cualquier ID que usted suministre en la URL. Pruebe «/polls/34/results/» y también «/polls/34/vote/» - estas mostrarán los resultados del marcador de posición y las páginas de votación.

When somebody requests a page from your website – say, «/polls/34/», Django will load the mysite.urls Python module because it’s pointed to by the ROOT_URLCONF setting. It finds the variable named urlpatterns and traverses the regular expressions in order. After finding the match at '^polls/', it strips off the matching text ("polls/") and sends the remaining text – "34/" – to the “polls.urls” URLconf for further processing. There it matches r'^(?P<question_id>[0-9]+)/$', resulting in a call to the detail() view like so:

detail(request=<HttpRequest object>, question_id='34')

La parte question_id='34' proviene de (?P<question_id>[0-9]+). Utilizar paréntesis en torno a un patrón «captura» el texto buscado por ese patrón y lo envía como un argumento a la función de vista; ?P<question_id> define el nombre que se utilizará para identificar el patrón buscado y [0-9]+ es una expresión regular para buscar una secuencia de dígitos (es decir, un número).

Debido a que los patrones de URL son expresiones regulares, realmente no hay límites en lo que puede hacer con ellos. Además, no hay necesidad de añadir códigos redundantes a la URL como .html; a menos que lo desee, en cuyo caso usted puede hacer algo como esto:

url(r'^polls/latest\.html$', views.index),

Pero, no haga eso. Es una tontería.

Escriba vistas que realmente hagan algo

Cada vista es responsable de hacer una de dos cosas: retornar un objeto HttpResponse con el contenido de la página solicitada, o levantar una excepción como Http404 . El resto depende de usted.

Su vista puede leer registros de una base de datos o no, puede usar un sistema de plantillas como el de Django o un sistema de plantillas Python de terceros o no. Puede generar un archivo PDF, salida XML, crear un archivo ZIP en la marcha, todo lo que quiera, utilizando cualquier librería de Python que desee.

Todo Django quiere es ese objeto HttpResponse o una excepción.

Debido a que es conveniente, vamos a utilizar la propia API de base de datos de Django, que estudiamos en el Tutorial 2. A continuación señalaremos una nueva vista index() que muestra en el sistema las 5 últimas preguntas de la encuesta, separadas por comas, según su fecha de publicación:

polls/views.py
from django.http import HttpResponse

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    output = ', '.join([q.question_text for q in latest_question_list])
    return HttpResponse(output)

# Leave the rest of the views (detail, results, vote) unchanged

Sin embargo, aquí hay un problema: el diseño de la página está codificado de forma fija en la vista. Si desea cambiar la forma en que se ve la página, tendrá que editar el código Python. Así que vamos a usar el sistema de plantillas de Django para separar el diseño de Python mediante la creación de una plantilla que la vista pueda utilizar.

Primero, cree un directorio llamado templates en su directorio polls. Django buscará las plantillas allí.

La configuración de TEMPLATES de su proyecto describe cómo Django cargará y creará las plantillas. El archivo de configuración predeterminada configura un backend DjangoTemplates cuya opción APP_DIRS está configurada como True. Convencionalmente, DjangoTemplates busca un subdirectorio «templates» en cada una de las INSTALLED_APPS.

Dentro del directorio templates que usted acaba de crear, cree otro directorio llamado polls y dentro de este cree un archivo llamado index.html. En otras palabras, su plantilla debe estar en polls/templates/polls/index.html. Ya que el cargador de plantillas app_directories funciona como se describió anteriormente, usted puede consultar esta plantilla dentro de Django simplemente como polls/index.html.

Espacio de nombres de plantillas

Ahora podríamos salirnos con la nuestra poniendo nuestras plantillas directamente en polls/templates (en lugar de crear otro subdirectorio polls), pero en realidad sería una mala idea. Django elegirá la primera plantilla que encuentre cuyo nombre coincida, y si usted tuviera una plantilla con el mismo nombre en una aplicación diferente, Django no podría distinguir entre ellas. Tenemos que ser capaces de indicarle a Django la correcta, y la forma más fácil de asegurar esto es organizándolas por espacio de nombres, es decir, poniendo esas plantillas dentro de otro directorio que recibe el nombre de la aplicación.

Introduzca el siguiente código en esa plantilla:

polls/templates/polls/index.html
{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

Ahora vamos a actualizar nuestra vista índex en polls/views.py para usar la plantilla:

polls/views.py
from django.http import HttpResponse
from django.template import loader

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = {
        'latest_question_list': latest_question_list,
    }
    return HttpResponse(template.render(context, request))

Ese código carga la plantilla llamada polls/index.html y le pasa un contexto. El contexto es un diccionario que relaciona los nombres de variables de plantillas con objetos Python.

Cargue la página señalando su navegador en «/polls/», y usted debería ver una lista con viñetas que contiene la pregunta «¿Qué pasa?» del Tutorial 2. El enlace señala la página de detalles de la pregunta.

Un atajo: render()

Es una práctica muy común cargar una plantilla, llenar un contexto y retornar un objeto HttpResponse con el resultado de la plantilla creada. Django proporciona un atajo. A continuación la vista index() completa, reescrita:

polls/views.py
from django.shortcuts import render

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {'latest_question_list': latest_question_list}
    return render(request, 'polls/index.html', context)

Tenga en cuenta que una vez que hemos hecho esto en todas estas vistas, ya no necesitamos importar loader y HttpResponse (usted querrá continuar HttpResponse si usted todavía tiene los métodos stub para detail, results y vote).

La función render() toma el objeto solicitado como su primer argumento, un nombre de plantilla como su segundo argumento y un diccionario como su tercer argumento opcional. La función retorna un objeto HttpResponse de la plantilla determinada creada con el contexto dado.

Levantar un error 404

Ahora vamos a abordar la vista de detalle de la pregunta: la página que muestra el texto de pregunta para una encuesta determinada. Aquí está la vista:

polls/views.py
from django.http import Http404
from django.shortcuts import render

from .models import Question
# ...
def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})

El nuevo concepto aquí: La vista levanta la excepción Http404 si no existe una pregunta con la ID solicitada.

Hablaremos de lo que podría poner en esa plantilla polls/detail.html un poco más tarde, pero si desea empezar a trabajar con el ejemplo anterior, un archivo que contenga simplemente:

polls/templates/polls/detail.html
{{ question }}

le ayudará a empezar por ahora.

Un atajo get_object_or_404()

Es una práctica muy común utilizar :meth::~django.db.models.query.QuerySet.get y levantar la excepción Http404 si no existe el objeto. Django proporciona un atajo. Aquí está la vista detail(), reescrita:

polls/views.py
from django.shortcuts import get_object_or_404, render

from .models import Question
# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

La función get_object_or_404() toma un modelo Django como su primer argumento y un número arbitrario de argumentos de palabra clave que pasa a la función get() del administrador del modelo. Levanta la excepción Http404 si no existe el objeto.

Filosofía

¿Por qué utilizamos una función auxiliar get_object_or_404() en lugar de capturar las excepciones ObjectDoesNotExist en un nivel superior, o hacer que la API del modelo levante la excepción Http404 en vez de la excepción ObjectDoesNotExist?

Porque eso acoplaría la capa de modelos con la capa de vistas. Uno de los objetivos de diseño más destacados de Django es mantener el bajo acoplamiento. Algo de acoplamiento controlado se introduce en el módulo django.shortcuts.

También hay una función get_list_or_404() , que funciona igual que get_object_or_404() - excepto usando filter() en lugar de get(). La misma levanta la excepción Http404 si la lista está vacía.

Utilice el sistema de plantillas

Vuelva a la vista detail() para nuestra aplicación encuesta. Teniendo en cuenta la variable de contexto question, así es como la plantilla polls/detail.html podría verse:

polls/templates/polls/detail.html
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

El sistema de plantillas utiliza la sintaxis de búsqueda con puntos para acceder a los atributos de variables. En el ejemplo de {{question.question_text}}, Django primero realiza una consulta en el diccionario sobre el objeto question. Si esto falla, intenta una búsqueda de atributos que en este caso funciona. Si la búsqueda de atributos hubiera fallado, hubiera intentado una búsqueda de índice de lista.

El llamado del método ocurre en el bucle {% for%}: question.choice_set.all se interpreta como el código Python question.choice_set.all() que retorna un iterable de objetos Choice y es adecuado para usarse en la etiqueta {% for%}.

Consulte la guía de plantillas para más información sobre las plantillas.

Quitar URLs codificadas de manera predeterminada en las plantillas

Recuerde que cuando escribimos el enlace para una pregunta en la plantilla polls/index.html, el enlace estaba parcialmente codificado de forma predeterminada como este:

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

El problema con este método de codificación predeterminada y estrechamente acoplada es que se hace difícil modificar las URLs en proyectos que tengan muchas plantillas. Sin embargo, puesto que usted definió el argumento de nombre en las funciones url() en el módulo polls.urls, usted puede eliminar la seguridad de rutas URL específicas definida en su configuración de URL usando la etiqueta de plantilla {% url %}:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

La forma como esto funciona es buscando la definición de la URL como se específica en el módulo polls.urls. Usted puede ver exactamente donde se define el nombre de la URL de “detail” a continuación:

...
# the 'name' value as called by the {% url %} template tag
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...

Si desea modificar la URL de la vista de detalle de las encuestas a algo diferente, quizás a algo como polls/specifics/12/, en lugar de hacerlo en la plantilla (o plantillas), usted la modificaría en polls/urls.py:

...
# added the word 'specifics'
url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...

Asignando los nombres de URLs

El proyecto tutorial solo tiene una aplicación; polls. En proyectos reales de Django, puede haber cinco, diez, veinte o más aplicaciones. ¿Cómo diferencia Django los nombres de las URLs entre ellos? Por ejemplo, la aplicación polls tiene una vista detail, como la podría tener también una aplicación en el mismo proyecto que es para un blog. ¿Cómo hacer para que Django sepa cual vista de aplicaciones crear para una URL cuando se utiliza la etiqueta de plantilla`` {% url%}``?

La solución es añadir espacios de nombres a su URLconf. En el archivo polls/urls.py, añada un app_name para configurar el espacio de nombres de la aplicación:

polls/urls.py
from django.conf.urls import url

from . import views

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

Ahora modifique su plantilla polls/index.html desde:

polls/templates/polls/index.html
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

para señalar la vista de detalle con espacio de nombres:

polls/templates/polls/index.html
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

Cuando esté familiarizado con la escritura de vistas, lea la parte 4 de este tutorial para aprender más sobre el procesamiento simple de formularios y vistas genéricas.

Back to Top