Construcción de APIs RESTful con Django REST Framework

Introducción a la Arquitectura REST

REST, o Representational State Transfer (Transferencia de Estado Representacional), no es una tecnología específica sino un estilo arquitectónico para sistemas distribuidos en la web. Su principio fundamental es la gestión de recursos: cada elemento de datos o funcionalidad en la red se considera un "recurso" y es identificado de manera única por una URL (Uniform Resource Locator). Los clientes interactúan con estos recursos a través de sus representaciones, lo que provoca cambios en el estado de la aplicación cliente.

En una arquitectura RESTful, todas las operaciones, ya sea la obtención o la manipulación (crear, leer, actualizar, eliminar), giran en torno a estos recursos. La idea de tratar todo como un recurso es la característica más distintiva de REST en comparación con otros estilos arquitectónicos. Esta filosofía ha dado origen a lo que se conoce como Arquitectura Orientada a Recursos (ROA).

Flujo de Desarrollo REST

El proceso de desarrollo REST se basa en el uso de métodos HTTP estándar para interactuar con los recursos. Estos métodos corresponden a las operaciones CRUD (Crear, Leer, Actualizar, Eliminar):

  • GET: Recupera la representación de un recurso o una colección de recursos.
  • POST: Crea un nuevo recurso.
  • PUT: Actualiza completamente un recurso existente.
  • PATCH: Actualiza parcialmente un recurso existente.
  • DELETE: Elimina un recurso.

En el contexto de un framwork web como Django, estas operaciones se implementan típicamente en vistas que responden a diferentes métodos HTTP. Es común usar vistas basadas en clases (CBV, Class-Based Views) ya que ofrecen mayor modularidad y la capacidad de integrar funcionalidades adicionales de manera más elegante que las vistas basadas en funciones (FBV, Function-Based Views). El desarrollo consiste en mapear las URLs a las vistas y las operaciones HTTP a los métodos correspondientes en esas vistas, devolviendo los datos y códigos de estado apropiados.

¿Qué es Django REST Framework (DRF)?

Django REST Framework (DRF) es una potente y flexible biblioteca de código abierto construida sobre el framework web Django, diseñada para facilitar la rápida construcción de APIs RESTful robustas y eficientes. Proporciona un conjunto de herramientas y abstracciones que simplifican tareas comunes como la serialización de datos, autenticación, autorización, limitación de tasas y la implementación de vistas de API.

Uso Básico con ViewSets

DRF ofrece un enfoque de desarrollo rápido mediante ViewSets, que agrupan la lógica para interactuar con un modelo completo (listado, creación, recuperación, actualización y eliminación) en una única clase.

Instalación y Configuración

pip install djangorestframework

Luego, registre la aplicación en su archivo settings.py:

# settings.py
INSTALLED_APPS = [
    # ... otras apps
    'rest_framework',
    'mi_app_api', # Su aplicación Django que contendrá las APIs
]

Definición de Vistas y Serializadores

Cree un modelo de ejemplo en mi_app_api/models.py:

# mi_app_api/models.py
from django.db import models

class PerfilUsuario(models.Model):
    nombre_usuario = models.CharField(max_length=100, unique=True)
    clave_hash = models.CharField(max_length=128) # Almacenar contraseñas hasheadas
    fecha_registro = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.nombre_usuario

Defina un serializador en mi_app_api/serializers.py para convertir el modelo a JSON/XML y viceversa:

# mi_app_api/serializers.py
from rest_framework import serializers
from . import models

class PerfilUsuarioSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = models.PerfilUsuario
        fields = ['id', 'nombre_usuario', 'fecha_registro'] # Campos a exponer
        # O fields = '__all__' para incluir todos los campos
        # O exclude = ['clave_hash'] para excluir campos específicos
        # depth = 1 # Para incluir representaciones anidadas de relaciones ForeignKey

Implemente un ViewSet en mi_app_api/views.py:

# mi_app_api/views.py
from rest_framework import viewsets
from . import models
from . import serializers

class UsuarioViewSet(viewsets.ModelViewSet):
    """
    API endpoint que permite ver y editar perfiles de usuario.
    """
    queryset = models.PerfilUsuario.objects.all().order_by('-fecha_registro')
    serializer_class = serializers.PerfilUsuarioSerializer

Configuración de URLs

En el archivo mi_proyecto/urls.py, configure el enrutador de DRF:

# mi_proyecto/urls.py
from django.urls import include, re_path
from rest_framework import routers
from mi_app_api import views

router = routers.DefaultRouter()
router.register(r'usuarios', views.UsuarioViewSet)

urlpatterns = [
    # ... otras URLs
    re_path(r'^api/', include(router.urls)),
]

Con esta configuración, DRF genera automáticamente las siguientes rutas:

  • GET /api/usuarios/: Listar todos los usuarios.
  • POST /api/usuarios/: Crear un nuevo usuario.
  • GET /api/usuarios/{id}/: Recuperar un usuario específico.
  • PUT /api/usuarios/{id}/: Actualizar completamente un usuario.
  • PATCH /api/usuarios/{id}/: Actualizar parcialmente un usuario.
  • DELETE /api/usuarios/{id}/: Eliminar un usuario.

Desarrollo de Vistas Basadas en Clases (CBV)

Para un control más granular, puede usar la clase APIView de DRF, que permite implementar métodos HTTP específicos para cada recurso.

Configuración de URLs

# mi_app_api/urls.py (crear este archivo si no existe)
from django.urls import re_path
from . import views

urlpatterns = [
    re_path(r'^usuarios-manual/$', views.ListaUsuariosAPI.as_view()),
    re_path(r'^usuarios-manual/(?P<pk>[0-9]+)/$', views.DetalleUsuarioAPI.as_view()),
]

# En mi_proyecto/urls.py, incluir las URLs de mi_app_api:
# from django.urls import include, re_path
# re_path(r'^api-manual/', include('mi_app_api.urls')),

Serializador Personalizado

# mi_app_api/serializers.py
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from . import models

class DatosUsuarioSerializer(serializers.Serializer):
    identificador = serializers.IntegerField(read_only=True, source='pk')
    nombre_usuario = serializers.CharField(required=True, allow_blank=False, max_length=100)
    clave_hash = serializers.CharField(write_only=True) # Campo de escritura única

    def validate_nombre_usuario(self, valor):
        if models.PerfilUsuario.objects.filter(nombre_usuario=valor).exists():
            raise ValidationError("Este nombre de usuario ya está en uso.")
        return valor

    def validate_clave_hash(self, valor):
        if len(valor) < 8:
            raise ValidationError("La clave debe tener al menos 8 caracteres.")
        # Aquí podrías agregar lógica para hashear la clave antes de guardar
        return valor

    def create(self, datos_validados):
        return models.PerfilUsuario.objects.create(**datos_validados)

    def update(self, instancia, datos_validados):
        instancia.nombre_usuario = datos_validados.get('nombre_usuario', instancia.nombre_usuario)
        instancia.clave_hash = datos_validados.get('clave_hash', instancia.clave_hash)
        instancia.save()
        return instancia

Vistas Basadas en Clases (CBV)

# mi_app_api/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from . import models
from .serializers import DatosUsuarioSerializer

class ListaUsuariosAPI(APIView):
    """
    Gestiona la lista de usuarios (GET para listar, POST para crear).
    """
    def get(self, request, *args, **kwargs):
        usuarios = models.PerfilUsuario.objects.all()
        serializador = DatosUsuarioSerializer(usuarios, many=True)
        return Response(serializador.data)

    def post(self, request, *args, **kwargs):
        serializador = DatosUsuarioSerializer(data=request.data)
        if serializador.is_valid():
            serializador.save()
            return Response(serializador.data, status=status.HTTP_201_CREATED)
        return Response(serializador.errors, status=status.HTTP_400_BAD_REQUEST)

class DetalleUsuarioAPI(APIView):
    """
    Gestiona un usuario individual (GET, PUT, DELETE).
    """
    def get_object(self, pk):
        try:
            return models.PerfilUsuario.objects.get(pk=pk)
        except models.PerfilUsuario.DoesNotExist:
            raise status.HTTP_404_NOT_FOUND # O levantar un Http404

    def get(self, request, pk, *args, **kwargs):
        usuario = self.get_object(pk)
        serializador = DatosUsuarioSerializer(usuario)
        return Response(serializador.data)

    def put(self, request, pk, *args, **kwargs):
        usuario = self.get_object(pk)
        serializador = DatosUsuarioSerializer(usuario, data=request.data)
        if serializador.is_valid():
            serializador.save()
            return Response(serializador.data)
        return Response(serializador.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk, *args, **kwargs):
        usuario = self.get_object(pk)
        usuario.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

Desarrollo de Vistas Basadas en Funciones (FBV)

DRF también permite usar vistas basadas en funciones con el decorador @api_view para manejar diferentes métodos HTTP.

Configuración de URLs

# mi_app_api/urls.py (añadir a este archivo)
from django.urls import re_path
from . import views

urlpatterns = [
    # ... URLs anteriores
    re_path(r'^usuarios-func/$', views.manejar_usuarios_func),
    re_path(r'^usuarios-func/(?P<pk>[0-9]+)/$', views.manejar_detalle_usuario_func),
]

Vistas Basadas en Funciones (FBV)

# mi_app_api/views.py (añadir a este archivo)
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from . import models
from .serializers import DatosUsuarioSerializer

@api_view(['GET', 'POST'])
def manejar_usuarios_func(request):
    """
    Listar todos los usuarios o crear uno nuevo.
    """
    if request.method == 'GET':
        usuarios = models.PerfilUsuario.objects.all()
        serializador = DatosUsuarioSerializer(usuarios, many=True)
        return Response(serializador.data)

    elif request.method == 'POST':
        serializador = DatosUsuarioSerializer(data=request.data)
        if serializador.is_valid():
            serializador.save()
            return Response(serializador.data, status=status.HTTP_201_CREATED)
        return Response(serializador.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET', 'PUT', 'DELETE'])
def manejar_detalle_usuario_func(request, pk):
    """
    Recuperar, actualizar o eliminar un usuario.
    """
    try:
        usuario = models.PerfilUsuario.objects.get(pk=pk)
    except models.PerfilUsuario.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializador = DatosUsuarioSerializer(usuario)
        return Response(serializador.data)

    elif request.method == 'PUT':
        serializador = DatosUsuarioSerializer(usuario, data=request.data)
        if serializador.is_valid():
            serializador.save()
            return Response(serializador.data)
        return Response(serializador.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        usuario.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

Control de Permisos

DRF ofrece un sistema flexible para controlar quién puede acceder a qué recursos. Puede definir permisos globales o específicos para cada vista.

Configuración Global en settings.py

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticatedOrReadOnly', # Solo usuarios autenticados pueden escribir
        #'mi_app_api.permisos.PermisoPersonalizado', # Para usar una clase de permiso personalizada
    ],
    # ... otras configuraciones de DRF
}

Creación de Permisos Personalizados

Puede definir sus propias clases de permiso en un archivo como mi_app_api/permisos.py:

# mi_app_api/permisos.py
from rest_framework import permissions

class EsAdministradorOSoloLectura(permissions.BasePermission):
    """
    Permite el acceso de escritura solo a administradores. Otros usuarios tienen solo lectura.
    """
    def has_permission(self, request, view):
        # Permiso de lectura para cualquier solicitud (GET, HEAD, OPTIONS)
        if request.method in permissions.SAFE_METHODS:
            return True
        # Permiso de escritura solo para usuarios que son staff (administradores)
        return request.user and request.user.is_staff

class EsPropietarioOAdmin(permissions.BasePermission):
    """
    Permite a los propietarios de un objeto o a los administradores editarlo/eliminarlo.
    """
    def has_object_permission(self, request, view, obj):
        # Permiso de lectura siempre permitido
        if request.method in permissions.SAFE_METHODS:
            return True
        # Permiso de escritura/edición solo si el usuario es el propietario del objeto o un staff
        return obj.nombre_usuario == request.user.username or request.user.is_staff

Para aplicar estos permisos, puede especificar permission_classes en sus vistas:

# mi_app_api/views.py (ejemplo con CBV)
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework import permissions # Importar permissions
from . import models
from .serializers import DatosUsuarioSerializer
from .permisos import EsAdministradorOSoloLectura # Importar su permiso personalizado

class ListaUsuariosAPI(APIView):
    permission_classes = [EsAdministradorOSoloLectura] # Aplicar permiso a toda la vista

    def get(self, request, *args, **kwargs):
        # ...
        pass

    def post(self, request, *args, **kwargs):
        # ...
        pass

Etiquetas: Django DjangoRESTFramework DRF REST APIsRESTful

Publicado el 7-14 08:15