Framework: Django Rest Framework#

Logo HTML5

Esta es la documentación que he recopilado para trabajar con Djang rest framework. Una extensión de django para crear API Rest.

Primeros Pasos#

Crear un proyecto Django#

  • Lo primero que vamos a hacer es crear un entorno virtual.

  • Luego instalamos Django pip install django

  • Creamos un projecto en Django django-admin startproject prueba_api

  • Ejecutamos el servidor para ver que se ha creado correctamente python manage.py runserver

Instalar y configurar Django Rest Framework#

Empezamos instalando las librerías necesarias:

  • Instalamos Django Rest Framework pip install djangorestframework

  • Instalamos Django-filter pip install django-filter

  • Y por último, instalamos Markdown: pip install markdown

Pasamos a configurar el proyecto:

  • Lo primero será añadir rest_framework a settings.py:

1INSTALLED_APPS = [
2    'django.contrib.admin',
3    'django.contrib.auth',
4    'django.contrib.contenttypes',
5    'django.contrib.sessions',
6    'django.contrib.messages',
7    'django.contrib.staticfiles',
8    'rest_framework'
9]
  • Ahora como mínimo necesitamos tener una app creada, para ello ejecutamos python manage.py startapp API

  • Añadimos la app a settings.py:

 1INSTALLED_APPS = [
 2    'django.contrib.admin',
 3    'django.contrib.auth',
 4    'django.contrib.contenttypes',
 5    'django.contrib.sessions',
 6    'django.contrib.messages',
 7    'django.contrib.staticfiles',
 8    'rest_framework',
 9    'API'
10]
  • Realizamos la migración de la base de datos python manage.py migrate

  • Y creamos un superusuario python manage.py createsuperuser

Nota

Una forma cómoda de trabajar con aplicaciones mixtas es crear un directorio API dentro de la app y ahí un nuevo views.py y serializers.py

Serializadores#

Los serializadores se encargan de trabajar con los modelos de datos para adaptarlos a la API.

  • Para comenzar tenemos que crear un modelo en nuestra API (API/models.py):

 1from django.db import models
 2
 3
 4class Consola(models.Model):
 5    """ Modelo de datos para videoconsolas """
 6
 7    marca = models.CharField(max_length=200)
 8    modelo = models.CharField(max_length=200)
 9    lanzamiento = models.IntegerField(blank=True, null=True)
10
11    def __str__(self):
12        return self.modelo
13
14
15class Videojuego(models.Model):
16    """ Modelo de datos para videojuegos """
17
18    titulo = models.CharField(max_length=200)
19    consola = models.ForeignKey(Consola, on_delete=models.CASCADE)
20
21    def __str__(self):
22        return self.modelo
  • Ejecutamos python manage.py makemigrations API para preparar las migraciones y migramos la tabla python manage.py migrate API

Serializador básico#

  • Vamos a crear un archivo para el serializador en la ruta (API/serializers/consolaSerializer.py):

 1# Importamos la librería de serializers:
 2from rest_framework import serializers
 3# Importamos el modelo de datos a usar:
 4from API.models import Consola
 5
 6# Creamos el serializador:
 7class ConsolaSerializer(serializers.ModelSerializer):
 8    # de forma similar a los formularios, es conveniente definir los campos del modelo:
 9    marca = serializers.CharField(required=True, max_length=200)
10    modelo = serializers.CharField(required=True, max_length=200)
11    lanzamiento = serializers.IntegerField(required=False)
12
13    class Meta:
14        # Elegimos el modelo:
15        model = Consola
16
17        # Aquí se definen los campos que va a manejar el serializador:
18        fields = [
19            'marca',
20            'modelo',
21            'lanzamiento'
22        ]
23
24    # el metodo validador recibirá los datos que se van a regsitrar y los comprobará antes de ingresarlos:
25    def validate(self, data):
26    # podemos crear validaciones específicas:
27    if data.get('lanzamiento') and data.get('lanzamiento')  < 1970:
28        raise serializers.ValidationError('El año de lanzamiento es demasiado bajo')

Con esto ya hemos preparado el primer serializador.

Importante

Recuerda crear un archivo vacío llamado __init__.py en cada carpeta que se generes para convertirlo en módulo.

Modificar salida de datos#

Se puede modificar la salida de datos a mostrar en el JSON:

 1from rest_framework import serializers
 2# importamos el modelo videojuego para listarlos:
 3from API.models import Consola, Videojuego
 4
 5class ConsolaSerializer(serializers.ModelSerializer):
 6    marca = serializers.CharField(required=True, max_length=200)
 7    modelo = serializers.CharField(required=True, max_length=200)
 8    lanzamiento = serializers.IntegerField(required=False)
 9
10    class Meta:
11        model = Consola
12
13        # Aquí se definen los campos que va a manejar el serializador:
14        fields = [
15            'id',
16            'marca',
17            'modelo',
18            'lanzamiento'
19        ]
20
21    def validate(self, data):
22        # podemos crear validaciones específicas:
23        if data.get('lanzamiento') and data.get('lanzamiento')  < 1970:
24            raise serializers.ValidationError('El año de lanzamiento es demasiado bajo')
25
26        return data
27
28    # Se puede personalizar la salida de datos con este método:
29    def to_representation(self, instance):
30        # podemos ir llamando cada uno de los títulos disponibles por su consola:
31        juegos = Videojuego.objects.filter(consola=instance.id)
32        return {
33            "id": instance.id,
34            "marca": instance.marca,
35            "modelo": instance.modelo,
36            "lanzamiento": instance.lanzamiento,
37            "juegos": [juego.titulo for juego in juegos] # tan solo hay que añadir el titulo de cada elemento del listado que hemos localizado
38        }

Modificar creación de datos#

Se puede modificar la creacíon de datos para ajustar su guardado:

 1from rest_framework import serializers
 2from API.models import Consola, Videojuego
 3
 4class ConsolaSerializer(serializers.ModelSerializer):
 5    marca = serializers.CharField(required=True, max_length=200)
 6    modelo = serializers.CharField(required=True, max_length=200)
 7    lanzamiento = serializers.IntegerField(required=False)
 8    # creamos un campo nuevo opcional para recibir un videojuego y crearlo si no existe:
 9    videojuego = serializers.CharField(required=False, max_length=200)
10
11    class Meta:
12        model = Consola
13
14        fields = [
15            'id',
16            'marca',
17            'modelo',
18            'lanzamiento',
19            'videojuego' # este hay que añadirlo a la lista de campos para que el validador lo verifique
20        ]
21
22    def validate(self, data):
23        if data.get('lanzamiento') and data.get('lanzamiento')  < 1970:
24            raise serializers.ValidationError('El año de lanzamiento es demasiado bajo')
25
26        return data
27
28    def to_representation(self, instance):
29        juegos = Videojuego.objects.filter(consola=instance.id)
30        return {
31            "id": instance.id,
32            "marca": instance.marca,
33            "modelo": instance.modelo,
34            "lanzamiento": instance.lanzamiento,
35            "juegos": [juego.titulo for juego in juegos]
36        }
37
38    # se usa el método create y en el se localiza si existe la consola y sino se procede a crear:
39    def create(self, validated_data):
40        # se crea la consola con los datos validados:
41        consola = Consola.objects.create(**validated_data)
42        # se crea el videojuego:
43        if validated_data.get('videojuego'):
44            Videojuego.objects.create(titulo=validated_data.get('videojuego'), consola_id=consola.id)
45
46        # se retorna la creación del objeto:
47        return consola

Atención

la información que se recibe a continuación ya ha sido validada, por lo tanto no se puede modificar aquello que no pase la validación.

Modificar actualización de datos#

Se puede modificar la creacíon de datos para ajustar su guardado:

 1from rest_framework import serializers
 2from API.models import Consola, Videojuego
 3
 4class ConsolaSerializer(serializers.ModelSerializer):
 5    marca = serializers.CharField(required=True, max_length=200)
 6    modelo = serializers.CharField(required=True, max_length=200)
 7    lanzamiento = serializers.IntegerField(required=False)
 8    videojuego = serializers.CharField(required=False, max_length=200)
 9
10    class Meta:
11        model = Consola
12
13        fields = [
14            'id',
15            'marca',
16            'modelo',
17            'lanzamiento',
18            'videojuego'
19        ]
20
21    def validate(self, data):
22        if data.get('lanzamiento') and data.get('lanzamiento')  < 1970:
23            raise serializers.ValidationError('El año de lanzamiento es demasiado bajo')
24
25        return data
26
27    def to_representation(self, instance):
28        juegos = Videojuego.objects.filter(consola=instance.id)
29        return {
30            "id": instance.id,
31            "marca": instance.marca,
32            "modelo": instance.modelo,
33            "lanzamiento": instance.lanzamiento,
34            "juegos": [juego.titulo for juego in juegos]
35        }
36
37    def create(self, validated_data):
38        consola = Consola.objects.create(**validated_data)
39        if validated_data.get('videojuego'):
40            Videojuego.objects.create(titulo=validated_data.get('videojuego'), consola_id=consola.id)
41
42        return consola
43
44
45    # se puede modificar el update para editar campos como queramos:
46    def update(self, instance, validated_data):
47
48        # se recuperan los campos validados que queremos modificar:
49        validated_data["marca"] = instance.marca # recuperamos del registro original la marca y evitamos que sea modificable.
50        validated_data["lanzamiento"] = 1990 # se puede establecer un valor fijo que no cambiará aunque enviemos un dato distinto.
51
52        # ahora se recorren los valores de ambos objetos y se combinan:
53        for key, value in validated_data.items():
54            setattr(instance,key,value)
55
56        # se guarda y retornan los cambios:
57        instance.save()
58        return instance
  • Al intentar actualizar el registro con los siguientes datos no permitirá el cambio de marca y el lanzamiento asignará siempre 1990:

1{
2    "marca": "Sammy",
3    "modelo": "Play Funstation",
4    "lanzamiento": 2015
5}

Atención

la información que se recibe a continuación ya ha sido validada, por lo tanto no se puede modificar aquello que no pase la validación.

ViewSets#

Los viewsets se implementan en las vistas de Django y sirven para mostrar los valores de la API o bien en su frontend o bien como un JSON.

GET (mostrar información)#

Para crear un ViewSet nos vamos a (API/views/consolaViewSet.py):

 1# importamos el apiview:
 2from rest_framework.views import APIView
 3# y el response:
 4from rest_framework.response import Response
 5# también es necesario traer el modelo:
 6from API.models import Consola
 7# se carga el serializador creado:
 8from API.serializers.consolaSerializer import ConsolaSerializer
 9
10# creamos la vista:
11class ConsolaApiView(APIView):
12
13    # con APIView se va definiendo que tipo de entrada permite la vista(funciona como una vista común en Django en lo referente a parametros):
14    def get(self, request):
15        # se carga los datos en una variable:
16        consolas = Consola.objects.all()
17        # se le envía al serializador, definimos si va a ser uno o varios resultados con many:
18        serializer = ConsolaSerializer(consolas, many=True)
19
20        # se muestra la respuesta:
21        return Response(serializer.data, status=status.HTTP_200_OK)

GET - Paginación y personalizar salida#

Se puede retornar un número determinado de elementos por página creando un paginador personalizado:

 1from rest_framework.views import APIView
 2from rest_framework.response import Response
 3from API.models import Consola
 4from API.serializers.consolaSerializer import ConsolaSerializer
 5# se cargan los siguientes módulos de paginador:
 6from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
 7
 8# creamos la vista:
 9class ConsolaApiView(APIView):
10    # se define un nuevo parámetro para recibir por url con el número de página:
11    def get(self, request, consola=None):
12
13        # Si queremos un solo resultado, usamos el parámetro consola por url y recuperamos solo un valor:
14        consola = Consola.objects.filter(id=consola).first()
15        if consola:
16            serializer = ConsolaSerializer(consola)
17            return Response(serializer.data, status=status.HTTP_200_OK)
18
19
20        consolas = Consola.objects.all()
21        serializer = ConsolaSerializer(consolas, many=True)
22        page = request.data.get('page')
23        # el paginador recibe la data del serializador y un número de páginas:
24        paginator = Paginator(serializer.data, 5)
25        # Se controla si se recibe o no página y el número de páginas:
26        try:
27            consolas_paginated = paginator.page(page)
28        except PageNotAnInteger:
29            consolas_paginated = paginator.page(1)
30        except EmptyPage:
31            consolas_paginated = paginator.page(paginator.num_pages)
32        # Se puede modificar la respuesta para retornar datos de la paginación:
33        return Response({
34            "cantidad_paginas": paginator.num_pages,
35            "pagina_actual": page,
36            "hay_siguiente": consolas_paginated.has_next(),
37            "pagina_siguiente": consolas_paginated.next_page_number() if consolas_paginated.has_next() else False,
38            "consolas": consolas_paginated.object_list
39        }, status=status.HTTP_200_OK)

POST (Crear registro)#

El post se define como un método nuevo para procesar la información:

 1from email.policy import HTTP
 2from rest_framework.views import APIView
 3from rest_framework.response import Response
 4from API.models import Consola
 5from API.serializers.consolaSerializer import ConsolaSerializer
 6from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
 7from rest_framework import status
 8
 9class ConsolaApiView(APIView):
10    def get(self, request, consola=None):
11
12        consola = Consola.objects.filter(id=consola).first()
13        if consola:
14            serializer = ConsolaSerializer(consola)
15            return Response(serializer.data, status=status.HTTP_200_OK)
16
17        consolas = Consola.objects.all()
18        serializer = ConsolaSerializer(consolas, many=True)
19
20        page = request.data.get("page")
21        paginator = Paginator(serializer.data, 5)
22
23        try:
24            consolas_paginated = paginator.page(page)
25        except PageNotAnInteger:
26            consolas_paginated = paginator.page(1)
27        except EmptyPage:
28            consolas_paginated = paginator.page(paginator.num_pages)
29        # Se puede modificar la respuesta para retornar datos de la paginación:
30        return Response({
31            "cantidad_paginas": paginator.num_pages,
32            "pagina_actial": page,
33            "hay_siguiente": consolas_paginated.has_next(),
34            "pagina_siguiente": consolas_paginated.next_page_number() if consolas_paginated.has_next() else False,
35            "consolas": consolas_paginated.object_list
36        }, status=status.HTTP_200_OK)
37
38    # se define el post en la api view:
39    def post(self, request):
40        # se carga la información de la petición post en el serializador:
41        serializer = ConsolaSerializer(data=request.data)
42        # se verifica si ha pasado la validación:
43        if serializer.is_valid():
44            # se guarda la información y se retorna el resultado:
45            serializer.save()
46            return Response(serializer.data, status=status.HTTP_201_CREATED)
47        # si no ha salido bien se retornan los errores:
48        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

DELETE - Eliminar elemento#

 1from email.policy import HTTP
 2from rest_framework.views import APIView
 3from rest_framework.response import Response
 4from API.models import Consola
 5from API.serializers.consolaSerializer import ConsolaSerializer
 6from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
 7from rest_framework import status
 8
 9class ConsolaApiView(APIView):
10    def get(self, request, consola=None):
11
12        consola = Consola.objects.filter(id=consola).first()
13        if consola:
14            serializer = ConsolaSerializer(consola)
15            return Response(serializer.data, status=status.HTTP_200_OK)
16
17        consolas = Consola.objects.all()
18        serializer = ConsolaSerializer(consolas, many=True)
19
20        page = request.data.get("page")
21        paginator = Paginator(serializer.data, 5)
22
23        try:
24            consolas_paginated = paginator.page(page)
25        except PageNotAnInteger:
26            consolas_paginated = paginator.page(1)
27        except EmptyPage:
28            consolas_paginated = paginator.page(paginator.num_pages)
29
30        return Response({
31            "cantidad_paginas": paginator.num_pages,
32            "pagina_actial": page,
33            "hay_siguiente": consolas_paginated.has_next(),
34            "pagina_siguiente": consolas_paginated.next_page_number() if consolas_paginated.has_next() else False,
35            "consolas": consolas_paginated.object_list
36        }, status=status.HTTP_200_OK)
37
38    def post(self, request):
39        serializer = ConsolaSerializer(data=request.data)
40        if serializer.is_valid():
41            serializer.save()
42            return Response(serializer.data, status=status.HTTP_201_CREATED)
43        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
44
45    # Eliminar un registro:
46    def delete(self, request, consola):
47        # se busca el valor:
48        consola = Consola.objects.filter(id=consola).first()
49        # si existe se borra:
50        if consola:
51            consola.delete()
52            return Response("Consola eliminada", status=status.HTTP_204_NO_CONTENT)
53        return Response("No existe consola", status=status.HTTP_400_BAD_REQUEST)

PUT - Actualizar registros#

 1from email.policy import HTTP
 2from rest_framework.views import APIView
 3from rest_framework.response import Response
 4from API.models import Consola
 5from API.serializers.consolaSerializer import ConsolaSerializer
 6from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
 7from rest_framework import status
 8
 9class ConsolaApiView(APIView):
10    def get(self, request, consola=None):
11        consola = Consola.objects.filter(id=consola).first()
12        if consola:
13            serializer = ConsolaSerializer(consola)
14            return Response(serializer.data, status=status.HTTP_200_OK)
15
16        consolas = Consola.objects.all()
17        serializer = ConsolaSerializer(consolas, many=True)
18
19        page = request.data.get("page")
20        paginator = Paginator(serializer.data, 5)
21
22        try:
23            consolas_paginated = paginator.page(page)
24        except PageNotAnInteger:
25            consolas_paginated = paginator.page(1)
26        except EmptyPage:
27            consolas_paginated = paginator.page(paginator.num_pages)
28
29        return Response({
30            "cantidad_paginas": paginator.num_pages,
31            "pagina_actial": page,
32            "hay_siguiente": consolas_paginated.has_next(),
33            "pagina_siguiente": consolas_paginated.next_page_number() if consolas_paginated.has_next() else False,
34            "consolas": consolas_paginated.object_list
35        }, status=status.HTTP_200_OK)
36
37    def post(self, request):
38        serializer = ConsolaSerializer(data=request.data)
39        if serializer.is_valid():
40            serializer.save()
41            return Response(serializer.data, status=status.HTTP_201_CREATED)
42        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
43
44
45    def delete(self, request, consola):
46        consola = Consola.objects.filter(id=consola).first()
47        if consola:
48            consola.delete()
49            return Response("Consola eliminada", status=status.HTTP_204_NO_CONTENT)
50        return Response("No existe consola", status=status.HTTP_400_BAD_REQUEST)
51
52
53    # actualizamos registros con put:
54    def put(self, request, consola):
55        # se recupera el registro a editar:
56        consola = Consola.objects.get(id=consola)
57
58        # se envía el objeto y la data recibida por put para hacer los cambios:
59        serializer = ConsolaSerializer(consola, data=request.data, partial=True)  # la opción partial indica que no es necesario enviar todo
60
61        # se comprueba que todo es correcto:
62        if serializer.is_valid():
63            serializer.save()
64            return Response(serializer.data, status=status.HTTP_200_OK)
65        return Response("Hay errores en la actualización", status=status.HTTP_400_BAD_REQUEST)

Con esto ya tenemos listo el ViewSet.

Nota

Si entramos a la ruta http://localhost:8000/api/consolas veremos una interfaz con el resultado, pero lo más recomendable es trabajar con software como Postman o Insomnia

Rutas de la API#

  • Archivo principal de rutas urls.py:

1from django.contrib import admin
2from django.urls import path, include
3
4urlpatterns = [
5    path('admin/', admin.site.urls),
6    path('api/', include('API.urls'))
7]
  • Archivo de rutas adicional: (API/urls.py):

1from django.urls import path
2from API.views.consolaViewset import ConsolaApiView
3
4urlpatterns = [
5    path('consolas', ConsolaApiView.as_view(), name='consolas'),
6    # agregamos la ruta para una consola individual:
7    path('consolas/<int:consola>', ConsolaApiView.as_view(), name='consola')
8]

Ahora podemos ejecutar la API en http://localhost:8080/api/consolas o http://localhost:8080/api/consolas/1 y ver como podemos añadir registros.

Atención

Con el nivel actual de permisos cualquiera puede introducir valores en la API. Para cambiar eso tenemos que ir al apartado de permisos

Permisos#

Tenemos varios tipos de permisos para gestionar nuestra API. Para establecer permisos creamos una lista al final de (prueba_api/settings.py):

  • Por defecto nuestra API estará disponible para lectura y escritura ante cualquier extraño.

Permisos Globales (settings.py)#

Se pueden establecer permisos globales en toda la aplicación editando settings.py:

  • Establecer permisos a solo lectura:

1REST_FRAMEWORK = {
2    'DEFAULT_PERMISSION_CLASSES': [
3        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
4    ],
5}
  • Añadir acceso por login para poder editar y ver datos.

1REST_FRAMEWORK = {
2    'DEFAULT_PERMISSION_CLASSES': [
3        'rest_framework.permissions.IsAuthenticated',
4    ],
5}
  • Login requerido para editar y visualización sin login:

1REST_FRAMEWORK = {
2    'DEFAULT_PERMISSION_CLASSES': [
3        'rest_framework.permissions.IsAuthenticatedOrReadOnly',
4    ],
5}

Permisos individuales#

En los ViewSets se pueden definir permisos para cada vista usando la variable permission_classes:

1# Se importan los permisos que vayamos a establecer como IsAuthenticatedOrReadOnly, IsAuthenticate, o IsAdminUser:
2from rest_framework.permissions import IsAuthenticatedOrReadOnly
3
4class ConsolaApiView(APIView):
5    # se asigna al atributo siguiente el tipo de permiso de esta APIView:
6    permission_classes = [IsAuthenticatedOrReadOnly]
7
8    def get(self, request, consola=None):
9    ...

Permisos personalizados#

Se puede crear un nuevo permiso y utilizarlo tanto como permiso global como para ViewSets individuales:

  • En la aplicación deseada se crea un archivo permissions.py:

 1# se creará un permiso para que acepte solo un usuario:
 2# se importa el permiso base:
 3from rest_framework.permissions import BasePermission
 4
 5# se crea la clase del permiso:
 6class SoloMeOrReadOnly(BasePermission):
 7
 8    # tendrá una función que comprueba si soy yo el que inicia sesión o no:
 9    def has_permission(self, request, view):
10        if request.method == 'GET':
11            return True
12        else:
13            return bool(request.user.is_authenticated and request.user.username == 'guillermo')
14
15
16    # Este segundo método se invoca para establecer los permisos en un objeto (un articulo o elemento simple):
17    def has_object_permission(self, request, view, obj):
18        if request.method == 'GET':
19            return True
20        else:
21            return bool(request.user.is_authenticated and request.user.username == 'guillermo')
  • En el ViewSet se invoca y se aplica como un permiso cualquiera:

1# se importa el permiso creado:
2from API.permissions import SoloMeOrReadOnly
3
4class ConsolaApiView(APIView):
5    # se agrega el permiso como cualquier estandar:
6    permission_classes = [SoloMeOrReadOnly]
7
8    def get(self, request, consola=None):
9    ...

Nota

El método has_object_permission que se ocupa de los permisos de un elemento en la tabla no es obligatorio si vamos a tener los mismos permisos como en el ejemplo anterior.

Crear documentación automática#

Es muy interesante crear un sistema de documentación automática en nuestra api rest.

Para ello se hace lo siguiente:

  1. Instalar coreapi: pip install coreapi

  2. Crear la ruta de la documentación en API/urls.py:

 1from django.urls import path
 2from API.views.consolaViewset import ConsolaApiView
 3# cargamos el modulo de rutas de rest_framework:
 4from rest_framework.documentation import include_docs_urls
 5
 6urlpatterns = [
 7    path('consolas', ConsolaApiView.as_view(), name='consolas'),
 8    # agregamos la ruta para una consola individual:
 9    path('consolas/<int:consola>', ConsolaApiView.as_view(), name='consola'),
10        path('docs/', include_docs_urls(title='Nombre API', public=False))
11]
  1. Se añade a la constante REST_FRAMEWORK el siguiente valor en settings.py:

1REST_FRAMEWORK = {'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema' }

Autenticación con JWT#

  1. Instalar jwt: pip install djangorestframework-simplejwt

  2. Se añade a INSTALLED_APPS la aplicación simplejwt: 'rest_framework_simplejwt'

  3. Se añaden a la constante REST_FRAMEWORK los siguientes valores en settings.py:

 1REST_FRAMEWORK = {
 2    'DEFAULT_PERMISSION_CLASSES': [
 3        'rest_framework.permissions.IsAuthenticated',
 4    ],
 5    'DEFAULT_AUTHENTICATION_CLASSES': ( # Este apartado define los metodos de autenticación
 6        'rest_framework_simplejwt.authentication.JWTAuthentication', # este es el método jwt que vamos a usar
 7        'rest_framework.authentication.SessionAuthentication', # este es el método por sesión
 8        'rest_framework.authentication.BasicAuthentication', # y este es el método básico de usuario y contraseña
 9    ),
10
11}
  1. Toca añadir las rutas para obtener el token de autenticación en API/urls.py:

 1from django.urls import path
 2from API.views.consolaViewset import ConsolaApiView
 3from rest_framework.documentation import include_docs_urls
 4# Se importan los metodos para obtener y refrescar el token:
 5from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
 6
 7urlpatterns = [
 8    path('consolas', ConsolaApiView.as_view(), name='consolas'),
 9    path('consolas/<int:consola>', ConsolaApiView.as_view(), name='consola'),
10        path('docs/', include_docs_urls(title='Nombre API', public=False)),
11    # Añadimos la ruta para obtener el token y para refrescarlo:
12    path('token', TokenObtainPairView.as_view(), name='token_obtain_pair'),
13    path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh')
14]

Obtener un Token#

Para obtener un Token se hace lo siguiente: 1. Abrir Postman o Insomnia (u otro cliente API). 2. Ejecutar una petición POST a la ruta http://127.0.0.1:8000/api/token con usuario y contraseña:

1    {
2        "username":"pepillo",
3        "password":"maizfrito"
4    }
  1. Esto nos devolverá un token por ejemplo:

1{
2    "refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY0NjA1MzkxNCwiaWF0IjoxNjQ1OTY3NTE0LCJqdGkiOiJmOGVhOWUzNTdhMGU0ZWU4ODU0Y2NiNWE2NTdjOGY1ZiIsInVzZXJfaWQiOjN9.9DvZVyzfZcmB-v9P_mgETFighXz2KjChPc_EslH5X3M",
3    "access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjQ1OTY3ODE0LCJpYXQiOjE2NDU5Njc1MTQsImp0aSI6IjBhNmEyNWM2YjVlMDQ4ZjQ5MzQxYzM2MGNlODM2OTdiIiwidXNlcl9pZCI6M30.He7w5XxjCrgeWupFOnGdVH4EusJ5fRZMbY3zkyZetCI"
4}
  1. Ahora este token «access» se utilizará para todas las operaciones contra la API que requieran autenticación. Solo hay que elegir en la pestaña Auth de Postman la opción Bearer e ingresar el token en cada consulta.

Haciendo una petición contra la API con JWT#

Ejemplo de uso con Python.

La petición se podría dividir en dos partes:

  1. Solicitud de token:

 1import requests
 2
 3headers = {
 4    'Content-Type': 'application/json',
 5    'Accept': '*/*',
 6}
 7
 8data = '{"username":"misterg@gmail.com", "password":"maizfrito"}'
 9
10r = requests.post('http://127.0.0.1:8000/api/token', headers=headers, data=data)
11print(r.status_code)
12token = r.json()
  1. Petición de datos (listado de series):

1headers['Authorization'] = 'Bearer ' + token.get('access')
2
3r = requests.get('http://127.0.0.1:8000/api/consolas', headers=headers)
4print(r.status_code)
5print(r.content)