50% OFF Plus
--:--:--
¡Obtener!

Validaciones y constraints

Intermedio
Flask
Flask
Actualizado: 20/06/2025

¡Desbloquea el curso de Flask completo!

IA
Ejercicios
Certificado
Entrar

Mira la lección en vídeo

Accede al vídeo completo de esta lección y a más contenido exclusivo con el Plan Plus.

Desbloquear Plan Plus

Validaciones de campo

Las validaciones de campo en SQLAlchemy permiten garantizar la integridad de los datos directamente en el modelo, antes de que lleguen a la base de datos. Estas validaciones se ejecutan automáticamente cuando intentamos crear o modificar registros, proporcionando una capa de seguridad adicional que complementa las restricciones de la base de datos.

SQLAlchemy ofrece múltiples mecanismos para implementar validaciones, desde constraints básicos hasta validaciones personalizadas más complejas. La ventaja de implementar estas validaciones a nivel de modelo es que se aplican consistentemente independientemente de cómo se acceda a los datos.

Validaciones con el decorador validates

El decorador @validates es la forma más directa de implementar validaciones personalizadas en SQLAlchemy. Este decorador permite definir funciones que se ejecutan automáticamente cuando se asigna un valor a un campo específico.

from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.orm import validates
from datetime import datetime
import re

class Usuario(db.Model):
    __tablename__ = 'usuarios'
    
    id = Column(Integer, primary_key=True)
    email = Column(String(120), nullable=False, unique=True)
    edad = Column(Integer)
    telefono = Column(String(15))
    fecha_registro = Column(DateTime, default=datetime.utcnow)
    
    @validates('email')
    def validar_email(self, key, email):
        patron = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        if not re.match(patron, email):
            raise ValueError("El formato del email no es válido")
        return email.lower()
    
    @validates('edad')
    def validar_edad(self, key, edad):
        if edad is not None and (edad < 0 or edad > 120):
            raise ValueError("La edad debe estar entre 0 y 120 años")
        return edad

En este ejemplo, las funciones de validación reciben tres parámetros: self (la instancia del modelo), key (el nombre del campo) y el valor que se está asignando. La función debe retornar el valor validado o lanzar una excepción si la validación falla.

Validaciones múltiples y transformaciones

El decorador @validates también permite validar múltiples campos con una sola función y realizar transformaciones de datos durante la asignación:

class Producto(db.Model):
    __tablename__ = 'productos'
    
    id = Column(Integer, primary_key=True)
    nombre = Column(String(100), nullable=False)
    precio = Column(Integer)  # Precio en centavos
    codigo = Column(String(20), unique=True)
    
    @validates('nombre', 'codigo')
    def validar_texto(self, key, valor):
        if not valor or not valor.strip():
            raise ValueError(f"El campo {key} no puede estar vacío")
        
        # Transformación: convertir a mayúsculas para códigos
        if key == 'codigo':
            return valor.upper().strip()
        
        return valor.strip()
    
    @validates('precio')
    def validar_precio(self, key, precio):
        if precio is not None and precio <= 0:
            raise ValueError("El precio debe ser mayor que cero")
        return precio

Validaciones con hybrid_property

Los hybrid properties ofrecen una alternativa elegante para campos que requieren validaciones complejas o cálculos dinámicos:

from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy import Column, String, Integer

class Empleado(db.Model):
    __tablename__ = 'empleados'
    
    id = Column(Integer, primary_key=True)
    _salario_base = Column('salario_base', Integer)  # Almacenado en centavos
    dni = Column(String(9), unique=True)
    
    @hybrid_property
    def salario_base(self):
        return self._salario_base / 100 if self._salario_base else 0
    
    @salario_base.setter
    def salario_base(self, valor):
        if valor < 0:
            raise ValueError("El salario no puede ser negativo")
        if valor > 1000000:  # 10,000 euros
            raise ValueError("El salario excede el límite máximo")
        self._salario_base = int(valor * 100)
    
    @validates('dni')
    def validar_dni(self, key, dni):
        if not dni or len(dni) != 9:
            raise ValueError("El DNI debe tener exactamente 9 caracteres")
        
        # Validación básica del formato DNI español
        numeros = dni[:8]
        letra = dni[8].upper()
        
        if not numeros.isdigit():
            raise ValueError("Los primeros 8 caracteres del DNI deben ser números")
        
        letras_validas = "TRWAGMYFPDXBNJZSQVHLCKE"
        letra_esperada = letras_validas[int(numeros) % 23]
        
        if letra != letra_esperada:
            raise ValueError("La letra del DNI no es correcta")
        
        return dni.upper()

Validaciones condicionales y dependientes

Guarda tu progreso

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

En ocasiones necesitamos validaciones que dependen del valor de otros campos o del estado del objeto:

from sqlalchemy import Column, String, Integer, Boolean, DateTime
from datetime import datetime, timedelta

class Reserva(db.Model):
    __tablename__ = 'reservas'
    
    id = Column(Integer, primary_key=True)
    fecha_inicio = Column(DateTime, nullable=False)
    fecha_fin = Column(DateTime, nullable=False)
    confirmada = Column(Boolean, default=False)
    precio_total = Column(Integer)
    descuento_aplicado = Column(Integer, default=0)
    
    @validates('fecha_fin')
    def validar_fecha_fin(self, key, fecha_fin):
        if self.fecha_inicio and fecha_fin <= self.fecha_inicio:
            raise ValueError("La fecha de fin debe ser posterior a la fecha de inicio")
        
        # No permitir reservas de más de 30 días
        if self.fecha_inicio and (fecha_fin - self.fecha_inicio).days > 30:
            raise ValueError("Las reservas no pueden exceder 30 días")
        
        return fecha_fin
    
    @validates('descuento_aplicado')
    def validar_descuento(self, key, descuento):
        if descuento < 0 or descuento > 100:
            raise ValueError("El descuento debe estar entre 0 y 100")
        
        # Solo permitir descuentos altos en reservas confirmadas
        if descuento > 50 and not self.confirmada:
            raise ValueError("Descuentos superiores al 50% requieren confirmación previa")
        
        return descuento

Manejo de errores en validaciones

Es importante capturar y manejar adecuadamente los errores de validación en nuestras rutas de Flask:

from flask import request, jsonify
from sqlalchemy.exc import IntegrityError

@app.route('/usuarios', methods=['POST'])
def crear_usuario():
    try:
        datos = request.get_json()
        
        usuario = Usuario(
            email=datos.get('email'),
            edad=datos.get('edad'),
            telefono=datos.get('telefono')
        )
        
        db.session.add(usuario)
        db.session.commit()
        
        return jsonify({
            'mensaje': 'Usuario creado exitosamente',
            'id': usuario.id
        }), 201
        
    except ValueError as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 400
        
    except IntegrityError as e:
        db.session.rollback()
        return jsonify({
            'error': 'Error de integridad en la base de datos',
            'detalle': 'Posible duplicado en campo único'
        }), 409

Las validaciones de campo proporcionan una capa robusta de protección que garantiza la calidad de los datos almacenados. Al combinar el decorador @validates con hybrid properties y un manejo adecuado de errores, podemos crear modelos que no solo almacenen datos, sino que también los validen y transformen según las reglas de negocio de nuestra aplicación.

Aprendizajes de esta lección de Flask

  • Comprender la importancia de las validaciones de campo en SQLAlchemy para mantener la integridad de los datos.
  • Aprender a utilizar el decorador @validates para implementar validaciones personalizadas en modelos.
  • Conocer cómo validar múltiples campos y realizar transformaciones durante la asignación de valores.
  • Entender el uso de hybrid_property para validaciones complejas y cálculos dinámicos.
  • Saber manejar errores de validación y excepciones para asegurar un flujo robusto en aplicaciones Flask.

Completa este curso de Flask y certifícate

Únete a nuestra plataforma de cursos de programación 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