feat(M2): Persönliche Dashboards — Tabelle, CRUD, Lazy-Seed, RLS (#360)
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
- dashboards-Tabelle (Layout JSONB, Tabs, is_default, partial unique name index) - 6 CRUD-Endpoints /api/v1/dashboards, Owner-only (saved_views-Präzedenz), Audit - Lazy Default-Seed aus MiniApp-Registry (permission-gefiltert, 12-Spalten-Flow) - CORE_PERMISSIONS dashboard:read/write (fixt Phantom-Permission in dashboard.py) - Migration 0144: RLS crm_api+crm_worker + konvergenter Fix der 3 Phase-L-Policies - Tests: test_dashboards_backend.py 23/23 (TDD rot->grün); Regression 162/163
This commit is contained in:
@@ -17,12 +17,13 @@ from app.models.contact_folder import ContactFolder
|
||||
from app.models.contact_merge import ContactMergeHistory
|
||||
from app.models.currency import Currency
|
||||
from app.models.custom_field_definition import CustomFieldDefinition
|
||||
from app.models.dashboard import Dashboard
|
||||
from app.models.entity_history import EntityHistory
|
||||
from app.models.entity_permission import EntityPermission
|
||||
from app.models.entity_policy import EntityPolicy
|
||||
from app.models.group import Group, UserGroup
|
||||
from app.models.notification import Notification, NotificationPreference, NotificationType
|
||||
from app.models.outbox import EventOutbox, OutboxDelivery
|
||||
from app.models.outbox import EventOutbox, OutboxDelivery # noqa: F401
|
||||
from app.models.owned_mixin import OwnedMixin
|
||||
from app.models.permission_delegation import PermissionDelegation
|
||||
from app.models.permission_template import PermissionTemplate
|
||||
@@ -80,6 +81,7 @@ __all__ = [
|
||||
"WorkflowInstance",
|
||||
"WorkflowStepHistory",
|
||||
"SavedView",
|
||||
"Dashboard",
|
||||
]
|
||||
from app.models.entity_attachment import EntityAttachment # noqa: F401
|
||||
from app.models.workspace import ( # noqa: F401
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
"""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
|
||||
)
|
||||
Reference in New Issue
Block a user