Creación de modelos con Flask-SQLAlchemy

Publicado por Andrea Navarro en

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.

IntegerNúmero entero
String(size)Cadena de caracteres con un largo máximo
TextCadena de texto unicode más largo
DateTimeFecha y hora expresada como el objeto datetime de Python
FloatNúmero de punto flotantet
BooleanValor booleano
PickleTypeObjeto pickled de Python
LargeBinaryDato 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 son
save-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 objeto
Persona.

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.


¿Preguntas? ¿Comentarios?

Si tenés dudas, o querés dejarnos tus comentarios y consultas, sumate al grupo de Telegram de la comunidad JuncoTIC!
¡Te esperamos!


Andrea Navarro

- Ingeniera en Informática - Docente universitaria - Investigadora