57 lines
1.7 KiB
Python
57 lines
1.7 KiB
Python
from pydantic import Field, computed_field
|
|
from sqlalchemy import ForeignKey, String, UniqueConstraint
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from src.models._common import BaseModelDb, ExportBaseModel, _hash
|
|
|
|
|
|
class Category(ExportBaseModel):
|
|
name: str = Field(pattern=r"[^:\r\n]+")
|
|
parent: Category | None = None
|
|
|
|
@computed_field # type: ignore[prop-decorator]
|
|
@property
|
|
def hash_id(self) -> str:
|
|
return _hash(self.name, self.parent)
|
|
|
|
@classmethod
|
|
def from_str(cls, s: str) -> Category | None:
|
|
if not s:
|
|
return None
|
|
components = s.split(":")
|
|
name = components[-1]
|
|
rest = ":".join(components[:-1])
|
|
return cls(name=name, parent=cls.from_str(rest))
|
|
|
|
def to_str(self) -> str | None:
|
|
if not self.parent:
|
|
return self.name
|
|
else:
|
|
return f"{self.parent.to_str()}:{self.name}"
|
|
|
|
def __str__(self):
|
|
return self.to_str()
|
|
|
|
def __repr__(self):
|
|
return f'Category.from_str("{self.__str__()}")'
|
|
|
|
|
|
class CategoryDb(BaseModelDb):
|
|
__tablename__ = "category"
|
|
__table_args__ = (
|
|
UniqueConstraint("name", "parent_id", name="uq_category_name_parent"),
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
name: Mapped[str] = mapped_column(String, nullable=False)
|
|
parent_id: Mapped[int | None] = mapped_column(
|
|
ForeignKey("category.id"), nullable=True
|
|
)
|
|
|
|
# Relationships
|
|
parent = relationship(
|
|
"CategoryDb", remote_side="CategoryDb.id", back_populates="children"
|
|
)
|
|
children = relationship("CategoryDb", back_populates="parent")
|
|
budgets = relationship("CategoryBudgetDb", back_populates="category")
|