53 lines
1.9 KiB
Python
53 lines
1.9 KiB
Python
|
|
"""Dashboard model — personal per-user dashboards (Phase M2).
|
||
|
|
|
||
|
|
Dashboards are personal (user-owned) layouts of MiniApp instances: tabs,
|
||
|
|
widget placements and per-instance settings, stored as JSONB. Access is
|
||
|
|
owner-only (saved_views precedent) — the active workspace limits only the
|
||
|
|
available widget types (Phase N), never this personal layout.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import uuid
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from sqlalchemy import Boolean, ForeignKey, Index, String, text
|
||
|
|
from sqlalchemy.dialects.postgresql import JSONB
|
||
|
|
from sqlalchemy.dialects.postgresql import UUID as PGUUID
|
||
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
||
|
|
|
||
|
|
from app.core.db import Base, TenantMixin
|
||
|
|
|
||
|
|
|
||
|
|
class Dashboard(Base, TenantMixin):
|
||
|
|
"""Personal dashboard — per-user layout of MiniApp instances."""
|
||
|
|
|
||
|
|
__tablename__ = "dashboards"
|
||
|
|
__table_args__ = (
|
||
|
|
# Partial unique: soft-deleted dashboards free their name (unlike the
|
||
|
|
# saved_views plain constraint, which keeps names occupied forever).
|
||
|
|
Index(
|
||
|
|
"uq_dashboards_tenant_user_name",
|
||
|
|
"tenant_id",
|
||
|
|
"user_id",
|
||
|
|
"name",
|
||
|
|
unique=True,
|
||
|
|
postgresql_where=text("deleted_at IS NULL"),
|
||
|
|
),
|
||
|
|
Index("ix_dashboards_tenant_user", "tenant_id", "user_id"),
|
||
|
|
)
|
||
|
|
|
||
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
||
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
||
|
|
)
|
||
|
|
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
||
|
|
# Layout JSONB (validated by app.schemas.dashboard.DashboardLayout):
|
||
|
|
# {version, tabs: [{id, name, widgets: [{app_id, settings, col, row, spans}]}]}
|
||
|
|
layout: Mapped[dict[str, Any]] = mapped_column(
|
||
|
|
JSONB, nullable=False, default=dict
|
||
|
|
)
|
||
|
|
is_default: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
||
|
|
PGUUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False
|
||
|
|
)
|