from sqlalchemy import Column, Integer, String, Float, Boolean, DateTime, ForeignKey, Table from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship from sqlalchemy.sql import func from datetime import datetime Base = declarative_base() # Association table for many-to-many relationship between shopping events and products shopping_event_products = Table( 'shopping_event_products', Base.metadata, Column('id', Integer, primary_key=True, autoincrement=True), # Artificial primary key Column('shopping_event_id', Integer, ForeignKey('shopping_events.id'), nullable=False), Column('product_id', Integer, ForeignKey('products.id'), nullable=False), Column('amount', Float, nullable=False), # Amount of this product bought in this event Column('price', Float, nullable=False) # Price of this product at the time of this shopping event ) # Association table for many-to-many self-referential relationship between related products related_products = Table( 'related_products', Base.metadata, Column('id', Integer, primary_key=True, autoincrement=True), # Artificial primary key Column('product_id', Integer, ForeignKey('products.id'), nullable=False), Column('related_product_id', Integer, ForeignKey('products.id'), nullable=False), Column('relationship_type', String, nullable=True), # Optional: e.g., "size_variant", "brand_variant", "similar" Column('created_at', DateTime(timezone=True), server_default=func.now()) ) class BrandInShop(Base): __tablename__ = "brands_in_shops" id = Column(Integer, primary_key=True, index=True) shop_id = Column(Integer, ForeignKey("shops.id"), nullable=False) brand_id = Column(Integer, ForeignKey("brands.id"), nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # Relationships shop = relationship("Shop", back_populates="brands_in_shop") brand = relationship("Brand", back_populates="shops_with_brand") class Brand(Base): __tablename__ = "brands" id = Column(Integer, primary_key=True, index=True) name = Column(String, nullable=False, index=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # Relationships products = relationship("Product", back_populates="brand") shops_with_brand = relationship("BrandInShop", back_populates="brand") class GroceryCategory(Base): __tablename__ = "grocery_categories" id = Column(Integer, primary_key=True, index=True) name = Column(String, nullable=False, index=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # Relationships products = relationship("Product", back_populates="category") class Product(Base): __tablename__ = "products" id = Column(Integer, primary_key=True, index=True) name = Column(String, nullable=False, index=True) category_id = Column(Integer, ForeignKey("grocery_categories.id"), nullable=False) brand_id = Column(Integer, ForeignKey("brands.id"), nullable=True) organic = Column(Boolean, default=False) weight = Column(Float, nullable=True) # in grams or kg weight_unit = Column(String, default="piece") # "g", "kg", "ml", "l", "piece" created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # Relationships category = relationship("GroceryCategory", back_populates="products") brand = relationship("Brand", back_populates="products") shopping_events = relationship("ShoppingEvent", secondary=shopping_event_products, back_populates="products") # Self-referential many-to-many relationship for related products # We'll use a simpler approach without back_populates to avoid circular references related_products = relationship( "Product", secondary=related_products, primaryjoin="Product.id == related_products.c.product_id", secondaryjoin="Product.id == related_products.c.related_product_id", viewonly=True ) class Shop(Base): __tablename__ = "shops" id = Column(Integer, primary_key=True, index=True) name = Column(String, nullable=False, index=True) city = Column(String, nullable=False) address = Column(String, nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # Relationships shopping_events = relationship("ShoppingEvent", back_populates="shop") brands_in_shop = relationship("BrandInShop", back_populates="shop") class ShoppingEvent(Base): __tablename__ = "shopping_events" id = Column(Integer, primary_key=True, index=True) shop_id = Column(Integer, ForeignKey("shops.id"), nullable=False) date = Column(DateTime(timezone=True), nullable=False, default=datetime.utcnow) total_amount = Column(Float, nullable=True) # Total cost of the shopping event notes = Column(String, nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # Relationships shop = relationship("Shop", back_populates="shopping_events") products = relationship("Product", secondary=shopping_event_products, back_populates="shopping_events")