lunes, 1 de abril de 2024

31.- Construyendo un API (django-rest-framework)

Construyendo una API

En el capítulo anterior, construimos un sistema para el registro de estudiantes y la inscripción en los diferentes cursos. Creaste vistas para mostrar el contenido de los cursos y aprendiste a usar el marco de caché de Django.

En este capítulo, crearemos una API RESTful para nuestra plataforma de aprendizaje electrónico. Una API te permite construir un núcleo común que puede ser utilizado en múltiples plataformas como sitios web, aplicaciones móviles, complementos, y más. Por ejemplo, puedes crear una API para ser utilizada  por una aplicación móvil para tu plataforma de aprendizaje electrónico. Si proporcionas una API a terceros, podrán consumir información y operar con tu aplicación de forma programática. Una API permite a los desarrolladores automatizar acciones en tu plataforma e integrar tu servicio con otras aplicaciones o servicios en línea. Construiremos una API completamente funcional para la plataforma de aprendizaje electrónico.

En este post, realizaremos lo siguiente:

• Instalar Django REST framework

• Crear serializadores para tus modelos

• Construir una API RESTful

• Crear serializadores anidados

• Construir vistas de API personalizadas

• Manejar autenticación de API

• Agregar permisos a vistas de API

• Crear un permiso personalizado

• Implementar ViewSets y enrutadores

• Usar la biblioteca Requests para consumir la API

Comencemos con la configuración de tu API.


Construyendo una API RESTful


Al construir una API, hay varias formas en las que puedes estructurar sus puntos finales y acciones, pero se recomienda seguir los principios REST. La arquitectura REST proviene de Transferencia de Estado Representacional. Las API RESTful están basadas en recursos; tus modelos representan recursos y los métodos HTTP como GET, POST, PUT o DELETE se utilizan para recuperar, crear, actualizar o eliminar objetos. Los códigos de respuesta HTTP también se utilizan en este contexto. Se devuelven diferentes códigos de respuesta HTTP para indicar el resultado de la solicitud HTTP, por ejemplo, códigos de respuesta 2XX para éxito, 4XX para errores, y así sucesivamente.

Los formatos más comunes para intercambiar datos en API RESTful son JSON y XML. Construiremos una API RESTful con serialización JSON para nuestro proyecto. Tu API proporcionará las siguientes funcionalidades:

• Recuperar materias

• Recuperar cursos disponibles

• Recuperar contenido del curso

• Inscribirse en un curso

Puedes construir una API desde cero con Django creando vistas personalizadas. Sin embargo, hay varios módulos de terceros que simplifican la creación de una API para tu proyecto; el más popular entre ellos es Django REST framework.


Instalando Django REST framework


Django REST framework te permite construir fácilmente APIs RESTful para tu proyecto. Puedes encontrar toda la información sobre el framework REST en https://www.django-rest-framework.org/.

Abre la terminal e instala el framework con el siguiente comando:

pip install djangorestframework

Edita el archivo settings.py del proyecto educa y agrega rest_framework al ajuste INSTALLED_APPS para activar la aplicación, de la siguiente manera:

INSTALLED_APPS = [

# ...

'rest_framework',

]

Luego, agrega el siguiente código al archivo settings.py:


REST_FRAMEWORK = {

'DEFAULT_PERMISSION_CLASSES': [

'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'

]

}

Puedes proporcionar una configuración específica para tu API utilizando el ajuste REST_FRAMEWORK. El framework REST ofrece una amplia gama de ajustes para configurar comportamientos predeterminados. El ajuste DEFAULT_PERMISSION_CLASSES especifica los permisos predeterminados para leer, crear, actualizar o eliminar objetos. Has establecido DjangoModelPermissionsOrAnonReadOnly como la única clase de permiso predeterminada. Esta clase se basa en el sistema de permisos de Django para permitir a los usuarios crear, actualizar o eliminar objetos mientras proporciona acceso de solo lectura para usuarios anónimos. Aprenderás más sobre los permisos más adelante, en la sección Agregar permisos a las vistas.

Para obtener una lista completa de los ajustes disponibles para el framework REST, puedes visitar https://www.django-restframework.org/api-guide/settings/.


Definición de serializadores


Después de configurar el framework REST, necesitas especificar cómo se serializarán tus datos. Los datos de salida deben serializarse en un formato específico, y los datos de entrada se deserializarán para su procesamiento. El framework proporciona las siguientes clases para construir serializadores para objetos individuales:

Serializer: Proporciona serialización para instancias de clases normales de Python

ModelSerializer: Proporciona serialización para instancias de modelos

HyperlinkedModelSerializer: Lo mismo que ModelSerializer, pero representa relaciones entre objetos con enlaces en lugar de claves primarias

Construyamos nuestro primer serializador. Crea la siguiente estructura de archivos dentro del directorio de la aplicación de courses:

api/

    __init__.py

    serializers.py

Construirás toda la funcionalidad de la API dentro del directorio api para mantener todo bien organizado. Edita el archivo serializers.py y agrega el siguiente código:

from rest_framework import serializers
from courses.models import Subject

class
SubjectSerializer(serializers.ModelSerializer):
    class Meta:
        model = Subject
        fields = ['id', 'title', 'slug']


Este es el serializador para el modelo Subject. Los serializadores se definen de manera similar a las clases Form y ModelForm de Django. La clase Meta te permite especificar el modelo a serializar y los campos que se incluirán para la serialización. Todos los campos del modelo se incluirán si no estableces un atributo fields.

Vamos a probar el serializador. Abre la línea de comandos y comienza el shell de Django con el siguiente comando:

python manage.py shell

Ejecuta el siguiente código:

>>> from courses.models import Subject
>>> from courses.api.serializers import SubjectSerializer
>>> subject = Subject.objects.latest('id')
>>> serializer = SubjectSerializer(subject)
>>> serializer.data

{'id': 4, 'title': 'Programación', 'slug': 'programacion'}

Es este ejemplo, conseguimos un objeto Subject, creamos una instancia del objeto SubjectSerializer, y acedemos a los datos serializados. Puedes observar que los datos del modelo se traducen en tipos de datos nativos de Python.


Entendiendo los parsers y renderizadores

Los datos serializados tienen que ser renderizados en un formato específico antes de devolverlos en una respuesta HTTP. De igual manera, cuando recibes una solicitud HTTP, debes analizar los datos entrantes y deserializarlos antes de poder operar con ellos. El framework REST incluye renderizadores y parsers para manejar eso.

Veamos cómo analizar los datos entrantes. Ejecuta el siguiente código en el shell de Python:

>>> from io import BytesIO
>>> from rest_framework.parsers import JSONParser
>>> data = b'{"id":4,"title":"Programming","slug":"programming"}'
>>> parsed_dataJSONParser().parse(BytesIO(data))
>>> print(parsed_data)

{'id': 4, 'title': 'Programming', 'slug': 'programming'}

Dado un input de cadena JSON, puedes utilizar la clase JSONParser proporcionada por el framework REST para convertirla en un objeto Python.

El framework REST también incluye clases de renderizadores que te permiten formatear las respuestas de la API. El framework determina qué renderizador utilizar a través de la negociación de contenido, inspeccionando el encabezado Accept de la solicitud para determinar el tipo de contenido esperado para la respuesta. Opcionalmente, el renderizador se determina por el sufijo de formato de la URL. Por ejemplo, la URL http://127.0.0.1:8000/api/data.json podría ser un punto final que activa el JSONRenderer para devolver una respuesta JSON.

Vuelve al shell y ejecuta el siguiente código para renderizar el objeto serializador del ejemplo anterior:

>>> from rest_framework.renderers import JSONRenderer
>>> JSONRenderer().render(parsed_data)

Verás la siguiente salida:

b'{"id":4,"title":"Programming","slug":"programming"}'

Utilizas JSONRenderer para renderizar los datos serializados en formato JSON. Por defecto, el framework REST utiliza dos renderizadores diferentes: JSONRenderer y BrowsableAPIRenderer. Este último proporciona una interfaz web para navegar fácilmente por tu API. Puedes cambiar las clases de renderizador predeterminadas con la opción DEFAULT_RENDERER_CLASSES del ajuste REST_FRAMEWORK.

Puedes encontrar más información sobre renderizadores y parsers en los siguientes enlaces:


- Renderizadores: [https://www.django-rest-framework.org/api-guide/renderers/](https://www.django-rest-framework.org/api-guide/renderers/)

- Parsers: [https://www.django-rest-framework.org/api-guide/parsers/](https://www.django-rest-framework.org/api-guide/parsers/)


A continuación, veremos cómo construir vistas de API y utilizar serializadores en las vistas.


Construyendo vistas de lista y detalle


El framework REST viene con un conjunto de vistas genéricas y mixins que puedes utilizar para construir tus vistas de API. Proporcionan la funcionalidad para recuperar, crear, actualizar o eliminar objetos del modelo. Puedes ver todos los mixins y vistas genéricas proporcionadas por el framework REST en [https://www.django-rest-framework.org/api-guide/generic-views/](https://www.django-rest-framework.org/api-guide/generic-views/).

Vamos a crear vistas de lista y detalle para recuperar objetos de Subject. Crea un nuevo archivo dentro del directorio courses/api/ y nómbralo views.py. Agrega el siguiente código a él:

Elearning/educa/courses/api/views.py

from rest_framework import generics
from courses.models import Subject
from courses.api.serializers import SubjectSerializer

class SubjectListView(generics.ListAPIView):
    queryset = Subject.objects.all()
    serializer_class = SubjectSerializer

class SubjectDetailView(generics.RetrieveAPIView):
    queryset = Subject.objects.all()
    serializer_class = SubjectSerializer

En este código, estás utilizando las vistas genéricas ListAPIView y RetrieveAPIView del framework REST. Incluyes un parámetro de URL pk para la vista de detalle para recuperar el objeto para la clave primaria dada. Ambas vistas tienen los siguientes atributos:

- queryset: El QuerySet base que se utilizará para recuperar objetos.

- serializer_class: La clase para serializar objetos.

Ahora agreguemos patrones de URL para tus vistas. Crea un nuevo archivo dentro del directorio courses/api/, nómbralo urls.py y déjalo de la siguiente manera:

Elearning/educa/courses/api/urls.py

from django.urls import path
from . import views

app_name = 'courses'

urlpatterns = [
    path('subjects/',
         views.SubjectListView.as_view(),
         name='subject_list'),
    path('subjects/<pk>/',
         views.SubjectDetailView.as_view(),
         name='subject_detail'),
         ]

Edita el archivo principal urls.py del proyecto educa e incluya los patrones de la API de la siguiente manera:

urlpatterns = [
    # ...
    path('api/', include('courses.api.urls', namespace='api')),
]

Con esto, nuestra API está lista para ser utilizada.


Utilizando el API.

Usa el espacio de nombres 'api' para las URL de tu API. Asegúrate de que tu servidor esté en funcionamiento con el siguiente comando:

python manage.py runserver

y que tienes en ejecución el docker del cache que hayas utilizado previamente, en el post previo.

Vamos a usar curl para utilizar la API. curl es una herramienta de línea de comandos que te permite transferir datos hacia y desde un servidor. Si estás usando Linux, macOS o Windows 10/11, es muy probable que curl esté incluido en tu sistema. Sin embargo, también puedes descargar curl desde https://curl.se/download.html.

Abre el terminal y recupera la URL http://127.0.0.1:8000/api/subjects/ con curl, de la siguiente manera:

curl http://127.0.0.1:8000/api/subjects/

Recibirás una respuesta similar a la siguiente:

[{"id":3,"title":"Física","slug":"fisica"},{"id":1,"title":"Matemáticas","slug":"matematicas"},{"id":2,"title":"Música","slug":"musica"},{"id":4,"title":"Programación","slug":"programacion"}]

Para obtener una respuesta JSON más legible y bien indentada, puedes usar curl con la utilidad json_pp, de la siguiente manera:

curl http://127.0.0.1:8000/api/subjects/ | json_pp

% Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 195 100 195 0 0 3058 0 --:--:-- --:--:-- --:--:-- 3421 [ { "id" : 3, "slug" : "fisica", "title" : "Física" }, { "id" : 1, "slug" : "matematicas", "title" : "Matemáticas" }, { "id" : 2, "slug" : "musica", "title" : "Música" }, { "id" : 4, "slug" : "programacion", "title" : "Programación" } ]

La respuesta HTTP contiene una lista de objetos Subject en formato JSON.

En lugar de curl, también puedes usar cualquier otra herramienta para enviar solicitudes HTTP personalizadas, incluida una extensión del navegador como Postman, que puedes obtener en https://www.getpostman.com/.

Abre http://127.0.0.1:8000/api/subjects/ en tu navegador. Verás la API navegadora del marco REST de la siguiente manera:


API

Esta interfaz HTML es proporcionada por el renderizador BrowsableAPIRenderer. Muestra los encabezados y el contenido del resultado, y te permite realizar solicitudes. También puedes acceder a la vista detallada de la API para un objeto Subject incluyendo su ID en la URL.

Abre http://127.0.0.1:8000/api/subjects/1/ en tu navegador. Verás un único objeto Subject renderizado en formato JSON.


API en formato json


Crear serializadores anidados

Vamos a crear un serializador para el modelo Course. Edita el archivo api/serializers.py de la aplicación courses y agrega el siguiente código:

from courses.models import Subject, Course

class
CourseSerializer(serializers.ModelSerializer):
    class Meta: model = Course fields = ['id', 'subject', 'title', 'slug',                                                 'overview', 'created', 'owner',
                                         'modules']

Veamos cómo se serializa un objeto Course. Abre la terminal y ejecuta el siguiente comando:

python manage.py shell

Ejecuta el siguiente código:

>>> from rest_framework.renderers import JSONRenderer
>>> from courses.models import Course
>>> from courses.api.serializers import CourseSerializer
>>> course = Course.objects.latest('id')
>>> serializer = CourseSerializer(course)
>>> JSONRenderer().render(serializer.data)

b'{"id":5,"subject":1,"title":"Matem\xc3\xa1ticas Discretas.",
"slug":"matematicas-discretas",
"overview":"curso de matem\xc3\xa1ticas discretas aplicadas a la inform\xc3\xa1tica."
,"created":"2024-03-07T20:37:12.985403+01:00","owner":2,"modules":[5,6]}'

Obtendremos un objeto JSON con los campos que incluimos en CourseSerializerPuedes ver que los objetos relacionados del gestor de módulos están serializados como una lista de claves primarias, de la siguiente manera:

"modules": [6, 7, 9, 10] Quieres incluir más información sobre cada módulo, así que necesitas serializar los objetos de módulo y anidarlos. Modifica el código anterior del archivo api/serializers.py de la aplicación de cursos para que se vea de la siguiente manera:

Elearning/educa/courses/api/serializers.py

from rest_framework import serializers
from courses.models import Subject, Course, Module


class SubjectSerializer(serializers.ModelSerializer):
    class Meta:
        model = Subject
        fields = ['id', 'title', 'slug']

class ModuleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Module
        fields = ['order', 'title', 'description']


class CourseSerializer(serializers.ModelSerializer):
    modules = ModuleSerializer(many=True, read_only=True)
    
    class Meta:
        model = Course
        fields = ['id', 'subject', 'title', 'slug',
                  'overview', 'created', 'owner',
                  'modules']
En el nuevo código, defines ModuleSerializer para proporcionar la serialización para el modelo Module. Luego, añades un atributo modules a CourseSerializer para anidar el serializador ModuleSerializer. Estableces many=True para indicar que estás serializando múltiples objetos. El parámetro read_only indica que este campo es de solo lectura y no debe incluirse en ninguna entrada para crear o actualizar objetos.

Abre el Shell de nuevo y crea una instancia de CourseSerializer. Muestra los datos serializados con JSONRenderer. En esta ocasión, los modulos listados han sido serializados con ModuleSerializer, tal como se muestra en la siguiente imagen:

 b'{"id":5,"subject":1,"title":"Matem\xc3\xa1ticas Discretas.",
"slug":"matematicas-discretas","overview":"curso de matem\xc3\xa1ticas discretas 
aplicadas a la inform\xc3\xa1tica.","created":"2024-03-07T20:37:12.985403+01:00",
"owner":2,
"modules":[
    {
        "order":0,
        "title":"Teoria de la informaci\xc3\xb3n",
        "description":"cuantificaci\xc3\xb3n de la informaci\xc3\xb3n"
    },
    {
        "order":1,
        "title":"L\xc3\xb3gica",
        "description":"L\xc3\xb3gica matem\xc3\xa1tica"}
]}'
Puedes encontrar más información sobre serializadores en https://www.django-rest-framework.org/api-guide/serializers/.

Las vistas genéricas de API son muy útiles para construir APIs REST basadas en tus modelos y serializadores. Sin embargo, también es posible que necesites implementar tus propias vistas con lógica personalizada. Aprendamos cómo crear una vista de API personalizada.

Construyendo vistas de API personalizadas


El framework REST proporciona una clase APIView que construye la funcionalidad de la API sobre la clase View de Django. La clase APIView difiere de View al utilizar objetos de Request y Response personalizados del framework REST y manejar excepciones de APIException para devolver las respuestas HTTP apropiadas. También cuenta con un sistema de autenticación y autorización integrado para gestionar el acceso a las vistas.

Vas a crear una vista para que los usuarios se inscriban en los cursos usando la API. Edita el archivo api/views.py de la aplicación de cursos y agrega el siguiente código resaltado en azul:

Elearning/educa/courses/api/serializers.py

from django.shortcuts import get_object_or_404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import generics
from courses.models import Subject, Course
from courses.api.serializers import SubjectSerializer

#...
class CourseEnrollView(APIView):
    def post(self, request, pk, format=None):
        course = get_object_or_404(Course, pk=pk)
        course.students.add(request.user)
        return Response({'enrolled': True})

La vista CourseEnrollView maneja la inscripción de los usuarios en los cursos. El código precedente es el siguiente:

  1. 1.- Creas una vista personalizada que hereda de APIView.
  2. 2.- Defines un método post() para acciones POST. No se permitirá ningún otro método HTTP para esta vista.
  3. 3.- Esperas un parámetro de URL pk que contiene el ID de un curso. Recuperas el curso mediante el parámetro pk dado y lanzas una excepción 404 si no se encuentra.
  4. 4.- Añades el usuario actual a la relación muchos a muchos de estudiantes del objeto Curso y devuelves una respuesta exitosa.
  5. Edita el archivo api/urls.py y añade el siguiente patrón de URL para la vista CourseEnrollView:
path('courses/<pk>/enroll/', views.CourseEnrollView.as_view(), name='course_enroll'),

Teóricamente, ahora podrías realizar una solicitud POST para inscribir al usuario actual en un curso. Sin embargo, necesitas ser capaz de identificar al usuario y prevenir que los usuarios no autenticados accedan a esta vista. Veamos cómo funcionan la autenticación y los permisos en la API.


Manejando la Autenticación.


El framework REST proporciona clases de autenticación para identificar al usuario que realiza la solicitud. Si la autenticación es exitosa, el framework establece el objeto Usuario autenticado en request.user. Si no hay ningún usuario autenticado, se establece una instancia de AnonymousUser de Django en su lugar. El framework REST proporciona los siguientes backends de autenticación:

- BasicAuthentication: Esta es la autenticación básica de HTTP. El usuario y la contraseña son enviados por el cliente en el encabezado HTTP Authorization codificado con Base64. Puedes obtener más información al respecto en https://en.wikipedia.org/wiki/Basic_access_authentication.

- TokenAuthentication: Esta es una autenticación basada en tokens. Se utiliza un modelo de Token para almacenar tokens de usuario. Los usuarios incluyen el token en el encabezado HTTP Authorization para la autenticación.

- SessionAuthentication: Esto utiliza el backend de sesión de Django para la autenticación. Este backend es útil para realizar solicitudes AJAX autenticadas a la API desde el frontend de tu sitio web.

- RemoteUserAuthentication: Esto te permite delegar la autenticación a tu servidor web, que establece una variable de entorno REMOTE_USER.

Puedes construir un backend de autenticación personalizado subclaseando la clase BaseAuthentication proporcionada por el framework REST y sobrescribiendo el método authenticate().

Puedes establecer la autenticación por vista, o configurarla globalmente con el ajuste DEFAULT_AUTHENTICATION_CLASSES.

La autenticación solo identifica al usuario que realiza la solicitud. No permitirá ni denegará el acceso a las vistas. Debes usar permisos para restringir el acceso a las vistas.

Puedes encontrar toda la información sobre autenticación en https://www.django-rest-framework.org/api-guide/authentication/.

Para agregar BasicAuthentication a tu vista, edita el archivo api/views.py de la aplicación de cursos y añade un atributo authentication_classes a CourseEnrollView, de la siguiente manera:

Elearning/educa/courses/api/views.py

# ...
from rest_framework.authentication import BasicAuthentication

class CourseEnrollView(APIView):
    authentication_classes = [BasicAuthentication]
    # ...

Los usuarios serán identificados por las credenciales establecidas en el encabezado de Autorización (Authorization header) de la solicitud HTTP.

Agregando permisos a las vistas

REST framework incluye un sistema de permisos para restringir el acceso a las vistas. Algunos de los permisos integrados en REST framework son: - AllowAny: Acceso sin restricciones, independientemente de si un usuario está autenticado o no. - IsAuthenticated: Permite el acceso solo a usuarios autenticados. - IsAuthenticatedOrReadOnly: Acceso completo para usuarios autenticados. Los usuarios anónimos solo pueden ejecutar métodos de lectura como GET, HEAD o OPTIONS. - DjangoModelPermissions: Permisos vinculados a django.contrib.auth. La vista requiere un atributo queryset. Solo los usuarios autenticados con permisos de modelo asignados tienen permiso. - DjangoObjectPermissions: Permisos de Django basados en objetos específicos. Si a los usuarios se les niega el permiso, generalmente recibirán uno de los siguientes códigos de error HTTP: - HTTP 401: No autorizado - HTTP 403: Permiso denegado Puedes encontrar más información sobre permisos en https://www.django-rest-framework.org/api-guide/permissions/.

Ahora edita el archivo api/views.py de la aplicación courses y añade el atributo permission_classes a CourseEnrollview, de la siguiente forma:

Elearning/educa/courses/api/views.py

# ...
from rest_framework.authentication import BasicAuthentication
from rest_framework.permissions import IsAuthenticated

class CourseEnrollView(APIView):
    authentication_classes = [BasicAuthentication]
    permission_classes = [IsAuthenticated]
    # ...

Has incluido el permiso IsAuthenticated. Esto evitará que los usuarios anónimos accedan a la vista. Ahora puedes realizar una solicitud POST a tu nuevo método de API.

Asegúrate de que el servidor de desarrollo esté en ejecución. Abre la terminal y ejecuta el siguiente comando:

curl -i -X POST http://127.0.0.1:8000/api/courses/1/enroll/

Obtendrás la siguiente respuesta:

HTTP/1.1 401 Unauthorized ... {"detail": "Authentication credentials were not provided."}

Has obtenido un código HTTP 401 como se esperaba ya que no estás autenticado. Vamos a utilizar la autenticación básica con uno de tus usuarios. Ejecuta el siguiente comando, reemplazando student:password con las credenciales de un usuario existentey con la id de un curso que exista:

curl -i -X POST -u student:password http://127.0.0.1:8000/api/courses/1/enroll/

Obtendrás la siguiente respuesta:

HTTP/1.1 200 OK ... {"enrolled": true}

Puedes acceder al sitio de administración y verificar que el usuario ahora está inscrito en el curso.

A continuación, aprenderás una forma diferente de construir vistas comunes utilizando ViewSets.



Creando ViewSets y Rutas.


Los ViewSets te permiten definir las interacciones de tu API y permiten que el framework REST construya las URLs dinámicamente con un objeto Router. Al utilizar ViewSets, puedes evitar repetir la lógica para múltiples vistas. Los ViewSets incluyen acciones para las siguientes operaciones estándar: - Operación de creación: create() - Operación de recuperación: list() y retrieve() - Operación de actualización: update() y partial_update() - Operación de eliminación: destroy()

Creemos in ViewSets para el modelo course. Edita el archivo api/views.py y añade el siguiente código.

Elearning/educa/courses/api/views.py

# ...
from rest_framework import viewsets
from courses.api.serializers import SubjectSerializer,
CourseSerializer

class CourseViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Course.objects.all()
    serializer_class = CourseSerializer

Las subclases ReadOnlyModelViewSet nos proporciona las acciones de solo lectura list() y retrieve() para listar objetos o recuperar un único objeto.

Edita el archivo api/urls.py y crea una ruta para nuestra ViewSet de la siguiente forma:

Elearning/educa/courses/api/urls.py

from django.urls import path, include
from rest_framework import routers
from . import views

router = routers.DefaultRouter()
router.register('courses', views.CourseViewSet)

urlpatterns = [
    # ...
    path('', include(router.urls)),
]

Creas un objeto DefaultRouter y registras tu ViewSet con el prefijo 'courses'. El enrutador se encarga de generar automáticamente las URLs para tu ViewSet.

Abre http://127.0.0.1:8000/api/ en tu navegador. Verás que el enrutador lista todos los ViewSets en su URL base, como se muestra en la siguiente imagen:

The API root page of the REST framework browsable API


Puedes acceder a http://127.0.0.1:8000/api/courses/ para recuperar la lista de cursos. Puedes obtener más información sobre ViewSets en https://www.django-rest-framework.org/api-guide/viewsets/. También puedes encontrar más información sobre routers en https://www.django-rest-framework.org/api-guide/routers/.


Añadiendo acciones adicionales al ViewSet.


Puedes añadir acciones adicionales a los ViewSets. Vamos a cambiar tu vista anterior CourseEnrollView en una acción personalizada de ViewSet. Edita el archivo api/views.py y modifica la clase CourseViewSet de la siguiente manera:


Elearning/educa/courses/api/views.py

# ...
from rest_framework.decorators import action

class CourseViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Course.objects.all()
    serializer_class = CourseSerializer

    @action(detail=True,
        methods=['post'],
        authentication_classes=[BasicAuthentication],
        permission_classes=[IsAuthenticated])
    def enroll(self, request, *args, **kwargs):
        course = self.get_object()
        course.students.add(request.user)
        return Response({'enrolled': True})

En el código anterior, añades un método enroll() personalizado que representa una acción adicional para este ViewSet. El código anterior es el siguiente:

1. Utilizas el decorador de acción del framework con el parámetro detail=True para especificar que esta es una acción que se realizará en un único objeto.

2. El decorador te permite añadir atributos personalizados para la acción. Especificas que solo se permite el método post() para esta vista y estableces las clases de autenticación y permisos.

3. Utilizas self.get_object() para recuperar el objeto Curso.

4. Añades el usuario actual a la relación muchos a muchos de estudiantes y devuelves una respuesta de éxito personalizada.

Edita el archivo api/urls.py y elimina o comenta la siguiente URL, ya que ya no la necesitaremos más:

Elearning/educa/courses/api/urls.py

path('courses/<pk>/enroll/',
         views.CourseEnrollView.as_view(),
         name='course_enroll'),

Luego, edita el archivo api/views.py y elimina o comenta la clase CourseEnrollView.

La URL para inscribirse en los cursos ahora se genera automáticamente por el enrutador. La URL sigue siendo la misma ya que se construye dinámicamente utilizando el nombre de la acción, enroll.

Después de que los estudiantes se inscriben en un curso, necesitan acceder al contenido del mismo. A continuación, aprenderás cómo asegurarte de que solo los estudiantes inscritos puedan acceder al curso.


Creando permisos personalizados.


Quieres que los estudiantes puedan acceder al contenido de los cursos en los que están inscritos. Solo los estudiantes inscritos en un curso deberían poder acceder a su contenido. La mejor manera de hacer esto es con una clase de permiso personalizada. Framework REST proporciona una clase BasePermission que te permite definir los siguientes métodos:

- has_permission(): Verificación de permisos a nivel de vista

- has_object_permission(): Verificación de permisos a nivel de instancia


Estos métodos deben devolver True para otorgar acceso, o False de lo contrario.

Crea un nuevo archivo dentro del directorio courses/api/ y llámalo permissions.py. Añade el siguiente código en él:

Elearning/educa/courses/api/permissions.py

from rest_framework.permissions import BasePermission

class IsEnrolled(BasePermission):
    def has_object_permission(self, request, view, obj):
        return obj.students.filter(id=request.user.id).exists()

Subclasificas la clase BasePermission y sobrescribes el método has_object_permission(). Verificas que el usuario que realiza la solicitud esté presente en la relación students del objeto Curso. A continuación, vas a utilizar el permiso IsEnrolled.


Serializando el contenido del curso.


Necesitamos serializar el contenido del curso. El modelo Content incluye una clave foránea genérica que te permite asociar objetos de diferentes modelos de contenido. Sin embargo, agregamos un método render() común para todos los modelos de contenido. Puedes usar este método para proporcionar contenido renderizado a tu API.

Edita el archivo api/serializers.py de la aplicación de courses y añade el siguiente código:

Elearning/educa/courses/api/serializers.py

from courses.models import Subject, Course, Module, Content

class ItemRelatedField(serializers.RelatedField):
    def to_representation(self, value):
        return value.render()


class ContentSerializer(serializers.ModelSerializer):
    item = ItemRelatedField(read_only=True)

    class Meta:
        model = Content
        fields = ['order', 'item']

En este código, definimos un campo personalizado subclaseando el campo serializador RelatedField proporcionado por el framework REST y sobrescribiendo el método to_representation(). Definimos el serializador ContentSerializer para el modelo Content y utilizas el campo personalizado para el campo genérico de clave externa item.

Necesitamos un serializador alternativo para el modelo Module que incluya su contenido, así como también un Course serializer extendido. Edita el archivo api/serializers.py y añade el siguiente código:

Elearning/educa/courses/api/serializers.py

class ModuleWithContentsSerializer(serializers.ModelSerializer):
    contents = ContentSerializer(many=True)

    class Meta:
        model = Module
        fields = ['order', 'title', 'description', 'contents']


class CourseWithContentsSerializer(serializers.ModelSerializer):
    modules = ModuleWithContentsSerializer(many=True)

    class Meta:
        model = Course
        fields = ['id', 'subject', 'title', 'slug',
                  'overview', 'created', 'owner',
                  'modules']

Vamos a crear una vista que imite el comportamiento de la acción retrieve(), pero que incluya el contenido del curso. Edita el archivo api/views.py y añade el siguiente método a la clase CourseViewSet:

Elearning/educa/courses/api/views.py

from courses.api.permissions import IsEnrolled
from courses.api.serializers import CourseWithContentsSerializer

class CourseViewSet(viewsets.ReadOnlyModelViewSet):
# ...
@action(detail=True,
    methods=['get'],
    serializer_class=CourseWithContentsSerializer,
    authentication_classes=[BasicAuthentication],
    permission_classes=[IsAuthenticated, IsEnrolled])
def contents(self, request, *args, **kwargs):
    return self.retrieve(request, *args, **kwargs)

La descripción de este método es la siguiente:

1. Utilizas el decorador de acción con el parámetro detail=True para especificar una acción que se realiza en un único objeto.

2. Especificas que solo se permite el método GET para esta acción.

3. Utilizas la nueva clase de serializador CourseWithContentsSerializer que incluye el contenido del curso renderizado.

4. Utilizas tanto IsAuthenticated como tus permisos personalizados IsEnrolled. Al hacerlo, te aseguras de que solo los usuarios inscritos en el curso puedan acceder a su contenido.

5. Utilizas la acción retrieve() existente para devolver el objeto Curso.


Abre http://127.0.0.1:8000/api/courses/2/contents/ en tu navegador. Si accedes a la vista con las credenciales correctas, verás que cada módulo del curso incluye el HTML renderizado para el contenido del curso.


API respuesta

Has construido una API simple que permite a otros servicios acceder a la aplicación de cursos de manera programática. REST framework también te permite manejar la creación y edición de objetos con la clase ModelViewSet.


Hemos cubierto los aspectos principales del framework Django REST, pero encontrarás más información sobre sus características en su extensa documentación en https://www.django-rest-framework.org/.


Utilizando la API RESTful


Ahora que hemos implementado una API, puedes utilizarla desde otras aplicaciones. Puedes interactuar con la API utilizando la Fetch API de JavaScript en el frontend de tu aplicación.

También puedes utilizar la API desde aplicaciones construidas con Python o cualquier otro lenguaje de programación. Crearemos una aplicación Python simple que utilice la API RESTful para recuperar todos los cursos disponibles y luego inscribir a un estudiante en todos ellos. Aprenderás cómo autenticarte contra la API utilizando autenticación básica de HTTP y realizar solicitudes GET y POST.

Para ello vamos a utilizar la biblioteca Python Requests. Requests abstrae la complejidad de tratar con solicitudes HTTP y proporciona una interfaz muy simple para consumir servicios HTTP. Puedes encontrar la documentación para la biblioteca Requests en https://requests.readthedocs.io/en/master/.

Abre el shell e instala la librería requests

pip install requests

Crea un nuevo directorio al mismo nivel que el directorio educa y llámalo api_examples. Crea un nuevo archivo dentro de este nuevo directorio y llámalo enroll_all.py. La estructura del directorio debería ser parecida a esta:

api_examples/
    enroll_all.py
educa/
    ....

Edita este nuevo archivo y añade el siguiente código:

Elearning/educa/api_examples/enroll_all.py

import requests

base_url = 'http://127.0.0.1:8000/api/'

# devuelve todos los cursos disponibles en la base de datos
r = requests.get(f'{base_url}courses/')
# trasforma el formato json en un diccionario python    
courses = r.json()

available_courses = ', '.join([course['title'] for course in courses])
print(f'Available courses: {available_courses}')

Con este código realizamos las siguientes acciones. 

  1. Importamos la librería requests y definimos la URL base de la API.
  2. Usamos requests.get() para obtener los datos de la API al enviar una petición get a la URL 'http:/127.0.0.1:8000/api/courses/. Esta dirección de la API se puede acceder de forma pública, así que no hace falta autenticación.
  3. Usamos el método json() para trasformar los datos en formato json proporcionados por la API a un diccionario Python.
  4. Imprimimos el atributo 'nombre' de cada curso.

Abre el servidor de desarrollo del proyecto con:

python manage.py runserver

En otro terminal ejecuta el siguiente comando desde el directorio api_examples/

python enroll_all.py

La salida del programa será una lista con los nombres de los cursos, algo parecido a esto:

$ python enroll_all.py 
Available courses: Matemáticas Discretas., Curso 2, Curso 1
Esta es nuestra primera llamada automática a la API.

Edita el archivo enroll_all.py y realiza los siguientes cambios:

Elearning/educa/api_examples/enroll_all.py

import requests

username = ''
password = ''
base_url = 'http://127.0.0.1:8000/api/'

# devuelve todos los cursos disponibles en la base de datos
r = requests.get(f'{base_url}courses/')
# trasforma el formato json en un diccionario python    
courses = r.json()

available_courses = ', '.join([course['title'] for course in courses])
print(f'Available courses: {available_courses}')

for course in courses:
    course_id = course['id']
    course_title = course['title']
    r = requests.post(f'{base_url}courses/{course_id}/enroll/', auth=(username, password))
    if r.status_code == 200:
        # successful request
        print(f'Successfully enrolled in {course_title}')

Remplaza los valores de usuario y contraseña con los de algún usuario existente. 

Con el nuevo código podrás realizar las siguientes acciones:
  1. Defines el usuario y contraseña del estudiante que quieres apuntar a tus cursos.
  2. Iteras sobre los cursos disponibles devueltos desde el API.
  3. Guardas el ID del curso en la variable course_id y el nombre del curso en la variable course_title.
  4. Usamos requests.post() para enviar una solicitud post a la URL http://127.0.0.1:8000/api/courses/[id]/enroll/ para cada curso. Esta URL se corresponde a la vista del API CourseEnrollView, la cual nos permite apuntar a los estudiantes a los cursos. Construimos la URL para cada curso usando la variable course_id. La vista CourseEnrollView requiere autentificación. Usa los permisos de IsAuthenticated y los de la clase BasicAuthentication. La librería Requests soporta autenticación básica, lista para usar. Usas el parámetro auth para pasar una tupla con el nombre de usuario y la contraseña para autenticar al usuario usando la autenticación básica HTTP.
  5. Si el código de estado de la petición es 200 OK, imprimimos un mensaje para indicar que el usuario ha sido registrado en el curso con éxito.
Podemos usar diferentes clases de autenticación con la librería Requests. Puedes encontrar más información en https://requests.readthedocs.io/en/master/user/authentication/

Ejecuta la siguiente instrucción en un terminal dentro del directorio api_examples.

python enroll_all.py

Verás una salida parecida a esta:

Available courses: Matemáticas Discretas., Curso 2, Curso 1
Successfully enrolled in Matemáticas Discretas.
Successfully enrolled in Curso 2
Successfully enrolled in Curso 1

¡Genial! Has registrado un usuario correctamente en todos los cursos disponibles usando la API. Verás un mensaje mostrando que todo ha sucedido correctamente. Como puedes ver es bastante sencillo usar la API desde otra aplicación. 

El código fuente de este capítulo se puede encontrar en este enlace de Github.

No hay comentarios:

Publicar un comentario