Creación de vistas de la interfaz pública

DjangoDjangoBeginner
Practicar Ahora

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

Este tutorial comienza donde dejó Configurar la base de datos. Continuaremos con la aplicación de sondeo web y nos centraremos en la creación de la interfaz pública: "vistas".


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL django(("Django")) -.-> django/CoreConfigurationandRoutingGroup(["Core Configuration and Routing"]) django(("Django")) -.-> django/DatabaseModelsandMigrationsGroup(["Database, Models, and Migrations"]) django(("Django")) -.-> django/UserInterfaceandInteractionGroup(["User Interface and Interaction"]) django/CoreConfigurationandRoutingGroup -.-> django/django_urls("Django Urls") django/CoreConfigurationandRoutingGroup -.-> django/django_exceptions("Django Exceptions") django/DatabaseModelsandMigrationsGroup -.-> django/models("Models") django/DatabaseModelsandMigrationsGroup -.-> django/databases("Databases") django/DatabaseModelsandMigrationsGroup -.-> django/request_and_response("Request and Response") django/DatabaseModelsandMigrationsGroup -.-> django/schemaeditor("SchemaEditor") django/UserInterfaceandInteractionGroup -.-> django/templates("Templates") subgraph Lab Skills django/django_urls -.-> lab-153743{{"Creación de vistas de la interfaz pública"}} django/django_exceptions -.-> lab-153743{{"Creación de vistas de la interfaz pública"}} django/models -.-> lab-153743{{"Creación de vistas de la interfaz pública"}} django/databases -.-> lab-153743{{"Creación de vistas de la interfaz pública"}} django/request_and_response -.-> lab-153743{{"Creación de vistas de la interfaz pública"}} django/schemaeditor -.-> lab-153743{{"Creación de vistas de la interfaz pública"}} django/templates -.-> lab-153743{{"Creación de vistas de la interfaz pública"}} end

Resumen

Una vista es un "tipo" de página web en su aplicación de Django que generalmente tiene una función específica y una plantilla específica. Por ejemplo, en una aplicación de blog, es posible que tenga las siguientes vistas:

  • Página principal del blog: muestra las últimas entradas.
  • Página de "detalles" de la entrada: página con enlace permanente para una sola entrada.
  • Página de archivos basada en el año: muestra todos los meses con entradas en el año dado.
  • Página de archivos basada en el mes: muestra todos los días con entradas en el mes dado.
  • Página de archivos basada en el día: muestra todas las entradas del día dado.
  • Acción de comentario: maneja la publicación de comentarios a una entrada determinada.

En nuestra aplicación de sondeo, tendremos las siguientes cuatro vistas:

  • Página de "índice" de preguntas: muestra las últimas preguntas.
  • Página de "detalles" de la pregunta: muestra el texto de una pregunta, sin resultados pero con un formulario para votar.
  • Página de "resultados" de la pregunta: muestra los resultados de una pregunta en particular.
  • Acción de voto: maneja el voto por una opción particular en una pregunta en particular.

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

Ahora, durante su tiempo en la web, es posible que haya encontrado hermosas URLs como ME2/Sites/dirmod.htm?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B. Tendrá el agrado de saber que Django nos permite patrones de URL mucho más elegantes que eso.

Un patrón de URL es la forma general de una URL, por ejemplo: /newsarchive/<year>/<month>/.

Para pasar de una URL a una vista, Django utiliza lo que se conoce como 'URLconfs'. Un URLconf mapea patrones de URL a vistas.

Este tutorial proporciona instrucciones básicas sobre el uso de URLconfs, y puede consultar /topics/http/urls para obtener más información.

Escribiendo más vistas

Ahora agreguemos algunas 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)

Conecte estas nuevas vistas al módulo polls.urls agregando las siguientes llamadas a ~django.urls.path:

Edite el archivo polls/urls.py y agregue las siguientes líneas:

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"),
]

Ahora, ejecute el servidor nuevamente:

cd ~/project/mysite
python manage.py runserver 0.0.0.0:8080

Vaya a la pestaña Web 8080, en /polls/34/. Ejecutará el método detail() y mostrará el ID que proporcione en la URL. Pruebe también /polls/34/results/ y /polls/34/vote/; estos mostrarán las páginas de resultados y de votación con marcadores de posición.

Django URL routing diagram

Cuando alguien solicita una página de su sitio web, digamos, /polls/34/, Django cargará el módulo de Python mysite.urls porque está apuntado por la configuración ROOT_URLCONF. Encuentra la variable llamada urlpatterns y recorre los patrones en orden. Después de encontrar la coincidencia en 'polls/', quita el texto coincidente ("polls/") y envía el texto restante, "34/", al URLconf de 'polls.urls' para su procesamiento adicional. Allí coincide con '<int:question_id>/', lo que resulta en una llamada a la vista detail() de la siguiente manera:

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

La parte question_id=34 proviene de <int:question_id>. Usando corchetes angulares, "captura" una parte de la URL y la envía como un argumento de palabras clave a la función de vista. La parte question_id de la cadena define el nombre que se usará para identificar el patrón coincidente, y la parte int es un convertidor que determina qué patrones deben coincidir con esta parte de la ruta de URL. El dos puntos (:) separa el convertidor y el nombre del patrón.

Escribe vistas que realmente hagan algo

Cada vista es responsable de hacer una de dos cosas: devolver un objeto ~django.http.HttpResponse que contenga el contenido de la página solicitada, o generar una excepción como ~django.http.Http404. Lo demás depende de ti.

Tu 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 de terceros para Python, o no. Puede generar un archivo PDF, emitir XML, crear un archivo ZIP en el vuelo, cualquier cosa que desees, usando cualquier biblioteca de Python que desees.

Todo lo que Django quiere es ese objeto ~django.http.HttpResponse. O una excepción.

Por conveniencia, usemos la propia API de base de datos de Django, que cubrimos en el Tutorial 2. Aquí está un intento de una nueva vista index(), que muestra las últimas 5 preguntas de sondeo del sistema, separadas por comas, según la fecha de publicación:

Edite el archivo polls/views.py y cámbielo para que se vea así:

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)


## Deje las demás vistas (detail, results, vote) sin cambios

Sin embargo, aquí hay un problema: el diseño de la página está codificado en duro en la vista. Si quieres cambiar la forma en que se ve la página, tendrás que editar este código de Python. Entonces, usemos el sistema de plantillas de Django para separar el diseño del Python creando una plantilla que la vista pueda usar.

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

La configuración TEMPLATES de su proyecto describe cómo Django cargará y renderizará las plantillas. El archivo de configuración predeterminado configura un backend DjangoTemplates cuya opción APP_DIRS <TEMPLATES-APP_DIRS> está establecida en True. Por convención, DjangoTemplates busca un subdirectorio "templates" en cada una de las INSTALLED_APPS.

Dentro del directorio templates que acabas de crear, cree otro directorio llamado polls, y dentro de ese cree un archivo llamado index.html. En otras palabras, su plantilla debe estar en polls/templates/polls/index.html. Debido a cómo funciona el cargador de plantillas app_directories como se describe anteriormente, puede referirse a esta plantilla dentro de Django como polls/index.html.

Espaciado de nombres de plantillas

Ahora podríamos poder evitar poner 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 tuvieras una plantilla con el mismo nombre en una diferente aplicación, Django no sería capaz de distinguir entre ellas.

Necesitamos poder apuntar a Django a la correcta, y la mejor manera de garantizar esto es mediante el espaciado de nombres. Es decir, al poner esas plantillas dentro de otro directorio nombrado con el nombre de la aplicación misma.

Ponga 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:

Para hacer el tutorial más corto, todos los ejemplos de plantilla usan HTML incompleto. En sus propios proyectos debe usar documentos HTML completos.

Ahora actualicemos 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 mapea los nombres de variables de plantilla a objetos de Python.

Ejecute el servidor nuevamente:

python manage.py runserver 0.0.0.0:8080

Cargue la página apuntando su navegador a "/polls/", y debería ver una lista con viñetas que contiene la pregunta "¿Qué pasa?" del Tutorial 2. El enlace apunta a la página de detalles de la pregunta.

Django polls index page

Un atajo: ~django.shortcuts.render

Es un idioma muy común cargar una plantilla, llenar un contexto y devolver un objeto ~django.http.HttpResponse con el resultado de la plantilla renderizada. Django proporciona un atajo. Aquí está 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 lo hemos hecho en todas estas vistas, ya no necesitamos importar ~django.template.loader y ~django.http.HttpResponse (deberá mantener HttpResponse si todavía tiene los métodos de esbozo para detail, results y vote).

La función ~django.shortcuts.render toma el objeto de solicitud como su primer argumento, el nombre de una plantilla como su segundo argumento y un diccionario como su tercer argumento opcional. Devuelve un objeto ~django.http.HttpResponse de la plantilla dada renderizada con el contexto dado.

Lanzando un error 404

Ahora, abordemos la vista de detalles de la pregunta, la página que muestra el texto de la pregunta para un sondeo dado. 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 lanza la excepción ~django.http.Http404 si no existe una pregunta con el ID solicitado.

Vamos a discutir un poco más adelante qué podrías poner en esa plantilla polls/detail.html, pero si quieres que el ejemplo anterior funcione rápidamente, un archivo que contenga solo:

{{ question }}

te ayudará a empezar por ahora.

Un atajo: ~django.shortcuts.get_object_or_404

Es un idioma muy común usar ~django.db.models.query.QuerySet.get y lanzar ~django.http.Http404 si el objeto no existe. 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 ~django.shortcuts.get_object_or_404 toma un modelo de Django como su primer argumento y un número arbitrario de argumentos de palabras clave, que pasa a la función ~django.db.models.query.QuerySet.get del administrador del modelo. Lanza ~django.http.Http404 si el objeto no existe.

¿Por qué usamos una función auxiliar ~django.shortcuts.get_object_or_404 en lugar de capturar automáticamente las excepciones ~django.core.exceptions.ObjectDoesNotExist en un nivel superior, o hacer que la API del modelo lance ~django.http.Http404 en lugar de ~django.core.exceptions.ObjectDoesNotExist?

Porque eso uniría la capa del modelo a la capa de la vista. Uno de los objetivos de diseño principales de Django es mantener un acoplamiento débil. Un poco de acoplamiento controlado se introduce en el módulo django.shortcuts.

También hay una función ~django.shortcuts.get_list_or_404, que funciona de la misma manera que ~django.shortcuts.get_object_or_404, excepto que utiliza ~django.db.models.query.QuerySet.filter en lugar de ~django.db.models.query.QuerySet.get. Lanza ~django.http.Http404 si la lista está vacía.

Utiliza el sistema de plantillas

Volvamos a la vista detail() de nuestra aplicación de sondeo. Dada la variable de contexto question, así podría verse la plantilla 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 por puntos para acceder a los atributos de las variables. En el ejemplo de {{ question.question_text }}, primero Django realiza una búsqueda en el diccionario del objeto question. Si falla, intenta una búsqueda de atributo, lo que funciona en este caso. Si la búsqueda de atributo hubiera fallado, habría intentado una búsqueda por índice de lista.

La llamada a métodos se produce en el bucle {% for %}<for>: question.choice_set.all se interpreta como el código de Python question.choice_set.all(), que devuelve un iterable de objetos Choice y es adecuado para utilizarse en la etiqueta {% for %}<for>.

Eliminando URLs codificadas en duro en las plantillas

Recuerde, cuando escribimos el enlace a una pregunta en la plantilla polls/index.html, el enlace estaba parcialmente codificado en duro de la siguiente manera:

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

El problema de este enfoque codificado en duro y fuertemente acoplado es que se vuelve difícil cambiar las URLs en proyectos con muchas plantillas. Sin embargo, dado que definiste el argumento name en las funciones ~django.urls.path del módulo polls.urls, puedes eliminar la dependencia de las rutas de URL específicas definidas en tus configuraciones de URL mediante la etiqueta de plantilla {% url %}:

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

La forma en que funciona es buscando la definición de URL como se especifica en el módulo polls.urls. Puedes ver exactamente dónde se define el nombre de URL de 'detail' a continuación:

## el valor 'name' como se llama por la etiqueta de plantilla {% url %}
path("<int:question_id>/", views.detail, name="detail"),

Si quieres cambiar la URL de la vista de detalles de los sondeos a algo diferente, quizás a algo como polls/specifics/12/ en lugar de hacerlo en la plantilla (o plantillas), lo cambiarías en polls/urls.py:

No es necesario cambiar la plantilla en absoluto.

## se agregó la palabra'specifics'
path("specifics/<int:question_id>/", views.detail, name="detail"),

Espaciado de nombres de URL

El proyecto del tutorial solo tiene una aplicación, polls. En los proyectos reales de Django, pueden haber cinco, diez, veinte aplicaciones o más. ¿Cómo distingue Django los nombres de URL entre ellas? Por ejemplo, la aplicación polls tiene una vista detail, y también podría haber una aplicación en el mismo proyecto que es para un blog. ¿Cómo se hace para que Django sepa qué vista de aplicación crear para una URL cuando se utiliza la etiqueta de plantilla {% url %}?

La respuesta es agregar espacios de nombres a su URLconf. En el archivo polls/urls.py, agregue un app_name para establecer el espacio de nombres de la aplicación:

from django.urls import path

from. import views

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

Ahora cambie su plantilla polls/index.html de:

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

para apuntar a la vista de detalles con espaciado de nombres:

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
URL namespacing example

Cuando esté cómodo escribiendo vistas, lea Procesamiento de formularios y reducción de nuestro código para aprender los conceptos básicos sobre el procesamiento de formularios y vistas genéricas.

Resumen

¡Felicidades! Has completado el laboratorio de Creación de Vistas de la Interfaz Pública. Puedes practicar más laboratorios en LabEx para mejorar tus habilidades.