API REST CRUD completo

Avanzado
Django
Django
Actualizado: 01/07/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

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.

¿Te está gustando esta lección?

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

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.

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.

Completa Django y certifícate

Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración