"""Сущность «документ»: описания задач и заметки проектов переносятся в неё,
вложения привязываются к документу вместо задачи (см. docs/TZ.md, модель данных).
Revision ID: d9e8f7c6b5a4
Revises: c8f7e6d5b4a3
Create Date: 2026-09-20
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "d9e8f7c6b5a4"
down_revision: str | Sequence[str] | None = "c8f7e6d5b4a3"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.create_table(
"documents",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("owner_type", sa.String(length=20), nullable=False),
sa.Column("owner_id", sa.Integer(), nullable=False),
sa.Column("body", sa.Text(), nullable=False, server_default=""),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.UniqueConstraint("owner_type", "owner_id"),
)
conn = op.get_bind()
# Документ заводится каждой задаче и проекту (включая пустой текст) —
# упрощает код: document всегда есть.
conn.execute(
sa.text(
"INSERT INTO documents (owner_type, owner_id, body, created_at, updated_at) "
"SELECT 'task', id, description, created_at, created_at FROM tasks"
)
)
conn.execute(
sa.text(
"INSERT INTO documents (owner_type, owner_id, body, created_at, updated_at) "
"SELECT 'project', id, note, created_at, created_at FROM projects"
)
)
op.add_column("attachments", sa.Column("document_id", sa.Integer(), nullable=True))
conn.execute(
sa.text(
"UPDATE attachments SET document_id = ("
"SELECT id FROM documents WHERE owner_type = 'task' AND owner_id = task_id)"
)
)
op.alter_column("attachments", "document_id", nullable=False)
op.create_foreign_key(
"fk_attachments_document_id_documents",
"attachments",
"documents",
["document_id"],
["id"],
ondelete="CASCADE",
)
op.drop_column("attachments", "task_id")
op.drop_column("tasks", "description")
op.drop_column("projects", "note")
def downgrade() -> None:
op.add_column("tasks", sa.Column("description", sa.Text(), nullable=False, server_default=""))
op.add_column("projects", sa.Column("note", sa.Text(), nullable=False, server_default=""))
conn = op.get_bind()
conn.execute(
sa.text(
"UPDATE tasks SET description = COALESCE(("
"SELECT body FROM documents WHERE owner_type = 'task' AND owner_id = tasks.id), '')"
)
)
conn.execute(
sa.text(
"UPDATE projects SET note = COALESCE(("
"SELECT body FROM documents WHERE owner_type = 'project'"
" AND owner_id = projects.id), '')"
)
)
op.add_column("attachments", sa.Column("task_id", sa.Integer(), nullable=True))
conn.execute(
sa.text(
"UPDATE attachments SET task_id = ("
"SELECT owner_id FROM documents WHERE documents.id = attachments.document_id)"
)
)
op.drop_constraint(
"fk_attachments_document_id_documents", "attachments", type_="foreignkey"
)
op.alter_column("attachments", "task_id", nullable=False)
op.create_foreign_key(
"fk_attachments_task_id_tasks", "attachments", "tasks", ["task_id"], ["id"],
ondelete="CASCADE",
)
op.drop_column("attachments", "document_id")
op.drop_table("documents")