En esta ocasión aprenderemos a crear una relación uno a muchos utilizando el micro framework Flask y la librería MySQLAlchemy.
Si tu proyecto aún no tiene configurada la conexión a la base de datos, no te preocupes, puedes apoyarte del siguiente vídeo de nuestro curso profesional de python web. Bien, comencemos.
Consideremos los siguientes modelos: Categoría y Tarea.
class Category(db.Model):
__tablename__ = 'categories'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(50))
description = db.Column(db.Text())
tasks = db.relationship('tasks', lazy='dynamic')
created_at = db.Column(db.DateTime, default=datetime.datetime.now())
class Task(db.Model):
__tablename__ = 'tasks'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(50))
description = db.Column(db.Text())
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
category = db.relationship("Category")
created_at = db.Column(db.DateTime, default=datetime.datetime.now())
updated_at = db.Column(db.DateTime)
En este caso podemos observar la relación uno a muchos. Una tarea le pertenece a una categoría y una categoría puede tener muchas tareas. Aquí la clave es utilizar la llave foránea para establecer la relación.
#Modelo Category
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
Con MySQLAlchemy la relación la logramos con las siguientes línea de código.
#Modelo Task
tasks = db.relationship('tasks', lazy='dynamic')
#Modelo Task
category = db.relationship("Category")
Ahora creemos un par de categorías.
>>> category1 = Category(title='Urgente', description='La tarea debe realizarse lo más pronto posible')
>>> category2= Category(title='Pronto', description='La tarea debe realizarse)
>>> db.session.add(category1)
>>> db.session.add(category2)
>>> db.session.commit()
Para crear una tarea utilizando formularios podemos apoyarnos de la librería wtforms. Generamos una nueva clase con los campos necesarios.
from wtforms import StringField, TextAreaField, SelectField
class TaskForm(Form):
title = StringField('Título', [
validators.length(min=4, max=50, message='Título fuera de rango.'),
validators.DataRequired(message='El título es requerido.')
])
description = TextAreaField('Descripción', [
validators.DataRequired(message='La descripción es requerida.')
], render_kw={'rows': 5})
category_id = SelectField('Categoria', choices=[], coerce=int)
En este caso nos interesa el atributo category_id que como podemos observar en un objeto SelectField. Al parámetro choices le asignamos una lista vacía, en esta lista colocaremos todas las opciones disponible para este input. El parámetro coerce le indica a nuestro input que el id será de tipp entero, por default es un string.
Si renderizamos nuestro formulario podremos observar un input select completamente vacío.
Para definir las opciones del input lo haremos mediante tuplas. Las tuplas serán almacenadas en la lista del parametro choices. Cada tupla representará una opción, y almacena dos valores, el valor del input y el texto a mostrar
(value, 'texto')
Con esto en mente modificamos la instancia de nuestro formulario. Como la relación es de uno a muchos, la tarea tendrá una categoría.
form.category_id.choices = [(category.id, category.title) for category in Category.query.all()]
Este paso es recomendado hacerlo en nuestra vista y no en nuestra clase Form, ya que al modelo estar en un archivo o paquete diferente es posible tener un error RuntimeError: No application found.
Por ejemplo,
def edit_task():
form = TaskForm(request.form, obj=task)
form.category_id.choices = [(category.id, category.title) for category in Category.get_all()]
if request.method == 'POST' and form.validate():
....
Una vez hecho esto las opciones serán desplegadas en el select.
Si deseamos persitir la tarea simplemente hacemos un commit a nuestra base de datos.
task = Task(title=form.title.data,
description=form.description.data,
category_id=form.category_id.data)
db.session.add(task)
db.session.commit()
Podemos acceder a la relación utilizando los objetos.
>>> task = Task.query.filter_by(id=1).first()
>>> task.category
<Category 2>
>>> category = Category.query.filter_by(id=1).first()
>>> category.tasks
[<Task 1>]