Saltar al contenido
🐍 Curso de Python
/

Módulo 4 · Lección 6 de 45

Tu primera vista y su URL

Una función que recibe una petición y devuelve una respuesta, y la lista que decide cuál se llama.

⏱ 13 min de lectura

Cinco lecciones de preparación y todavía no has escrito una línea de código tuyo. Se acabó. En esta se escriben las dos piezas mínimas de cualquier página de Django: una vista, que es una función, y una ruta, que es una línea en una lista.

Son literalmente eso. Django tiene fama de grande y de tener mucho que aprender, y es verdad, pero el núcleo de lo que hace cabe en las dos cosas de esta lección. Todo lo demás del módulo son comodidades construidas encima.

Una vista es una función#

Crea un fichero vistas.py dentro del paquete de configuración, al lado de settings.py:

agenda/vistas.py
from django.http import HttpResponse


def portada(peticion):
        return HttpResponse("<h1>Agenda de contactos</h1>")

Eso es una vista completa. Repasa lo que tiene y, sobre todo, lo que no tiene: no hereda de nada, no lleva ningún decorador, no está registrada en ninguna parte y no sabe nada de Django más allá del objeto que devuelve.

El contrato es de una línea: una vista recibe la petición como primer argumento y devuelve una respuesta. Si cumple eso, a Django le vale.

El parámetro se llama request en toda la documentación y en el noventa y nueve por ciento del código que veas por ahí. Aquí se llama peticion, siguiendo la costumbre del curso de escribir en español, igual que en el módulo anterior la ventana se llamaba ventana y no root. El nombre no importa, es un parámetro posicional normal; lo que importa es que es el primero.

Ese objeto trae dentro todo lo que llegó por la red, con los nombres de la lección 2:

AtributoQué tiene
peticion.method'GET', 'POST'...
peticion.pathLa ruta pedida, como '/contactos/'
peticion.GETLo que venía detrás del ? en la dirección
peticion.POSTLos datos del formulario
peticion.headersLas cabeceras

HttpResponse es el otro lado: recibe el texto que quieres mandar y construye la respuesta con su código 200 y su Content-Type: text/html; charset=utf-8. Tiene un parecido evidente con el send_response que escribías a mano en la lección 2, y hace lo mismo, con todos los detalles resueltos.

Conectarla a una dirección#

La vista existe pero nadie la llama. Falta decirle a Django qué dirección le corresponde, y eso se hace en urls.py:

agenda/urls.py
from django.contrib import admin
from django.urls import path

from . import vistas

urlpatterns = [
    path('', vistas.portada),
    path('admin/', admin.site.urls),
]

Tres cosas de esas líneas:

from . import vistas es una importación relativa, de las de la lección 41 del módulo 1. El punto significa "el paquete en el que estoy", así que importa agenda/vistas.py. Se usa la forma relativa porque así el paquete se podría renombrar sin tocar esta línea.

path('', vistas.portada) empareja una ruta con una vista. La cadena vacía es la raíz del sitio, o sea /. Y fíjate en que se pasa vistas.portada, sin paréntesis: se está entregando la función, no llamándola. Es la misma distinción del command=saludar de Tkinter, y produce el mismo tipo de fallo si te equivocas.

urlpatterns tiene que llamarse así. Es el único nombre que Django busca en este fichero.

Arranca el servidor y visita http://127.0.0.1:8000/. La página del cohete ha desaparecido y en su sitio está tu título.

Una segunda vista#

Con dos se ve mejor cómo encaja todo. Añade a vistas.py:

agenda/vistas.py
from django.http import HttpResponse


def portada(peticion):
        return HttpResponse("<h1>Agenda de contactos</h1>")


def contactos(peticion):
        nombres = ["Ana Ruiz", "Luis Pardo"]
    filas = "".join(f"<li>{n}</li>" for n in nombres)
    return HttpResponse(f"<h1>Contactos</h1><ul>{filas}</ul>")

Y su línea en urls.py:

python
urlpatterns = [
    path('', vistas.portada),
    path('contactos/', vistas.contactos),
    path('admin/', admin.site.urls),
]
bash
curl -s http://127.0.0.1:8000/contactos/
Salida en pantalla
<h1>Contactos</h1><ul><li>Ana Ruiz</li><li>Luis Pardo</li></ul>

Los nombres están escritos a mano y el HTML se construye con f-strings. Las dos cosas son provisionales: los datos vendrán de la base de datos en el bloque B y el HTML saldrá de una plantilla en el C. Ahora mismo lo que importa es que el recorrido completo funciona.

Cuando la ruta no existe#

Pide http://127.0.0.1:8000/nada y sale un 404. Con DEBUG = True, esa página no es la neutra que ve el público: te dice exactamente qué ha intentado.

Salida en pantalla
Using the URLconf defined in agenda.urls, Django tried these URL patterns, in this order:

  1. [name='']
  2. contactos/
  3. admin/

The current path, nada, didn't match any of these.

Esa página contesta de una vez las tres preguntas que uno se hace: qué fichero de rutas se está usando, qué patrones hay dentro y en qué orden, y con qué ruta se han comparado. Casi todos los 404 que te encuentres mientras desarrollas se resuelven leyéndola: o falta la línea en urlpatterns, o el patrón no es el que creías, o hay uno anterior que se lo está comiendo.

Fíjate en in this order. urlpatterns se recorre de arriba abajo y gana el primero que encaja; los de debajo ni se miran.

La barra del final#

Prueba a pedir la dirección sin la barra final:

bash
curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" http://127.0.0.1:8000/contactos
Salida en pantalla
301 -> http://127.0.0.1:8000/contactos/

No es un 404: es un 301 a la versión con barra. Lo hace Django, con un ajuste que viene activado y que se llama APPEND_SLASH. Cuando una ruta no encaja con nada, prueba a añadirle una barra al final, y si así sí encaja, redirige.

Esto explica la costumbre de Django de escribir todos los patrones terminados en barra. No es adorno: es que con APPEND_SLASH activado, las direcciones con barra funcionan con y sin ella, y las que no la llevan solo funcionan exactamente como están escritas. Conviene seguir la costumbre por coherencia y porque toda la documentación lo hace así.

Cuando la vista falla#

Las dos formas de romper una vista, y qué se ve al hacerlo.

Una excepción cualquiera:

python
def rota(peticion):
        total = 1 / 0
    return HttpResponse(total)

Sale la página amarilla con ZeroDivisionError: division by zero, la traza completa y el código de alrededor. Y en la terminal:

Salida en pantalla
Internal Server Error: /rota/

Django ha recogido la excepción, ha contestado un 500 y sigue funcionando: el servidor no se cae. Es un comportamiento cómodo y tiene una trampa, porque en producción esos 500 ocurren en silencio si nadie ha montado un registro.

Olvidarse del return, que es el error propio de quien empieza:

python
def olvidadiza(peticion):
        HttpResponse("me falta el return")

La función construye la respuesta y no la devuelve, así que devuelve None. El mensaje es de los buenos:

Salida en pantalla
ValueError: The view agenda.vistas.olvidadiza didn't return an HttpResponse object.
It returned None instead.

Dice qué vista es, qué debía devolver y qué ha devuelto. Cuando lo veas, la respuesta casi siempre es un return que falta, o un if con una rama por la que se sale sin devolver nada.