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:
Si tiene problemas para seguir este tutorial, diríjase a la sección Obteniendo ayuda del 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:
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()
:
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 lo que 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:
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:
{% 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:
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:
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 de la solicitud como su primer argumento, un nombre de plantilla como su segundo argumento y un diccionario como su tercer argumento opcional. La función devuelve un objeto HttpResponse
de la plantilla que se ha proporcionado representada 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:
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:
{{ 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:
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:
<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.
La llamada del método ocurre en el bucle {% for %}
: question.choice_set.all
se interpreta como el código Python question.choice_set.all()
, que devuelve 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 qué 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:
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:
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
para señalar la vista de detalle con espacio de nombres:
<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.