Flask
Tutorial Flask: Validaciones y constraints
Aprende a implementar validaciones y constraints en SQLAlchemy para garantizar la integridad y seguridad de tus datos en modelos Python.
Aprende Flask y certifícateValidaciones 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
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.
Otras lecciones de Flask
Accede a todas las lecciones de Flask y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Flask
Introducción Y Entorno
Instalación Y Configuración Flask Con Venv
Introducción Y Entorno
Rutas Endpoints Rest Get
Api Rest
Respuestas Con Esquemas Flask Marshmallow
Api Rest
Rutas Endpoints Rest Post, Put Y Delete
Api Rest
Manejo De Errores Y Códigos De Estado Http
Api Rest
Ejercicios de programación de Flask
Evalúa tus conocimientos de esta lección Validaciones y constraints con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.