sqlalchemy многие ко многим на себя
У меня есть таблица товаров Product. В ней я отображаю сырье из которого сделан данный товар. Сейчас возникла необходимость сделать составные товары. То есть некую связку товаров, которая в свою очередь будет как товарная единица. Чтобы понятнее было опишу на примере. Допустим у нас магазин шаров. Мы делаем товары - шарик обычный, шарик с наполнение, шарик с рисунком. Теперь мы хотим сделать набор в котором будет к примеру 2 обычных шарика, 2 с наполнением и один с рисунком. Этот набор тоже считается как один товар, но включает в себя другие товары, назовем их базовые, у которых просчитан состав сырья. У составного товара сырья нет, оно должно считаться по сырью из базовых товаров. Использую postgresql.
Получается отношение многие ко многим в одной таблице. На текущий момент я это реализовал с помощью двух разных моделей Product и ProductSet с моделью-связкой ProductSetRelation. Мне не нравится такое решение, так как чтобы получить все товары (базовые и составные) нужно делать 2 запроса в базу либо использовать outer join, который возвращает все поля для каждой таблицы, но они у меня в большинстве одинаковые. Ниже код как я это реализовал:
@as_declarative()
class Base:
__name__: str
# Generate __table_name__ automatically
@declared_attr
def __tablename__(cls) -> str:
table_name = ''
for letter in cls.__name__:
table_name += f'_{letter.lower()}' if letter.isupper() else letter
return table_name[1:]
id = Column(UUID(as_uuid=True), primary_key=True, nullable=False, default=uuid4)
is_active = Column(Boolean, nullable=False, default=True)
class Product(Base):
shop_id = Column(UUID(as_uuid=True), ForeignKey('shop.id'), nullable=False)
category_id = Column(UUID(as_uuid=True), ForeignKey('product_category.id'), nullable=False)
product_category = relationship("ProductCategory", back_populates="products")
raw = relationship('ProductRawRelation', cascade="all, delete-orphan")
name = Column(String(255), nullable=False, comment='Название')
description = Column(String(255), comment='Описание')
image = Column(String(255), comment='Изображение')
price = Column(Float, default=0, comment='Цена')
old_price = Column(Float, comment='Старая цена')
show_on_store = Column(Boolean, nullable=False, default=True, comment='Отображать на витрине')
class ProductSetRelation(Base):
product_id = Column(UUID(as_uuid=True), ForeignKey('product.id'))
product_set_id = Column(UUID(as_uuid=True), ForeignKey('product_set.id'))
quantity = Column(Float, default=0)
class ProductSet(Base):
shop_id = Column(UUID(as_uuid=True), ForeignKey('shop.id'), nullable=False)
category_id = Column(UUID(as_uuid=True), ForeignKey('product_category.id'), nullable=False)
product_category = relationship("ProductCategory", back_populates="product_sets")
products = relationship('ProductSetRelation', cascade="all, delete-orphan")
name = Column(String(255), nullable=False, comment='Название')
description = Column(String(255), comment='Описание')
image = Column(String(255), comment='Изображение')
price = Column(Float, default=0, comment='Цена')
old_price = Column(Float, comment='Старая цена')
show_on_store = Column(Boolean, nullable=False, default=True, comment='Отображать на витрине')
Можно как то это сделать с использованием только одной модели Product и модели-связки для связи многие ко многим?
У меня пока мысль добавить в Product ссылку на ProductSet.id как foreign key:
class Product(Base):
shop_id = Column(UUID(as_uuid=True), ForeignKey('shop.id'), nullable=False)
category_id = Column(UUID(as_uuid=True), ForeignKey('product_category.id'), nullable=False)
product_category = relationship("ProductCategory", back_populates="products")
raw = relationship('ProductRawRelation', cascade="all, delete-orphan")
name = Column(String(255), nullable=False, comment='Название')
description = Column(String(255), comment='Описание')
image = Column(String(255), comment='Изображение')
price = Column(Float, default=0, comment='Цена')
old_price = Column(Float, comment='Старая цена')
show_on_store = Column(Boolean, nullable=False, default=True, comment='Отображать на витрине')
product_set_id = Column(UUID(as_uuid=True), ForeignKey('product_set.id'))
class ProductSetRelation(Base):
product_id = Column(UUID(as_uuid=True), ForeignKey('product.id'))
product_set_id = Column(UUID(as_uuid=True), ForeignKey('product_set.id'))
quantity = Column(Float, default=0)
class ProductSet(Base):
products = relationship('ProductSetRelation', cascade="all, delete-orphan")
Ответы (3 шт):
В итоге избавился от одной таблицы, сделал так:
class Product(Base):
shop_id = Column(UUID(as_uuid=True), ForeignKey('shop.id'), nullable=False)
category_id = Column(UUID(as_uuid=True), ForeignKey('product_category.id'), nullable=False)
product_category = relationship("ProductCategory", back_populates="products")
raw = relationship('ProductRawRelation', cascade="all, delete-orphan")
base = Column(Boolean, nullable=False, default=True, comment='Базовый товар')
name = Column(String(255), nullable=False, comment='Название')
description = Column(String(255), comment='Описание')
image = Column(String(255), comment='Изображение')
price = Column(Float, default=0, comment='Цена')
old_price = Column(Float, comment='Старая цена')
show_on_store = Column(Boolean, nullable=False, default=True, comment='Отображать на витрине')
product_set_id = Column(UUID(as_uuid=True), index=True, unique=True, nullable=True)
products = relationship('ProductSetRelation',
primaryjoin="Product.product_set_id==ProductSetRelation.product_set_id",
cascade="all, delete-orphan")
class ProductSetRelation(Base):
product_id = Column(UUID(as_uuid=True), ForeignKey('product.id'))
product_set_id = Column(UUID(as_uuid=True), ForeignKey('product.product_set_id'))
quantity = Column(Float, default=0)
Немножко не в тему, сейчас заметил у вас
price = Column(Float, default=0, comment='Цена')
не надо так, чревато эффектами представления чисел с плавающей запятой. Лучше
price = Column(Numeric(precision=40, scale=2), default=0, comment='Цена')
Ну и вашу схему я вижу скорее как-то так
@as_declarative()
class ProductRaw:
__name__ = 'product_raw'
id = Column(UUID(as_uuid=True), primary_key=True, nullable=False, default=uuid4)
product_id = Column(UUID(as_uuid=True), ForeignKey('product.id'))
name = Column(String(255), nullable=False, comment='Название')
@as_declarative()
class Part:
__name__ = 'part'
product_id = Column(UUID(as_uuid=True), ForeignKey('product.id'))
parent_id = Column(UUID(as_uuid=True), ForeignKey('product.id'))
quantity = Column(Numeric(precision=40, scale=3), default=0)
@as_declarative()
class Product:
__name__ = 'product'
id = Column(UUID(as_uuid=True), primary_key=True, nullable=False, default=uuid4)
name = Column(String(255), nullable=False, comment='Название')
raw = relationship('ProductRawRelation', cascade="all, delete-orphan")
price = Column(Numeric(precision=40, scale=2), default=0, comment='Цена')
parts = relationship('Part',
primaryjoin="Product.id==Part.parent_id",
cascade="all, delete-orphan")