SQLAlchemy — связи relationship

Admin SQLAlchemy

Как связать несколько моделей таблиц базы данных PostgreSQL между собой в SQLAlchemy.

Пример связей

Я уже когда-то писал статью в которой затрагивалась тема связей SQLAlchemy. Это статья будет эволюционным продолжением предыдущий.

Возьмём пример из официальной документации:
https://docs.sqlalchemy.org/en/14/orm/basic_relationships.html

class Association(Base):
    __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:

child = relationship("Child", back_populates="parents")

Название переменной в единственном роде и оно же ссылается на класс (модель, таблицу) с названием в единственном роде. Принцип построения этой связи легко запоминается по критерию — единственный род.

3. Второй параметр из примера выше (но для разнообразия возьмем другую связь) back_populates:

parent = relationship("Parent", back_populates="children")

Видим, что back_populates ссылается на свойство children в классе Parent.

4. В обоих моделях (таблицах), которые мы связываем есть отсылки к связанной модели через модель ассоциаций:

children = relationship("Association", back_populates="parent")

Т.е. 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)

Пример доступа к связанным объектам

Рассмотрим следующий пример. Допустим нам нужно получить объект из одной таблицы, но подтянуть связи из других таблиц. Весь код выглядит так:

class TaskCategory(db.Model):
    __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 {}

Я говорю об этом участке:

    @property
    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), но одновременно запрашиваем обработанные данные из двух других связанных таблиц.

В этом случае:

'task_cats': [x.get_task_cats for x in self.categories]

обращение идет к данным class TaskCategory(db.Model).

В другом случае:

'cats': [x.get_cats for x in self.categories]

обращение идет также к классу class TaskCategory(db.Model), но к функции, которая обращается за данными уже к третей таблице.

Таким образом мы берем данные по связи из третей таблицы через вторую (таблица которая связывает обе).

У сайта нет цели самоокупаться, поэтому на сайте нет рекламы. Но если вам пригодилась информация, можете лайкнуть страницу, оставить комментарий или отправить мне подарок на чашечку кофе.

Добавить комментарий

Напишите свой комментарий, если вам есть что добавить/поправить/спросить по теме текущей статьи:
"SQLAlchemy — связи relationship"