Как связать несколько моделей таблиц базы данных PostgreSQL между собой в SQLAlchemy.
Пример связей
Я уже когда-то писал статью в которой затрагивалась тема связей SQLAlchemy. Это статья будет эволюционным продолжением предыдущий.
Возьмём пример из официальной документации:
https://docs.sqlalchemy.org/en/14/orm/basic_relationships.html
__tablename__ = "association_table"
left_id = Column(ForeignKey("left_table.id"), primary_key=True)
right_id = Column(ForeignKey("right_table.id"), primary_key=True)
extra_data = Column(String(50))
child = relationship("Child", back_populates="parents")
parent = relationship("Parent", back_populates="children")
class Parent(Base):
__tablename__ = "left_table"
id = Column(Integer, primary_key=True)
children = relationship("Association", back_populates="parent")
class Child(Base):
__tablename__ = "right_table"
id = Column(Integer, primary_key=True)
parents = relationship("Association", back_populates="child")
В примере выше есть все необходимое. Я лишь обращу внимание на конкретные места, чтобы не было путаницы.
1. Начинаем строить наши связи с таблицы, через которую будет связываться две другие.
2. Обратим внимание на первую модель, на свойство child:
Название переменной в единственном роде и оно же ссылается на класс (модель, таблицу) с названием в единственном роде. Принцип построения этой связи легко запоминается по критерию — единственный род.
3. Второй параметр из примера выше (но для разнообразия возьмем другую связь) back_populates:
Видим, что back_populates ссылается на свойство children в классе Parent.
4. В обоих моделях (таблицах), которые мы связываем есть отсылки к связанной модели через модель ассоциаций:
Т.е. class Parent(Base) мы связываем не напрямую через класс class Child(Base), а через класс class Association(Base) и свойство в нем parent, которое уже ссылается на другой класс class Child(Base).
Важно понимать как строится связи, иначе будут возникать ошибки:
SAWarning: relationship ‘Task.cat_Task’ will copy column Task.id to column categories_Task.Task_id, which conflicts with relationship(s): ‘Category.Task’ (copies Task.id to categories_Task.Task_id), ‘Task.cats’ (copies Task.id to categories_Task.Task_id). If this is not the intention, consider if these relationships should be linked with back_populates, or if viewonly=True should be applied to one or more if they are read-only. For the less common case that foreign key constraints are partially overlapping, the orm.foreign() annotation can be used to isolate the columns that should be written towards. To silence this warning, add the parameter ‘overlaps=»cats,Task»‘ to the ‘Task.cat_Task’ relationship. (Background on this error at: https://sqlalche.me/e/14/qzyx)
Пример доступа к связанным объектам
Рассмотрим следующий пример. Допустим нам нужно получить объект из одной таблицы, но подтянуть связи из других таблиц. Весь код выглядит так:
__tablename__ = 'task_categories'
id = db.Column(db.Integer, primary_key=True, unique=True)
category = db.relationship('Category', back_populates="tasks")
task = db.relationship('Task', back_populates="categories")
@property
def get_task_cats(self):
return {
'task_cat_id': self.id,
}
@property
def get_cats(self):
return self.category.get_cats
class Task(db.Model):
__tablename__ = 'tasks'
id = db.Column(db.Integer, primary_key=True, unique=True)
categories = db.relationship('TaskCategory', back_populates="task")
@property
def serialize(self):
return {
'task_id': self.id,
'cats': [x.get_cats for x in self.categories],
'task_cats': [x.get_task_cats for x in self.categories],
}
class Category(db.Model):
__tablename__ = 'categories'
tasks = db.relationship('TaskCategory', back_populates="category")
@property
def get_cats(self):
return {}
Я говорю об этом участке:
def serialize(self):
return {
'task_cats': [x.get_task_cats for x in self.categories],
'cats': [x.get_cats for x in self.categories],
}
В примере выше мы хотим получить по свойству serialize данные из таблицы class Task(db.Model), но одновременно запрашиваем обработанные данные из двух других связанных таблиц.
В этом случае:
обращение идет к данным class TaskCategory(db.Model).
В другом случае:
обращение идет также к классу class TaskCategory(db.Model), но к функции, которая обращается за данными уже к третей таблице.
Таким образом мы берем данные по связи из третей таблицы через вторую (таблица которая связывает обе).