Vistas¶
Django básico
En el MVC de Django se podría decir que las vistas se corresponden con los controladores. En esta sección veremos cómo manejar las vistas y explotar sus principales funcionalidades.
La primera vista¶
Una vista no es más que una función Python que recibe (como primer parámetro) la petición HTTP (HttpRequest) y retorna una respuesta HTTP (HttpResponse):
flowchart LR
request["<tt>HttpRequest</tt>"]
response["<tt>HttpResponse</tt>"]
view{{View}}
request --> view --> response
Supongamos una primera vista que por ejemplo sea el punto de entrada de nuestra aplicación del «blog»:
from django.http import HttpResponse#(1)!
def post_list(request):#(2)!
return HttpResponse('Welcome to the ultimate blog!')#(3)!
- La clase
HttpResponsepermite devolver una respuesta HTTP al usuario. - Todas las vistas reciben como primer parámetro un objeto de tipo
HttpRequestque — por convención — se nombra comorequest. - Las vistas siempre deben devolver una respuesta (HTTP), de lo contrario se lanzará una excepción.
Contenido
En el fichero views.py sólo deben ir vistas. Cualquier otro artefacto que queramos implementar debe ir bien en el modelo o bien en un fichero auxiliar.
Renderizando plantillas¶
Si quisiéramos devolver un HTML mediante el objeto HttpResponse nos quedaría algo relativamente incómodo:
from django.http import HttpResponse
def hello_world(request):
return HttpResponse("""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Blog</title>
</head>
<body>
<h1>Welcome to the ultimate blog!</h1>
</body>
</html>""")
Es por ello que Django nos facilita esta tarea mediante el «shortcut» render que se encarga de renderizar una plantilla y devolver el contenido de la misma mediante un objeto de tipo HttpResponse.
Veamos el mismo ejemplo que antes pero utilizando esta funcionalidad:
from django.shortcuts import render#(1)!
def post_list(request):
return render(request, 'posts/post/list.html')#(2)!
- Importamos la función
render()desde los «shortcuts» de Django. - La función
render()siempre recibe como primer parámetro la peticiónHttpRequesty como segundo parámetro la ruta a la plantilla.
Pasando un contexto¶
Una de las ventajas importantes del uso de plantillas en Django es la posibilidad de «inyectar» variables. Dicho de otro modo, cuando estamos renderizando una plantilla podemos pasar un contexto con las variables que vamos a utilizar.
Para seguir evolucionando el listado de «posts» necesitaríamos obtener todos los «posts» del blog y pasar un contexto con ellos. Veamos un ejemplo de este escenario mediante la siguiente vista:
from django.shortcuts import render
from .models import Post#(1)!
def post_list(request):
posts = Post.objects.all()#(2)!
return render(
request,
'posts/post/list.html',
{'posts': posts},#(3)!
)
- Necesitamos importar el modelo
Postpara utilizarlo en la vista. - Hacemos una consulta y recuperamos todos los objetos de tipo «post» existentes en la base de datos.
-
- La función
render()admite un tercer parámetro (contexto) que es un diccionario. - De esta forma podemos «generalizar» la plantilla y parametrizarla con el contexto en función del contenido de la base de datos.
- La función
Vistas con parámetros¶
Es bastante probable que la vista reciba una serie de parámetros desde la URL. Esto le permitirá atender a distintas casuísticas en función de los valores de entrada de dichos parámetros.
Veamos un ejemplo de vista que recibe el «slug» de un «post»:
from django.http import HttpResponse
from django.shortcuts import render
from .models import Post
def post_detail(request, post_slug: str):
try:
post = Post.objects.get(slug=post_slug)
except Post.DoesNotExist:
return HttpResponse(f'Post with slug "{post_slug}" does not exist!')
return render(
request,
'posts/post/detail.html',
{'post': post},
)
Método de la petición¶
Hay ocasiones, especialmente en el manejo de formularios que nos interesa detectar el método de la petición HTTP para tomar una acción u otra en función de ello.
Veamos un fragmento de código en el que identificamos el método HTTP utilizado en la petición:
def view(request):
if request.method == 'POST':#(1)!
# This is a POST method HTTP request
else:#(2)!
# This is (probably) a GET method HTTP request
- El atributo
methodde los objetosHttpRequestnos informan del método HTTP mediante una cadena de texto en mayúsculas. -
- Aunque existen múltiples métodos HTTP para una petición, lo habitual es trabajar con dos de ellos:
GETyPOST. - En cualquier caso si se quiere asegurar que es un método
GETbasta con indicarlo explícitamenteif request.method == 'GET':
- Aunque existen múltiples métodos HTTP para una petición, lo habitual es trabajar con dos de ellos:
Decorando vistas¶
Los decoradores en Python permite modificar el comportamiento de una función de un modo poco intrusivo y bastante elegante.
Hay escenarios en los que vemos que se repite un determinado bloque de código y que podría ser razonable refactorizar. Obviamente una primera opción es extraer a una función, pero un decorador también puede ser una buena estrategia.
En el ejemplo del «blog» pensemos que en cada vista que maneja un «post» (detalle, borrado o edición) tenemos que localizar dicho «post» mediante su «slug». A continuación se muestra una posible implementación mediante decoradores:
from django.http import HttpResponse
from .models import Post
def post_handle(view):
def wrapper(request, post_slug, *args, **kwargs):#(1)!
try:
request.post = Post.objects.get(slug=post_slug)#(2)!
except Post.DoesNotExist:
return HttpResponse(f'Post with slug "{post_slug}" does not exist!')
return view(request, post_slug, *args, **kwargs)
return wrapper
- Los dos primeros parámetros posicionales coinciden con los parámetros de la vista.
- Para poder disponer del «post» en la vista, inyectamos el «post» en el objeto
request.
from django.shortcuts import render
from .decorators import post_handle#(1)!
@post_handle#(2)!
def post_detail(request, post_slug: str):
return render(request, 'posts/post/detail.html', {'post': request.post})#(3)!
- Importamos el decorador correspondiente.
- Decoramos la vista.
- Utilizamos el «post» previamente inyectado en la propia petición («request») HTTP.
Hay otras formas de llevar a cabo aproximaciones similares:
Tipos de respuestas¶
Django intermedio
La clase HttpResponse permite indicar el código de estado de la respuesta. Esto hace posible ser más exactos en el mensaje que trasladamos a un cliente.
Para ello usaremos el atributo status con un valor numérico (del código de estado) que podemos encontrar en este enlace.
Supongamos por ejemplo que queremos incorporar este enfoque en el detalle de un «post» de un «blog»:
from django.shortcuts import render
from django.http import HttpResponse
from .models import Post
def post_detail(request, post_slug: str):
try:
post = Post.objects.get(slug=post_slug)
except Post.DoesNotExist:
return HttpResponse(
f"Post with slug '{post.slug}' does not exist",
status=404,
)
return render(request, 'posts/post/detail.html', {'post': post})
Pero Django ofrece ciertas clases ya predefinidas para cubrir algunos códigos de estado HTTP habituales:
status |
Significado | Clase |
|---|---|---|
| 400 | Bad request | HttpResponseBadRequest() |
| 401 | Unauthorized | - |
| 403 | Forbidden | HttpResponseForbidden() |
| 404 | Not Found | HttpResponseNotFound() |
| 405 | Method Not Allowed | HttpResponseNotAllowed() |
| 409 | Conflict | - |
| 422 | Unprocessable Content | - |
| 500 | Internal Server Error | HttpResponseServerError() |
Diferencias relevantes:
Suele ser fuente de confusión, diferenciar los códigos de estado HTTP 401 y 403. Vamos a aclararlo:
- 401 (Unauthorized) «No estás autenticado (no has demostrado quién eres)». Ejemplo
@login_required - 403 (Forbidden) «Sí estás autenticado, pero no tienes permiso para acceder a este recurso». Ejemplo
/admin/
Suele ser fuente de confusión, diferenciar los códigos de estado HTTP 400 y 409. Vamos a aclararlo:
- 400 (Bad Request) «La petición no se ha podido procesar porque la petición es incorrecta». Ejemplo falta un campo obligatorio en un formulario.
- 409 (Conflict) «La petición es correcta, pero no se ha podido procesar debido a un conflicto con el estado actual del recurso». Ejemplo intentar crear un recurso que ya existe.
Por tanto, el ejemplo anterior de «post» no encontrado se podría reescribir de la siguiente manera:
from django.shortcuts import render
from django.http import HttpResponseNotFound
from .models import Post
def post_detail(request, post_slug: str):
try:
post = Post.objects.get(slug=post_slug)
except Post.DoesNotExist:
return HttpResponseNotFound(
f"Post with slug '{post.slug}' does not exist",
)
return render(request, 'posts/post/detail.html', {'post': post})
Consulta no encontrada¶
Django proporciona un «shortcut» (atajo) para cuando queremos recuperar un objeto pero —en vez de lanzar una excepción de tipo DoesNotExist— devolver un Http404.
Por ejemplo podemos reescribir la vista que recupera un «post» de la base de datos a partir de su «slug»:
from django.shortcuts import get_object_or_404, render
from .models import Post
def post_detail(request, post_slug: str):
post = get_object_or_404(Post, slug=post_slug)#(1)!
return render(request, 'posts/post/detail.html', {'post': post})
- El primer parámetro es el modelo y el segundo es el filtro/condición a aplicar.
Vistas de error personalizadas¶
Cuando Django devuelve una respuesta con código de estado HTTP distinto a 200, muestra una página de error predefinida, y en muchos casos, no del todo «estética» según se mire.
Pero existe la posibilidad de personalizar esas vistas predefinidas de error para incorporar lógica adicional y/o nuevos diseños de plantilla.
Veamos un ejemplo para personalizar la vista y plantilla para un código de error 404 (no encontrado), aunque sería análogo para cualquier otro:
from django.shortcuts import render
def custom_404(request, exception):#(1)!
context = {}#(2)!
return render(request, '404.html', context, status=404)#(3)!
-
- Este nombre de vista no es obligatorio. Puede ser cualquiera.
- Además de la petición HTTP se recibe la excepción levantada.
- Como cualquier otra vista, podemos definir un contexto para pasar a la plantilla.
-
- Indicamos la plantilla a renderizar.
- El parámetro
statusdefine el código de estado de la respuesta.
from django.contrib import admin
from django.urls import include, path
handler404 = 'shared.views.custom_404'#(1)!
urlpatterns = [
path('admin/', admin.site.urls),
path('posts/', include('posts.urls')),
]
- La variable
handler404establece la vista que procesa errores 404.
Es muy probable que con la configuración anterior sigas sin poder ver la página 404 personalizada. Esto se debe a que en modo «debug» Django proporciona otro tipo de plantillas con mayor información para desarrollo.
Para poder probar este tipo de respuestas en desarrollo, debemos «simular» un entorno de producción de la siguiente manera:
- Necesitamos indicar que no estamos en depuración (desarrollo).
- Django obliga a permitir «hosts» de acceso cuando el modo depuración está deshabilitado: CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False.
Ahora sí que ya debería de funcionar la personalización del error 404. Para ello bastaría con acceder a una URL del estilo http://localhost:8000/posts/este-post-no-existe/
Manejadores de error¶
Django nos ofrece los siguientes manejadores de error para poder personalizar las respuestas:
| Error | Manejador |
|---|---|
| 400 | handler400 |
| 403 | handler403 |
| 404 | handler404 |
| 500 | handler500 |