/
systemsstrategyy
/
Treker
Обзор
Документация
Войти
/
systemsstrategyy
/
Treker
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
api/modules/spaces/models.py
130 строк
5 KB
SystemsStrategy
Initial import
02 июл 2026, 15:15
02 июл 2026, 15:15
9624ed1
Код
Авторство
О чём код?
"""ORM-модели модуля `spaces`. ⚠️ Драфт-коммит (Kanban Day 1): Foundation Agent утвердит и сгенерирует миграцию через `alembic revision --autogenerate`. Сейчас этих таблиц в БД нет — `SpaceService` с реальной БД пишется в Day 2 после merge миграции. Схема: `app.spaces` / `app.columns` / `app.lanes`. Колонки и FK описаны в `docs/requests/2026-05-13_kanban_schema_request.md`. Конвенции: - `id BigInteger` autoincrement — для внутренних FK. - `uid UUID` (`UUIDMixin`) — для внешних API. - `archived_at` (UX-архив) + `deleted_at` (soft-delete trash) — оба nullable. - Cross-schema FK `app.* → auth.organizations.id` поддерживается Postgres. """ from __future__ import annotations from datetime import datetime from sqlalchemy import ( BigInteger, Boolean, CheckConstraint, DateTime, Float, ForeignKey, Index, Integer, String, Text, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from api.shared.db.base import Base, TimestampMixin, UUIDMixin class Space(UUIDMixin, TimestampMixin, Base): """Пространство — одновременно проект и доска (упрощение vs Kaiten). Иерархия: `Organization → Spaces → Columns → Cards`. Уровня Boards нет. """ __tablename__ = "spaces" __table_args__ = ({"schema": "app"},) id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) organization_id: Mapped[int] = mapped_column( BigInteger, ForeignKey("auth.organizations.id", ondelete="CASCADE"), nullable=False, index=True, ) name: Mapped[str] = mapped_column(String(200), nullable=False) description: Mapped[str | None] = mapped_column(Text, nullable=True) icon_emoji: Mapped[str | None] = mapped_column(String(10), nullable=True) color: Mapped[str | None] = mapped_column(String(20), nullable=True) sort_order: Mapped[float] = mapped_column(Float, nullable=False, server_default="1.0") # Счётчик выданных номеров карточек (сквозная нумерация per-space). # Инкрементится атомарно в CardService.create_card: UPDATE ... RETURNING. card_seq: Mapped[int] = mapped_column(BigInteger, nullable=False, server_default="0") archived_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) columns: Mapped[list[BoardColumn]] = relationship( back_populates="space", cascade="all, delete-orphan", order_by="BoardColumn.sort_order", ) lanes: Mapped[list[Lane]] = relationship( back_populates="space", cascade="all, delete-orphan", order_by="Lane.sort_order", ) class BoardColumn(UUIDMixin, TimestampMixin, Base): """Колонка канбан-доски внутри space. Класс назван `BoardColumn` (не `Column`), чтобы не конфликтовать с `sqlalchemy.Column`. В БД табличное имя — `columns`. """ __tablename__ = "columns" __table_args__ = ( Index("ix_columns_space_id_sort_order", "space_id", "sort_order"), CheckConstraint( "wip_limit IS NULL OR wip_limit > 0", name="ck_columns_wip_limit_positive", ), {"schema": "app"}, ) id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) space_id: Mapped[int] = mapped_column( BigInteger, ForeignKey("app.spaces.id", ondelete="CASCADE"), nullable=False, ) name: Mapped[str] = mapped_column(String(100), nullable=False) sort_order: Mapped[float] = mapped_column(Float, nullable=False) wip_limit: Mapped[int | None] = mapped_column(Integer, nullable=True) is_done_column: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default="false") archived_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) space: Mapped[Space] = relationship(back_populates="columns") class Lane(UUIDMixin, TimestampMixin, Base): """Горизонтальная дорожка (опциональна, по умолчанию space без дорожек).""" __tablename__ = "lanes" __table_args__ = ( Index("ix_lanes_space_id_sort_order", "space_id", "sort_order"), {"schema": "app"}, ) id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) space_id: Mapped[int] = mapped_column( BigInteger, ForeignKey("app.spaces.id", ondelete="CASCADE"), nullable=False, ) name: Mapped[str] = mapped_column(String(100), nullable=False) sort_order: Mapped[float] = mapped_column(Float, nullable=False) archived_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) space: Mapped[Space] = relationship(back_populates="lanes")