Módulo 4 · Lección 21 de 45
URLs con parámetros y con nombre
Los conversores que validan antes de llegar a la vista, y dejar de escribir direcciones a mano en todo el proyecto.
La lección 6 dejó las URLs a medias: rutas fijas y un path por vista. La 16 usó un <int:pk> sin explicarlo. Esta lección cierra el tema, y de paso paga la deuda que dejó la 15, cuando get_absolute_url no se pudo escribir porque faltaban las rutas con nombre.
Son dos ideas, y la segunda es más importante de lo que parece: capturar trozos de la dirección y no volver a escribir una dirección a mano.
Conversores#
urlpatterns = [
path('', views.lista, name='lista'),
path('<int:pk>/', views.detalle, name='detalle'),
path('grupo/<slug:slug>/', views.por_grupo, name='por-grupo'),
path('anio/<int:anio>/<int:mes>/', views.por_fecha, name='por-fecha'),
]Lo de dentro de los ángulos son dos cosas separadas por dos puntos: el conversor y el nombre. El nombre es el del parámetro de la vista, así que <int:pk> obliga a que la función tenga un parámetro pk.
Los cinco que trae Django:
| Conversor | Acepta | Llega a la vista como |
|---|---|---|
str | Cualquier cosa menos / | str |
int | Cero y enteros positivos | int |
slug | Letras, números, guiones y guiones bajos | str |
uuid | Un identificador con su formato exacto | UUID |
path | Cualquier cosa, incluida la / | str |
Medidos, para que se vea que no solo recortan sino que convierten:
grupo/mi-grupo-2/ -> grupo='mi-grupo-2' tipo=str
anio/2026/09/ -> anio=2026 mes=9 tipo=int
buscar/a/b/c/ -> resto='a/b/c'
id/12345678-1234-5678-1234-567812345678/ -> tipo=UUIDDos cosas que merecen atención. El 09 llega como el entero 9, no como la cadena '09': el conversor convierte, no solo comprueba. Y path se traga las barras, lo que lo hace útil para el último trozo de una ruta y peligroso en medio, porque se come todo lo que venga detrás.
Si no pones conversor, <pk> es lo mismo que <str:pk>.
Cuando los cinco no llegan#
Para un formato propio está re_path, con una expresión regular del módulo 1:
from django.urls import re_path
re_path(r'^dni/(?P<dni>\d{8}[A-Z])/$', views.eco_dni, name='eco-dni'),dni/12345678Z/ -> 200 dni='12345678Z'
dni/1234Z/ -> 404Los grupos tienen que llevar nombre, con esa sintaxis (?P<nombre>...), y el nombre es el del parámetro de la vista.
re_path es más potente y bastante menos legible, así que la costumbre sana es usarlo solo cuando hace falta. La alternativa elegante, cuando el mismo formato se repite en varias rutas, es escribir un conversor propio: una clase con un regex, un to_python y un to_url, que se registra con register_converter. Con eso el patrón vuelve a leerse como los demás.
Rutas con nombre#
Esto es la mitad importante de la lección.
path('<int:pk>/', views.detalle, name='detalle'),app_name = 'contactos'El name bautiza la ruta. El app_name mete todos los nombres de ese fichero en un espacio de nombres, y a partir de ahí la ruta se llama contactos:detalle. Es lo mismo que la carpeta repetida de las plantillas: evita que dos aplicaciones con una ruta llamada lista se estorben.
El trato es este: escribes la dirección una vez, en el fichero de rutas, y en el resto del proyecto la pides por su nombre. El día que /contactos/3/ pase a ser /agenda/personas/3/, cambia una línea.
Desde Python#
from django.urls import reversereverse('contactos:lista') -> /contactos/
reverse('contactos:detalle', args=[3]) -> /contactos/3/
reverse('contactos:detalle', kwargs={'pk': 3}) -> /contactos/3/
reverse('contactos:por-fecha', kwargs={'anio': 2026, 'mes': 9}) -> /contactos/anio/2026/9/Con args va por posición y con kwargs por nombre. Con más de un parámetro, kwargs se lee mejor y no se equivoca de orden.
Desde la plantilla#
{% url 'contactos:lista' %}
{% url 'contactos:detalle' contacto.pk %}
{% url 'contactos:detalle' pk=contacto.pk as u %}A url simple : /contactos/
B url con arg : /contactos/3/
C url con as : [/contactos/3/]Mismo mecanismo. El nombre de la ruta va entre comillas; sin ellas, Django lo toma por una variable, que no existe, y con el silencio de la lección 17 acaba buscando una ruta llamada cadena vacía. La forma con as guarda la dirección en una variable, útil cuando se usa dos veces o cuando hace falta comprobar antes si existe.
get_absolute_url#
Ahora ya se puede escribir lo que la lección 15 dejó pendiente:
from django.urls import reverse
class Contacto(models.Model):
# ...
def get_absolute_url(self):
return reverse('contactos:detalle', kwargs={'pk': self.pk})D absolute : /contactos/3/
E enlace : <a href="/contactos/3/">Zoe Alvarez</a>Es un método con un nombre especial, y ese nombre importa porque hay código que lo busca:
redirect(objeto) -> 302 /contactos/3/
redirect('contactos:detalle', pk=3) -> 302 /contactos/3/
redirect('contactos:lista', permanent=True) -> 301 /contactos/redirect acepta un objeto y sabe dónde mandar. Las vistas genéricas de la lección 24 lo usan después de guardar. El panel de administración pone un botón de "ver en el sitio" si existe. Y en las plantillas, {{ contacto.get_absolute_url }} es más corto que el {% url %} equivalente.
La regla: si un objeto tiene una página propia, escribe get_absolute_url. Son tres líneas y las aprovecha medio Django.
Fíjate de paso en el permanent=True: ahí está la diferencia entre 301 y 302 de la lección 2, y la advertencia de entonces sigue en pie, porque un 301 se queda cacheado en los navegadores durante mucho tiempo.
Incluir y ordenar#
urlpatterns = [
path('', vistas.portada, name='portada'),
path('contactos/', include('contactos.urls')),
path('admin/', admin.site.urls),
]include engancha el fichero de rutas de la aplicación bajo un prefijo, como se vio en la lección 7. El prefijo está solo aquí, así que dentro de la aplicación las rutas se escriben sin él, y mover la sección entera a otra dirección es cambiar esta línea.
Y una cosa que hay que tener presente: Django prueba los patrones en orden y se queda con el primero que encaje. No busca el que mejor encaja, el primero.
path('<int:pk>/', views.detalle, name='detalle'),
path('nuevo/', views.nuevo, name='nuevo'), # bien: 'nuevo' no es un int
path('<str:nombre>/', views.buscar),
path('nuevo/', views.nuevo), # mal: nunca se llegaCon conversores de tipo esto casi nunca muerde, porque nuevo no encaja con <int:pk>. Con <str:...> sí muerde, y el síntoma es una vista que no se ejecuta nunca sin que nada avise. Las rutas fijas van antes que las que capturan cadenas.