kanban-app/backend/app/models/epic.py

80 lines
2.7 KiB
Python

from datetime import UTC, datetime
from sqlalchemy.dialects.postgresql import JSONB
from app import db
class Epic(db.Model):
"""Epic model for tracking large features across multiple cards"""
__tablename__ = "epics"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(200), nullable=False, index=True)
description = db.Column(db.Text)
content = db.Column(JSONB) # Rich text content (Slate.js JSON)
color = db.Column(db.String(7)) # Hex color for epic badge
closed = db.Column(db.Boolean, default=False, index=True)
pos = db.Column(db.Float) # Position for sorting in epic list
depth_limit = db.Column(db.Integer, default=5) # Max nesting depth
# Foreign keys
board_id = db.Column(
db.Integer,
db.ForeignKey("boards.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
parent_epic_id = db.Column(
db.Integer, db.ForeignKey("epics.id", ondelete="SET NULL")
)
# Timestamps
date_last_activity = db.Column(db.DateTime)
created_at = db.Column(db.DateTime, default=lambda: datetime.now(UTC))
updated_at = db.Column(
db.DateTime,
default=lambda: datetime.now(UTC),
onupdate=lambda: datetime.now(UTC),
)
# JSON fields for metrics
metrics = db.Column(JSONB) # {"card_count": 10}
# Relationships
board = db.relationship("Board", backref="epics")
parent_epic = db.relationship("Epic", remote_side=[id], backref="child_epics")
cards = db.relationship("Card", backref="epic", cascade="all, delete-orphan")
attachments = db.relationship(
"FileAttachment",
foreign_keys="FileAttachment.attachable_id",
primaryjoin="""and_(FileAttachment.attachable_id == Epic.id,
FileAttachment.attachable_type == 'Epic')""",
cascade="all, delete-orphan",
lazy="dynamic",
)
def to_dict(self):
"""Convert epic to dictionary"""
return {
"id": self.id,
"name": self.name,
"description": self.description,
"content": self.content,
"color": self.color,
"closed": self.closed,
"pos": self.pos,
"depth_limit": self.depth_limit,
"board_id": self.board_id,
"parent_epic_id": self.parent_epic_id,
"date_last_activity": self.date_last_activity.isoformat()
if self.date_last_activity
else None,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
"metrics": self.metrics or {"card_count": 0},
}
def __repr__(self):
return f"<Epic {self.name}>"