flask_template_codex/app/tasks/models.py
2026-08-08 05:37:41 +02:00

79 lines
2.7 KiB
Python

from datetime import datetime, timezone
from typing import Any
from sqlalchemy import CheckConstraint, ForeignKey, Index, JSON, String, Text
from sqlalchemy.ext.mutable import MutableList
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.extensions import db
TASK_STATUSES = ("not_started", "in_progress", "done")
TASK_PRIORITIES = ("urgent", "important", "normal")
class Task(db.Model):
__table_args__ = (
CheckConstraint(
"status IN ('not_started', 'in_progress', 'done')",
name="ck_task_status",
),
CheckConstraint(
"priority IN ('urgent', 'important', 'normal')",
name="ck_task_priority",
),
Index(
"ix_task_owner_visibility_status_due",
"user_id",
"cleared_at",
"status",
"due_date",
),
)
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String(200))
description: Mapped[str | None] = mapped_column(Text)
status: Mapped[str] = mapped_column(String(20), default="not_started")
priority: Mapped[str] = mapped_column(String(20), default="normal")
created_at: Mapped[datetime] = mapped_column(
default=lambda: datetime.now(timezone.utc)
)
due_date: Mapped[datetime | None]
completed_at: Mapped[datetime | None]
cleared_at: Mapped[datetime | None]
clear_batch_id: Mapped[str | None] = mapped_column(String(36), index=True)
user_id: Mapped[int] = mapped_column(
ForeignKey("user.id", ondelete="CASCADE"), index=True
)
# Stored now so Phase 3 can add timers and chunking without reshaping CRUD.
timer_seconds: Mapped[int] = mapped_column(default=0)
context: Mapped[str | None] = mapped_column(String(100))
chunk_sessions: Mapped[list[Any]] = mapped_column(
MutableList.as_mutable(JSON), default=list
)
subtasks: Mapped[list["Subtask"]] = relationship(
back_populates="task",
cascade="all, delete-orphan",
order_by="Subtask.id",
)
@property
def progress_percentage(self) -> int:
if not self.subtasks:
return 100 if self.status == "done" else 0
completed = sum(subtask.completed for subtask in self.subtasks)
return round(completed / len(self.subtasks) * 100)
class Subtask(db.Model):
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String(100))
completed: Mapped[bool] = mapped_column(default=False)
task_id: Mapped[int] = mapped_column(
ForeignKey("task.id", ondelete="CASCADE"), index=True
)
task: Mapped[Task] = relationship(back_populates="subtasks")