from typing import TYPE_CHECKING, Optional from sqlalchemy import ForeignKey, Table, Column from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import Mapped, mapped_column, relationship from models.base import BaseModel from models.mixins import IdMixin, SoftDeleteMixin if TYPE_CHECKING: from models import Module, Deal module_attribute = Table( "module_attribute", BaseModel.metadata, Column("module_id", ForeignKey("modules.id"), primary_key=True), Column("attribute_id", ForeignKey("attributes.id"), primary_key=True), ) class AttributeType(BaseModel, IdMixin, SoftDeleteMixin): __tablename__ = "attribute_types" type: Mapped[str] = mapped_column(unique=True) name: Mapped[str] = mapped_column(unique=True) attributes: Mapped["Attribute"] = relationship( back_populates="type", lazy="noload", ) class Attribute(BaseModel, IdMixin, SoftDeleteMixin): __tablename__ = "attributes" label: Mapped[str] = mapped_column() is_applicable_to_group: Mapped[bool] = mapped_column( default=False, comment="Применять ли изменения атрибута карточки ко всем карточкам в группе", ) is_shown_on_dashboard: Mapped[bool] = mapped_column( default=False, comment="Отображается ли атрибут на дашборде", ) is_highlight_if_expired: Mapped[bool] = mapped_column( default=False, comment="Подсветка атрибута, если Дата/ДатаВремя просрочена", ) is_nullable: Mapped[bool] = mapped_column(default=False) default_value: Mapped[Optional[dict[str, any]]] = mapped_column(JSONB) description: Mapped[str] = mapped_column(default="") is_built_in: Mapped[bool] = mapped_column(default=False) type_id: Mapped[int] = mapped_column(ForeignKey("attribute_types.id")) type: Mapped[AttributeType] = relationship( back_populates="attributes", lazy="joined", ) modules: Mapped[list["Module"]] = relationship( secondary=module_attribute, back_populates="attributes", lazy="noload", ) values: Mapped[list["AttributeValue"]] = relationship( uselist=True, back_populates="attribute", lazy="noload", ) class AttributeValue(BaseModel, IdMixin): __tablename__ = "attribute_values" value: Mapped[Optional[dict[str, any]]] = mapped_column(JSONB) deal_id: Mapped[int] = mapped_column( ForeignKey("deals.id"), primary_key=True, ) deal: Mapped["Deal"] = relationship( back_populates="attributes_values", lazy="noload", ) module_id: Mapped[int] = mapped_column( ForeignKey("modules.id"), primary_key=True, ) module: Mapped["Module"] = relationship( back_populates="attribute_values", lazy="noload", ) attribute_id: Mapped[int] = mapped_column( ForeignKey("attributes.id"), primary_key=True, ) attribute: Mapped[Attribute] = relationship( back_populates="values", lazy="joined", ) def set_value(self, value: Optional[dict | str | bool | int | float]): if value is None: return self.value = {"value": value} def get_value(self) -> Optional[dict | str | bool | int | float]: if self.value is None: return None return self.value["value"] class AttributeLabel(BaseModel): __tablename__ = "attribute_labels" label: Mapped[str] = mapped_column() module_id: Mapped[int] = mapped_column( ForeignKey("modules.id"), primary_key=True, ) module: Mapped["Module"] = relationship( backref="attribute_labels", lazy="noload", ) attribute_id: Mapped[int] = mapped_column( ForeignKey("attributes.id"), primary_key=True, ) attribute: Mapped[Attribute] = relationship( backref="attribute_labels", lazy="joined", )