Creación de modelos con Flask-SQLAlchemy
En este artículo veremos como crear los modelos que representarán las tablas en la base de datos de nuestra aplicación Flask utilizando la extensión Flask-SQLAlchemy. Analizaremos como generar dichas tablas y generar las relaciones entre ellas.
Al utilizar un ORM como SQLAlchemy cada tabla de la base de datos debe estar representada como un objeto en nuestra aplicación. De está manera, será posible realizar consultas a la base de datos a través de los objetos que la representan.
Modelos en SQLAlchemy
En Flask-SQLAlchemy cada uno de los modelos que se utilizarán para representar y manejar las tablas de las bases de datos deben heredar de la clase base db.Model
.
class Persona(db.Model):
# Configuración de columnas
El nombre que se le dará a la tabla será el nombre de la clase convertido a minúscula y en el caso
de que el nombre este en formato CamelCase entonces se separarán las palabras por un guion bajo.
Si se quiere setear el nombre de la tabla a otro valor sin modificar el nombre de la clase puede configurarse el atributo tablename
de la clase.
class Persona(db.Model):
__tablename__ = 'nombre_tabla'
Columnas
Para definir cada una de las columnas de la tabla se utiliza la clase Column
. El nombre de la misma
será el del atributo al que se ha asignado o en el caso que se quiera configurar un valor diferente este se puede definir como una cadena de caracteres y pasarlo como primer parámetro.
class Persona(db.Model):
apellido = db.Column(db.String(80)) #El nombre es el del atributo
class Persona(db.Model):
apellido = db.Column('Apellido', db.String(80))#Se especifica el nombre de la columna
El siguiente parámetro debe ser el del tipo de columna que le indicará a SQLAlchemy que tipo de
columna crear en la base de datos. A continuación se listan los tipos de columnas soportados por SQLAlchemy. Es importante resaltar que al crear las tablas la extensión creará el tipo de columna que se asemeje más al tipo de columna correspondiente para el tipo de base de datos elegida en la configuración.
Integer | Número entero |
String(size) | Cadena de caracteres con un largo máximo |
Text | Cadena de texto unicode más largo |
DateTime | Fecha y hora expresada como el objeto datetime de Python |
Float | Número de punto flotantet |
Boolean | Valor booleano |
PickleType | Objeto pickled de Python |
LargeBinary | Dato grande binario |
Para especificar que una columna es clave primaria es necesario agregar el parámetro primary_key
. Esto marca la columna como obligatoria por defecto.
class Persona(db.Model):
personaId = db.Column(db.Integer, primary_key=True)
Para setear una columna para que su valor sea obligatorio se agrega el parámetro nullable
igualado
a False
. En el caso que la columna sea opcional no es necesario agregar este parámetro ya que por defecto su valor es True
.
class Persona(db.Model):
nombre = db.Column(db.String(80), nullable=False)
Función de representación
La función repr
se utiliza dentro del modelo para especificar como se mostrará el objeto cuando se imprime utilizando la función print
de Python. Esta función no es obligatoria y se utiliza mayormente para funciones de debug.
class Persona(db.Model):
nombre = db.Column(db.String(80), nullable=False)
apellido = db.Column(db.String(80), nullable=False)
def __repr__(self):
return '<Persona: %r %r>' % (self.nombre, self.apellido)
Relaciones entre modelos con SQLAlchemy
Flask-SQLAlchemy permite definir y manejar las relaciones entre las tablas. Una vez configuradas estás relaciones, se verán reflejadas en las tablas creadas y podrán ser manejadas fácilmente desde el código de nuestra aplicación.
Para definir las relaciones entre tablas es necesario definir la claves foráneas propias de las tablas
pero también definir en los modelos dichas relaciones. A continuación se listan los casos más comunes de relaciones entre tablas. Comenzando por un diseño de base de datos veremos cómo crear los modelos para generar dichas tablas y relaciones.
Relación uno a uno
Este tipo de relación ocurre cuando un solo registro de una tabla se relaciona con un solo registro de
otra tabla. En el siguiente ejemplo se tiene una tabla persona que guardará los datos personales y
dos tablas, profesor
y alumno
cada una de las cuales está asociada a la tabla persona a través de la
clave foránea personaId
.
Para representar esta relación primero es necesario definir la columna personaId
correspondiente a la clave foránea en la tabla alumno
class Alumno(db.Model):
alumnoId = db.Column(db.Integer, primary_key=True)
personaId = db.Column(db.Integer, db.ForeignKey('persona.personaId'), nullable=False) #Definición de clave foranea
plan = db.Column(db.Integer, nullable=False)
En este caso creamos una columna tipo entero a la que le especificamos su naturaleza de clave foránea mediante la clase ForeingKey
. A esta clase se le pasa por parámetro en nombre de la tabla con la que se relaciona seguida por la clave primaria de dicha tabla.
Repetimos dicho proceso en la tabla profesor.
class Profesor(db.Model):
profesorId = db.Column(db.Integer, primary_key=True)
personaId = db.Column(db.Integer, db.ForeignKey('persona.personaId'), nullable=False) #Definición de clave foranea
numEmpleado = db.Column(db.Integer, nullable=False)
Esto ha definido las columnas correspondientes a la clave foránea entre las tablas pero no ha definido la relación entre ambas tablas para poder relacionar los objetos en nuestro código. Para ello es necesario especificar estas relaciones en todas las tablas.
Para el modelo correspondiente a la tabla persona
es necesario cargar dos relaciones utilizando la
función relationship
, la que especifica la relación con la clase Alumno
y la que especifica la
relación con la clase Profesor
.
class Persona(db.Model):
personaId = db.Column(db.Integer, primary_key=True)
nombre = db.Column(db.String(80), nullable=False)
apellido = db.Column(db.String(80), nullable=False)
# Relaciones
profesor = db.relationship('Profesor',uselist=False,
back_populates="persona",cascade="all, delete-orphan",single_parent=True)
alumno = db.relationship('Alumno',uselist=False,
back_populates="persona",cascade="all, delete-orphan",single_parent=True)
El primer parámetro de la función relationship
representa la clase con la que se está creando en la relación.
El parámetro uselist
es un valor booleano que indica si se quiere que el valor obtenido de la relación
sea una lista. En este caso, como la relación es uno a uno y al cargar una persona se traerá solo un
profesor o un alumno este valor es configurado como False
.
El parámetro back_populates
indica el nombre de la relación en la dirección inversa. Es decir, el
modelo Persona
se relaciona con Profesor
a través del atributo llamado profesor
y a su vez el
modelo Profesor
se relacionará con el modelo Persona
a través de un atributo llamado persona
.
El parámetro cascade
define el comportamiento en cascada de las tablas con respecto a sus
relaciones. Se especifica como una lista de reglas separadas por coma, las reglas disponibles sonsave-update
, merge
, expunge
, delete
, delete-orphan
y refresh-expire
. Una manera de aplicar todas estas reglas de una manera más reducida es colocar la regla all
que incluye a todas las
nombradas anteriormente excepto delete-orphan
que debe colocarse por separado. Una descripción más completa de la funcionalidad cascade
puede encontrarse en la documentación oficial.
Al estar activadas estas reglas si, por ejemplo, se modifica un atributo propio de un objeto Profesor
a través de un objeto Persona
, al guardar esa Persona
se aplicarán estos cambios a la tabla correspondiente a la clase Profesor
.
Si está aplicada la regla delete-orphan
entonces si se elimina una Persona
, el Profesor
asociado será borrado automáticamente.
El parámetro single_parent
es un valor booleano que si está configurado como True
previene que un objeto esté asociado a más de un objeto de la relación. En este ejemplo una Persona
no puede estar asociado a dos registros de la tabla profesor
.
En las clases Profesor
y Alumno
es necesario especificar estas relaciones además de la clave foránea.
class Alumno(db.Model):
alumnoId = db.Column(db.Integer, primary_key=True)
# Clave foranea
personaId = db.Column(db.Integer, db.ForeignKey('persona.personaId'), nullable=False) #Definición de clave foranea
plan = db.Column(db.Integer, nullable=False)
# Relación
persona = db.relationship('Persona', back_populates="alumno", single_parent=True, cascade="all,delete-orphan")
El primer parámetro corresponde nuevamente al nombre de la clase con la que se relaciona. El atributo back_populates
asocia con el atributo de relación alumno
de la clase Persona
.
Lo mismo sucede en la clase Profesor
:
class Profesor(db.Model):
profesorId = db.Column(db.Integer, primary_key=True)
# Clave foranea
personaId = db.Column(db.Integer, db.ForeignKey('persona.personaId'), nullable=False) #Definición de clave foranea
plan = db.Column(db.Integer, nullable=False)
# Relación
persona = db.relationship('Persona', back_populates="profesor", single_parent=True, cascade="all,delete-orphan")
Una vez realizada esta relación será posible acceder a los objetos relacionados dentro del mismo objeto en el caso de obtenerlo en una consulta. En el siguiente caso se accede a los atributos de los objetos profesor
y alumno
desde el objeto persona utilizando el atributo de relación.
persona.nombre
persona.alumno.plan
persona.profesor.numEmpleado
También es posible acceder a los atributos de persona a partir de un objeto alumno o profesor.
alumno.persona.nombre
profesor.persona.nombre
Relación uno a muchos
Este tipo de relación ocurre cuando un solo registro de una tabla se relaciona con muchos registros
de otra tabla. En el siguiente ejemplo se tiene una tabla persona
que guardará los datos personales y una tabla llamada venta
que contendrá el nombre de la venta realizada asociada a la persona que la realizó a través de la clave foránea personaId
.
En este caso una persona
estará asociada a muchas ventas
pero cada venta
está asociada a una sola persona
.
En la clase Persona
es necesario especificar la relación con la clase Venta
class Persona(db.Model):
personaId = db.Column(db.Integer, primary_key=True)
nombre = db.Column(db.String(80), nullable=False)
apellido = db.Column(db.String(80), nullable=False)
# Relación
ventas = db.relationship("Venta", back_populates="persona",cascade="all,
delete-orphan")
El atributo ventas
es colocado en plural ya que obtendrá todas las ventas asociadas al objetoPersona
.
El atributo back_populates
lo asocia a el atributo persona
de la clase Venta
.
Se aplican los atributos de cascade
ya que de esta manera es posible modificar o eliminar ventas
desde el objeto persona.
No es necesario colocar el atributo uselist
ya que en este caso la relación si contendrá una lista de objetos Venta
y este atributo por defecto tiene un valor True
.
En la clase Venta
deben colocarse tanto el atributo correspondiente a la clave foránea y el propio de la relación.
class Venta(db.Model):
ventaId = db.Column(db.Integer, primary_key=True)
# Clave foranea
personaId = db.Column(db.Integer, db.ForeignKey('persona.personaId'),
nullable=False)
producto = db.Column(db.String(80), nullable=False)
# Relación
persona = db.relationship('Persona', back_populates="ventas", uselist=False, single_parent=True)
El atributo uselist
se configura como False
ya que para cada venta
solo debe cargarse un objeto persona
. En este caso no se utiliza el atributo cascade
ya que no debe poderse modificar la persona
a partir del objeto venta
.
Una vez creada la relación se podrá acceder a los datos del vendedor a partir de una venta.
venta.persona.nombre
venta.persona.apellido
También es posible acceder a la lista de ventas a partir de la relación ventas de persona.
for venta in persona.ventas:
print(venta.producto)
Crear tablas a partir de modelos con SQLAlchemy
Una vez que los modelos han sido definidos es posible generar las tablas correspondientes en nuestra base de datos utilizando el siguiente código.
with app.app_context():
db.create_all()
En la primera linea estamos obteniendo el contexto de nuestra aplicación Flask para poder tener acceso a la conexión de la base de datos. La segunda linea solicita a SQLAlchemy que cree todas las tablas a partir de los modelos especificados.
En el caso que hayan surgido modificaciones en los modelos la función create_all
no actualizará dichos cambios en la db. Será necesario en ese caso borrar las tablas ya existentes o utilizar una librería que permita manejar migraciones.
En este artículo hemos visto como crear los modelos y relaciones que representarán a la base de datos para trabajar dichas entidades desde nuestro código. En el siguiente artículo veremos como realizar consultas a la base de datos a través de SQLAlchemy.