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