Volver a la wiki

URLs de blueprints/maps: redirecciones en lugar de includes sin barra

Contexto

El paquete blueprints.urls estaba incluido cuatro veces en config/urls.py:

El problema: includes sin barra

Cuando Django incluye un URLconf bajo un prefijo sin barra final, concatena los paths hijos directamente sin separador. Esto genera rutas absurdas pero alcanzables:

Peor: reverse() devolvía ESAS rutas pegadas:

reverse("blueprint_detail", args=[3])  # → "/maps3/" (antes)
reverse("blueprint_editor", args=[3])  # → "/mapseditor/3/" (antes)

Alcance (por qué no saltó a producción)

El fallo era latente:

La primera pantalla que pidiera {% url "blueprint_detail" args=[3] %} habría devuelto /maps3/ al cliente.

Cómo se destapó

Lo descubrió el mapa de superficie generado en v1.66.6: al listar todas las URLs alcanzables leyendo el código estático, encontró rutas que nadie habría escrito a mano. La herramienta encontró algo en su primer uso real.


Decisión: RedirectView + query_string=True

Sustituir los dos include() sin barra por redirecciones explícitas:

path("blueprints/", include("blueprints.urls")),
path("maps/", include("blueprints.urls")),
path("blueprints", RedirectView.as_view(url="/blueprints/", query_string=True)),
path("maps", RedirectView.as_view(url="/maps/", query_string=True)),

Por qué redirecciones y no borrar directamente

APPEND_SLASH está desactivado a propósito en settings/base.py:

Contrato fijado por tests

Se añade tests/test_urls_blueprints_prefixes.py con 4 tests parametrizados:

  1. test_la_url_generada_esta_bien_formada: reverse() devuelve URLs bien separadas (/maps/ o /blueprints/, nunca pegadas)
  2. test_la_url_generada_resuelve_de_vuelta: la URL generada resuelve correctamente con resolve()
  3. test_el_prefijo_sin_barra_redirige_y_conserva_la_query: /blueprints y /maps redirigen 301/302 conservando query
  4. test_las_rutas_concatenadas_ya_no_existen: /blueprints5, /mapseditor/3 devuelven 404

Implicaciones arquitectónicas

Este patrón de error (include sin barra) aparece en muchas Django apps. La decisión documenta:

  1. Anti-patrón: no incluir URLconf sin barra final (aunque sea sintácticamente válido)
  2. Patrón seguro: siempre barra final en include() + redirecciones explícitas si es necesario
  3. Conservación de query: query_string=True en RedirectView para no perder parámetros de navegación
  4. Testing: parametrizar tests de URLs para asegurar ida/vuelta y no regresiones silenciosas

Véase también

Subir