Escribiendo su primera aplicación en Django, parte 3

This tutorial begins where Tutorial 2 left off. We’re continuing the web-poll application and will focus on creating the public interface – «views.»

Dónde obtener ayuda:

If you’re having trouble going through this tutorial, please head over to the Getting Help section of the FAQ.

Información general

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 blog application, you might have the following views:

  • 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 «detalle» de preguntas– muestra un texto de pregunta sin resultados, pero con un formulario para votar.
  • La 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.

In Django, web pages and other content are delivered by views. Each view is represented by a Python function (or method, in the case of class-based views). Django will choose a view by examining the URL that’s requested (to be precise, the part of the URL after the domain name).

Now in your time on the web you may have come across such beauties as ME2/Sites/dirmod.htm?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B. You will be pleased to know that Django allows us much more elegant URL patterns than that.

A URL pattern is the general form of a URL - for example: /newsarchive/<year>/<month>/.

Para llegar desde una URL a una vista Django usa lo que se conoce como “URLconfs”. Una URLconf mapea patrones de URL a vistas.

Este tutorial proporciona instrucción básica en el uso de URLconfs. Usted puede consultar URL dispatcher para más información.

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)

Una estas nuevas vistas al módulo polls.urls añadiendo las siguientes llamadas path():

polls/urls.py
from django.urls import path

from . import views

urlpatterns = [
    # ex: /polls/
    path("", views.index, name="index"),
    # ex: /polls/5/
    path("<int:question_id>/", views.detail, name="detail"),
    # ex: /polls/5/results/
    path("<int:question_id>/results/", views.results, name="results"),
    # ex: /polls/5/vote/
    path("<int:question_id>/vote/", views.vote, name="vote"),
]

Take a look in your browser, at «/polls/34/». It’ll run the detail() function and display whatever ID you provide in the URL. Try «/polls/34/results/» and «/polls/34/vote/» too – these will display the placeholder results and voting pages.

Cuando se solicita una página a tu sitio web – digamos, «/polls/34/», Django cargará el módulo Python mysite.urls apuntado por el parámetro ROOT_URLCONF. Recorre la lista de patrones urlpatterns en su orden. Al encontrar la coincidencia 'polls/', elimina el texto coincidente ("polls/") y envía el texto restante – "34/" – a la URLconf “polls.urls” para su posterior procesamiento. Allí coincide con '<int:question_id>/', lo que resulta en una llamada a la vista detail() como esta:

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

La cadena question_id=34 proviene de <int:question_id>. El texto del URL entre paréntesis angulares se «captura» y se envía como el argumento clave a la función de la vista. La parte question_id de la cadena define el nombre (campo) que se utilizará para identificar el patrón coincidente, y la parte int convierte el patrón a enteros y determinará coincidencias con esta parte del URL. Los dos puntos (:) separan el convertidor y el nombre del patrón.

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.

Within the templates directory you have just created, create another directory called polls, and within that create a file called index.html. In other words, your template should be at polls/templates/polls/index.html. Because of how the app_directories template loader works as described above, you can refer to this template within Django as polls/index.html.

Espacio de nombres de plantillas

Now we might be able to get away with putting our templates directly in polls/templates (rather than creating another polls subdirectory), but it would actually be a bad idea. Django will choose the first template it finds whose name matches, and if you had a template with the same name in a different application, Django would be unable to distinguish between them. We need to be able to point Django at the right one, and the best way to ensure this is by namespacing them. That is, by putting those templates inside another directory named for the application itself.

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 %}

Nota

To make the tutorial shorter, all template examples use incomplete HTML. In your own projects you should use complete HTML documents.

Ahora vamos a actualizar nuestra vista index 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 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>

The problem with this hardcoded, tightly-coupled approach is that it becomes challenging to change URLs on projects with a lot of templates. However, since you defined the name argument in the path() functions in the polls.urls module, you can remove a reliance on specific URL paths defined in your url configurations by using the {% url %} template tag:

<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
path("<int:question_id>/", 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'
path("specifics/<int:question_id>/", 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.urls import path

from . import views

app_name = "polls"
urlpatterns = [
    path("", views.index, name="index"),
    path("<int:question_id>/", views.detail, name="detail"),
    path("<int:question_id>/results/", views.results, name="results"),
    path("<int:question_id>/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>

When you’re comfortable with writing views, read part 4 of this tutorial to learn the basics about form processing and generic views.

Back to Top