Implementación completa de CRUD con ViewSets
Ahora que conoces los fundamentos de serializadores y ViewSets, es momento de construir una API REST completa que integre todas las operaciones CRUD con una base de datos real. Vamos a desarrollar un sistema de gestión de productos que demuestre cómo Django REST Framework maneja automáticamente las operaciones de creación, lectura, actualización y eliminación.
Definición del modelo
Comenzaremos definiendo un modelo que represente un producto en nuestro sistema. Este modelo incluirá campos comunes que encontrarías en cualquier aplicación comercial:
# models.py
from django.db import models
from django.core.validators import MinValueValidator
from decimal import Decimal
class Categoria(models.Model):
nombre = models.CharField(max_length=100, unique=True)
descripcion = models.TextField(blank=True)
activa = models.BooleanField(default=True)
def __str__(self):
return self.nombre
class Meta:
verbose_name_plural = "categorías"
class Producto(models.Model):
nombre = models.CharField(max_length=200)
descripcion = models.TextField()
precio = models.DecimalField(
max_digits=10,
decimal_places=2,
validators=[MinValueValidator(Decimal('0.01'))]
)
stock = models.PositiveIntegerField(default=0)
categoria = models.ForeignKey(
Categoria,
on_delete=models.CASCADE,
related_name='productos'
)
activo = models.BooleanField(default=True)
fecha_creacion = models.DateTimeField(auto_now_add=True)
fecha_actualizacion = models.DateTimeField(auto_now=True)
def __str__(self):
return self.nombre
class Meta:
ordering = ['-fecha_creacion']
Serializadores para CRUD completo
Los serializadores deben manejar tanto la serialización de salida como la validación de entrada para todas las operaciones CRUD:
# serializers.py
from rest_framework import serializers
from .models import Producto, Categoria
class CategoriaSerializer(serializers.ModelSerializer):
productos_count = serializers.SerializerMethodField()
class Meta:
model = Categoria
fields = ['id', 'nombre', 'descripcion', 'activa', 'productos_count']
def get_productos_count(self, obj):
return obj.productos.filter(activo=True).count()
class ProductoSerializer(serializers.ModelSerializer):
categoria_nombre = serializers.CharField(source='categoria.nombre', read_only=True)
class Meta:
model = Producto
fields = [
'id', 'nombre', 'descripcion', 'precio', 'stock',
'categoria', 'categoria_nombre', 'activo',
'fecha_creacion', 'fecha_actualizacion'
]
read_only_fields = ['fecha_creacion', 'fecha_actualizacion']
def validate_precio(self, value):
if value <= 0:
raise serializers.ValidationError("El precio debe ser mayor que cero")
return value
def validate_stock(self, value):
if value < 0:
raise serializers.ValidationError("El stock no puede ser negativo")
return value
ViewSets con operaciones CRUD completas
El ModelViewSet proporciona automáticamente todas las operaciones CRUD, pero podemos personalizarlo para añadir lógica de negocio específica:
# views.py
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django.db.models import Q
from .models import Producto, Categoria
from .serializers import ProductoSerializer, CategoriaSerializer
class CategoriaViewSet(viewsets.ModelViewSet):
queryset = Categoria.objects.all()
serializer_class = CategoriaSerializer
def destroy(self, request, *args, **kwargs):
categoria = self.get_object()
if categoria.productos.exists():
return Response(
{'error': 'No se puede eliminar una categoría con productos asociados'},
status=status.HTTP_400_BAD_REQUEST
)
return super().destroy(request, *args, **kwargs)
class ProductoViewSet(viewsets.ModelViewSet):
queryset = Producto.objects.select_related('categoria').all()
serializer_class = ProductoSerializer
def get_queryset(self):
queryset = super().get_queryset()
# Filtrado por categoría
categoria_id = self.request.query_params.get('categoria')
if categoria_id:
queryset = queryset.filter(categoria_id=categoria_id)
# Filtrado por estado activo
activo = self.request.query_params.get('activo')
if activo is not None:
queryset = queryset.filter(activo=activo.lower() == 'true')
# Búsqueda por nombre
busqueda = self.request.query_params.get('buscar')
if busqueda:
queryset = queryset.filter(
Q(nombre__icontains=busqueda) |
Q(descripcion__icontains=busqueda)
)
return queryset
def perform_create(self, serializer):
# Lógica adicional al crear un producto
serializer.save()
def perform_update(self, serializer):
# Lógica adicional al actualizar un producto
serializer.save()
@action(detail=True, methods=['post'])
def actualizar_stock(self, request, pk=None):
producto = self.get_object()
nuevo_stock = request.data.get('stock')
if nuevo_stock is None:
return Response(
{'error': 'El campo stock es requerido'},
status=status.HTTP_400_BAD_REQUEST
)
try:
nuevo_stock = int(nuevo_stock)
if nuevo_stock < 0:
raise ValueError()
except (ValueError, TypeError):
return Response(
{'error': 'El stock debe ser un número entero positivo'},
status=status.HTTP_400_BAD_REQUEST
)
producto.stock = nuevo_stock
producto.save()
serializer = self.get_serializer(producto)
return Response(serializer.data)
Configuración de URLs
Las URLs se configuran de manera simple utilizando el router de DRF, que automáticamente genera todos los endpoints CRUD:
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ProductoViewSet, CategoriaViewSet
router = DefaultRouter()
router.register(r'productos', ProductoViewSet)
router.register(r'categorias', CategoriaViewSet)
urlpatterns = [
path('api/', include(router.urls)),
]
Endpoints generados automáticamente
Con esta configuración, el router genera automáticamente los siguientes endpoints:
Para productos:
GET /api/productos/ # Listar todos los productos
POST /api/productos/ # Crear un nuevo producto
GET /api/productos/{id}/ # Obtener un producto específico
PUT /api/productos/{id}/ # Actualizar completamente un producto
PATCH /api/productos/{id}/ # Actualizar parcialmente un producto
DELETE /api/productos/{id}/ # Eliminar un producto
POST /api/productos/{id}/actualizar_stock/ # Acción personalizada
Para categorías:
GET /api/categorias/ # Listar todas las categorías
POST /api/categorias/ # Crear una nueva categoría
GET /api/categorias/{id}/ # Obtener una categoría específica
PUT /api/categorias/{id}/ # Actualizar completamente una categoría
PATCH /api/categorias/{id}/ # Actualizar parcialmente una categoría
DELETE /api/categorias/{id}/ # Eliminar una categoría
Ejemplos de uso de la API
Crear un producto:
POST /api/productos/
{
"nombre": "Laptop Gaming",
"descripcion": "Laptop para gaming de alta gama",
"precio": "1299.99",
"stock": 10,
"categoria": 1,
"activo": true
}
Actualizar parcialmente un producto:
PATCH /api/productos/1/
{
"precio": "1199.99",
"stock": 8
}
Filtrar productos por categoría:
GET /api/productos/?categoria=1&activo=true&buscar=laptop
Manejo de errores y validaciones
El ViewSet maneja automáticamente los errores de validación y devuelve respuestas HTTP apropiadas:
# Ejemplo de respuesta de error de validación
{
"precio": ["El precio debe ser mayor que cero"],
"stock": ["El stock no puede ser negativo"]
}
Para errores de objetos no encontrados, DRF devuelve automáticamente un error 404:
{
"detail": "No encontrado."
}
Esta implementación proporciona una API REST completa y funcional que maneja todas las operaciones CRUD de manera eficiente, incluyendo validaciones, filtrado y manejo de errores. El código es escalable y sigue las mejores prácticas de Django REST Framework.
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en Django
Documentación oficial de Django
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Django es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Django
Explora más contenido relacionado con Django y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Comprender la definición de modelos Django para representar entidades comerciales.
- Aprender a crear serializadores que validen y transformen datos para operaciones CRUD.
- Utilizar ViewSets para implementar operaciones CRUD completas y personalizadas.
- Configurar routers para generar automáticamente endpoints RESTful.
- Manejar validaciones, filtrados y respuestas de error en una API REST.