URLs¶
Django básico
Cuando Django recibe una petición HTTP lo primero que hace es intentar encontrar el patrón que coincide con la URL solicitada:
flowchart TD
client[Client] -->|"https://myblog.com/posts/django-is-awesome/"| django[Django]
subgraph "URLs de primer nivel"
main_urls[main/urls.py]
end
django -->|"/posts/django-is-awesome/"| main_urls
subgraph "URLs de segundo nivel"
main_urls -->|"django-is-awesome/"| post_urls[posts/urls.py]
end
post_urls -->|"'django-is-awesome'"| post_views[posts/views.py]
subgraph "Vistas"
post_views --> post_detail["post_detail('django-is-awesome')"]
end
En esta sección veremos cómo configurar estos patrones para lanzar las acciones oportunas.
URLs de primer nivel¶
Si hemos creado el proyecto Django con la carpeta base main podremos encontrar las URLs de primer nivel en el fichero main/urls.py.
El contenido (por defecto) de este fichero es el siguiente:
from django.contrib import admin#(1)!
from django.urls import path#(2)!
urlpatterns = [#(3)!
path('admin/', admin.site.urls),#(4)!
]
- Este módulo contiene las funcionalidades de la interfaz administrativa de Django.
- Esta función nos permite definir las rutas URL correspondientes.
- Las URLs deben almacenarse en una lista con nombre
urlpatterns. -
- Cada URL viene definida por la función path que vincula (en general) una ruta con una vista.
- En este caso se indica que si la URL de entrada es
/admin/se pase el control al módulo admin.site.urls.
URLs de segundo nivel¶
Cada aplicación en un proyecto Django puede tener sus propias URLs que definen el comportamiento de la misma.
Supongamos por ejemplo que estamos desarrollando una aplicación llamada posts y queremos que las siguientes URLs cobren vida:
/posts/#(1)!/posts/this-is-a-new-post/#(2)!
- Listado de todos los «posts» del «blog».
- Detalle de un «post» en concreto con el slug
this-is-a-new-post.
Lo primero será modificar el fichero de configuración de las URLs de primer nivel para añadir la delegación a la aplicación correspondiente:
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
path('posts/', include('posts.urls')),#(2)!
]
- Importamos la función
include(). -
- Indicamos que las URLs que comiencen por
/posts/deben delegarse a las URLs de segundo nivel que están enposts/urls.py. - Las urls se especifican usando una cadena de texto «cualificada».
- Cabe la posibilidad de especificar un argumento
namespacepara sobreescribir el valor asignado aapp_name(urls.py).
- Indicamos que las URLs que comiencen por
Barra final
Para evitar problemas, recuerda siempre acabar las URLs con la barra / del final.
Por ejemplo 'comments/' en vez de 'comments
Ahora ya podemos definir las URLs de segundo nivel en la aplicación posts creando el fichero posts/urls.py con el siguiente contenido:
from django.urls import path
from . import views
app_name = 'posts'#(1)!
urlpatterns = [
path('', views.post_list, name='post-list'),#(2)!
path('<slug:post_slug>/', views.post_detail, name='post-detail'),#(3)!
]
- La variable
app_namedefine el espacio de nombres de las URLs de cada aplicación. - Analicemos cada parámetro de la función
pathpor separado:''Si concatenamos/posts/con la cadena vacía, obtenemos que la URL resultante es:/posts/views.post_listvista que se lanzará si la URL casa con este patrón.name='post-list'nombre de la URL, que unido al espacio de nombres, lo identifican unívocamente en todo el proyecto. Por tanto será:posts:post-list.
- Analicemos cada parámetro de la función
pathpor separado:'<post_slug>'El uso de ángulos nos indica que se trata de un parámetro variable. Casa con cualquier entrada. Si concatenamos/posts/con<post_slug>obtenemos que la URL resultante es/posts/this-is-a-new-post/views.post_detailvista que se lanzará si la URL casa con este patrón.name='post-detail'nombre de la URL, que unido al espacio de nombres, lo identifican unívocamente en todo el proyecto. Por tanto será:posts:post-detail.
Nombres de URLs
Es habitual usar «slugs» en los nombres de URLs. Es decir, cuando utilizamos el parámetro name de la función path. En vez de name='post_list' suele ser de buen estilo escribir name='post-list'.
Agrupar URLs¶
En el caso de tener distintos patrones de URLs en un mismo fichero urls.py se aconseja agrupar los patrones por similitud.
Un pequeño ejemplo en el que se plantea este escenario:
Conversores de rutas¶
En las rutas dinámicas (aquellas que contienen parámetros variables) es posible indicar el tipo de cada parámetro para que Django realice una conversión «implícita» al tipo de dato correspondiente.
La sintaxis de un conversor de ruta es la siguiente: path(<param:converter>, ...)
Conversores predefinidos¶
Veamos una tabla resumen con los conversores de rutas predefinidos en Django:
| Conversor | Ejemplo | Explicación |
|---|---|---|
path('<username>', ...) |
/guido/ |
Equivalente al conversor str |
path('<str:query>', ...) |
/django+python+dev/ |
Casa con cualquier cadena de caracteres excluyendo el separador / y retorna un str |
path('<int:post_id>', ...) |
/4673/ |
Casa con 0 o un entero positivo y retorna int |
path('<slug:product_slug>', ...) |
/display-23-inches/ |
Casa con un «slug» y retorna str |
path('<uuid:token>', ...) |
/075194d3-6885-417e-a8a8-6c931e272f00/ |
Casa con cualquier UUID y retorna un objeto UUID |
path('<path:resource_path>', ...) |
/products/tech/logitech-keyboard/ |
Casa con cualquier cadena de caracteres incluyendo el separador / y retorna un str |
Conversores personalizados¶
Django intermedio
Django permite crear registrar conversores de rutas personalizados de tal forma que obtenemos un objeto del tipo (clase) deseado directamente en la vista.
Y además es muy fácil de implementar. Lo único que necesitamos es escribir una clase con dos métodos concretos y registrarla convenientemente.
Veamos un ejemplo donde creamos un conversor personalizado para un «post» de un «blog» a partir de su slug:
from django.shortcuts import get_object_or_404
from .models import Post#(1)!
class PostConverter:#(2)!
regex = r'[\w-]+'#(3)!
def to_python(self, post_slug: str) -> Post:#(4)!
return get_object_or_404(Post, slug=post_slug)#(5)!
def to_url(self, post: Post) -> str:#(6)!
return post.slug
- Importamos el modelo sobre el que vamos a trabajar.
- Aunque sólo es una convención, si el modelo es
ModelllamamosModelConverteral conversor. - Hay que especificar la expresión regular que captura el patrón en la URL.
-
- Este método convierte el patrón capturado
stren el objeto correspondiente. - URL Python
- Este método convierte el patrón capturado
- Ver consulta no encontrada.
-
- Este método convierte el objeto a la subruta correspondiente de la URL.
- Python URL
from django.urls import path, register_converter#(1)!
from . import converters, views#(2)!
app_name = 'posts'
register_converter(converters.PostConverter, 'post')#(3)!
urlpatterns = [
path('', views.post_list, name='post-list')
path('<post:post>/', views.post_detail, name='post-detail')#(4)!
]
- Necesitamos importar la función
register_converter()para registrar el conversor. - Necesitamos importar el módulo
convertersde conversores personalizados. - Registramos el conversor asignándole un identificador que utilizaremos en la ruta.
- Indicamos que estamos capturando un objeto de tipo «post» con el conversor previamente registrado.
Redirección¶
Se considera una mala práctica «hardcodear»1 las URLs directamente (tanto en vistas como en plantillas) ya que, ante un determinado cambio de una URL en el futuro, tendremos que localizar todas las ocurrencias de dicha URL en el código y modificarlas.
Para resolver esta problemática, Django nos «anima» a utilizar nombres de URLs en la función path() dentro de los distintos ficheros urls.py.
En el ejemplo anterior del «blog» se han definido las siguientes URLs:
# ...
app_name = 'posts'
urlpatterns = [
path('', views.post_list, name='post-list'),
path('<slug:post_slug>/', views.post_detail, name='post-detail'),
]
Así las cosas, Django nos permite identificar cada URL mediante <app_name>:<url_name>:
'posts:post-list'identifica la URL del listado de «posts».'posts:post-detail'identifica la URL del detalle de un «post».
En una vista usaremos la función redirect() para hacer redirecciones y pasar el control a otra URL:
Redirección permanente
Django aplica por defecto una redirección temporal 302. Si lo que se quiere es realizar una redirección permanente 301 habrá que usar el argumento permanent=True en la función redirect().
Como era esperable, también podremos redirigir a cualquier otra URL externa que queramos: redirect('https://python.org').
URL desde nombre¶
Django intermedio
Hay ocasiones en las que nos interesa obtener una URL a partir de su nombre (alias). Para ello, Django proporciona la función reverse().
Continuando con el ejemplo previo del «blog», supongamos que queremos obtener el nombre de ciertas URLs. El uso de la función reverse() depende de si la URL tiene o no parámetros:
>>> from django.urls import reverse
>>> reverse('posts:post-detail', args=['test'])#(1)!
'/posts/test/'
>>> reverse('posts:post-detail', kwargs={'post_slug': 'test'})#(2)!
'/posts/test/'
- Aproximación usando parámetros posicionales.
- Aproximación usando parámetros nominales.
Accesos directos en primer nivel¶
Django intermedio
En las URLs de primer nivel podemos ir más allá del típico «include». En este sentido se abren varias posibilidades:
Apuntar a vistas.
Redireccionar a URLs.
Renderizar plantillas.
Apuntar a vistas¶
Es posible que queramos «apuntar» una determinada URL en main/urls.py a una cierta vista de una aplicación concreta.
Supongamos por ejemplo que disponemos de una vista de contacto (información acerca de la web) en la aplicación shared y que queremos apuntar directamente a dicha vista desde las URLs de primer nivel:
from django.contrib import admin
from django.urls import path, include
import shared.views#(1)!
urlpatterns = [
path('admin/', admin.site.urls),
path('posts/', include('posts.urls'))
path('contact/', shared.views.contact, name='contact')#(2)!
]
- En
main/urls.pyse recomienda importar las vistas de esta forma para evitar «colisiones» con otros espacios de nombres. - Se apunta a la vista de «contact» de la aplicación
shared.
Caso de uso
Este enfoque es más adecuado cuando la plantilla a renderizar requiere de datos que sean procesados desde la vista.
Redireccionar a URLs¶
Es posible que queramos redireccionar una determinada URL en main/urls.py a otra URL (habitualmente a través de su nombre).
Supongamos por ejemplo un escenario en el que queremos redirigir la URL raíz de nuestro blog / al listado de «posts» que hay en la plataforma:
from django.contrib import admin
from django.shortcuts import redirect
from django.urls import path, include
urlpatterns = [
path('', lambda _: redirect('posts:post-list'), name='index')#(1)!
path('admin/', admin.site.urls),
path('posts/', include('posts.urls'))
]
-
- Simulamos una vista mediante una función lambda.
- Como no usamos el supuesto parámetro
requestescribimos_como primer argumento. - Poner la redirección «lambda» en primer lugar es una buena práctica para visualizar más claramente las URLs.
Caso de uso
Este enfoque es más adecuado cuando queramos que la URL (del navegador) cambie en la propia redirección y pase el control a otra vista.
Renderizar plantillas¶
Es posible que queramos renderizar una plantilla directamente desde main/urls.py.
En un ejemplo donde tengamos una plantilla «estática» que no dependa del contexto, podemos aplicar esta técnica de manera sencilla. Supongamos que queremos renderizar una página de índice:
from django.contrib import admin
from django.shortcuts import render
from django.urls import path, include
urlpatterns = [
path('', lambda r: render(r, 'index.html'), name='index')#(1)!
path('admin/', admin.site.urls),
path('posts/', include('posts.urls'))
]
-
- Simulamos una vista mediante una función lambda.
- Necesitamos el parámetro
requestpor eso escribimosrcomo primer argumento. - Poner la redirección «lambda» en primer lugar es una buena práctica para visualizar más claramente las URLs.
Caso de uso
Este enfoque es más adecuado cuando la plantilla a renderizar no requiere de datos que sean procesados desde la vista.
Pasar argumentos a una vista¶
Django intermedio
Hay ocasiones en las que interesa pasar argumentos a una vista desde la propia URL.
Supongamos un ejemplo donde cambiamos el estado de publicación de un determinado «post» en un «blog»:
from django.urls import path
from . import views
urlpatterns = [
path(
'<slug:post_slug>/status/private/',
views.change_post_status,
name='change-post-status',
kwargs={'post_private': True},#(1)!
),
path(
'<slug:post_slug>/status/public/',
views.change_post_status,
name='change-post-status',
kwargs={'post_private': False},#(2)!
),
]
- Pasamos los argumentos que recibe la vista como nominales.
- Pasamos los argumentos que recibe la vista como nominales.
from django.http import HttpResponse
from .models import Post
def change_post_status(request, post_slug: str, post_private: bool):#(1)!
try:
post = Post.objects.get(slug=post_slug)
post.private = post_private
post.save()
except Post.DoesNotExist:
return HttpResponse('Post does not exist')
- El parámetro
post_privatevendrá establecido desdeposts/urls.py.
Expresiones regulares¶
Django avanzado
A la hora de definir los patrones en las URLs, Django nos permite utilizar expresiones regulares. Es una técnica muy potente ya que permite ir más allá de los formatos «básicos» y definir reglas más específicas.
En este escenario, en vez de utilizar la función path() usaremos la función re_path() que, como su propio nombre indica, nos permite definir rutas (URLs) mediante expresiones regulares (re).
Planteamos un ejemplo en el que queremos mostrar los «posts» de un «blog» con una determinada categoría, pero con el matiz de que el código de categoría es un «string» de 3 letras mayúsculas:
from django.urls import re_path#(1)!
from . import views
app_name = 'posts'
urlpatterns = [
re_path(#(2)!
r'^(?P<category_code>[A-Z]{3})/$',#(3)!
views.post_by_category,
name='post-by-category',
)
]
- Importamos la función.
- Utilizamos la función como el resto de patrones.
-
- Es conveniente usar cadenas en crudo para las expresiones regulares.
- También es recomendable empezar la cadena con
^(comienzo de línea) y acabarla con$(final de línea) para delimitar el patrón. - Se utiliza un grupo de captura nominal
(?P<name>)para el parámetro correspondiente. - La expresión regular viene a continuación. En este caso
[A-Z]{4}indica cuatro apariciones de cualquier letra en mayúsculas.
Mezclando patrones
Cuando usamos re_path() tenemos que utilizar expresiones regulares en toda la URL. No es posible mezclar patrones «convencionales» con patrones expresión regular.
-
«Hardcodear» significa escribir literales/valores directamente en el código. ↩