Files
govoplan-views/src/govoplan_views/backend/manifest.py
T
zemion 9a53898388
Module Package Release / publish-packages (push) Successful in 12s
fix(webui): bind view publication to help
2026-08-24 11:36:48 +02:00

606 lines
28 KiB
Python

from __future__ import annotations
from govoplan_core.core.modules import with_documentation_structured_translations
from govoplan_views.backend.german_structured_documentation import GERMAN_STRUCTURED_TRANSLATIONS
from pathlib import Path
from govoplan_core.core.access import (
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
)
from govoplan_core.core.module_guards import (
drop_table_retirement_provider,
persistent_table_uninstall_guard,
)
from govoplan_core.core.modules import (
CapabilityDocumentation,
DocumentationCondition,
DocumentationLink,
DocumentationTopic,
FrontendModule,
MigrationSpec,
ModuleContext,
ModuleInterfaceProvider,
ModuleManifest,
PermissionDefinition,
RoleTemplate,
)
from govoplan_core.core.provider_governance import declared_module_architecture
from govoplan_core.core.policy import CAPABILITY_POLICY_IMPACT_SUBJECT_PREFIX
from govoplan_core.core.views import CAPABILITY_VIEWS_RESOLVER, ViewSurface
from govoplan_core.db.base import Base
from govoplan_views.backend.db import models as view_models
from govoplan_views.backend.dsar_provider import (
VIEWS_DSAR_CAPABILITY,
ViewsDsarProvider,
)
MODULE_ID = "views"
MODULE_NAME = "Views"
MODULE_VERSION = "0.1.21"
DEFINITION_READ_SCOPE = "views:definition:read"
DEFINITION_WRITE_SCOPE = "views:definition:write"
GROUP_DEFINITION_READ_SCOPE = "views:group_definition:read"
GROUP_DEFINITION_WRITE_SCOPE = "views:group_definition:write"
PERSONAL_DEFINITION_READ_SCOPE = "views:personal_definition:read"
PERSONAL_DEFINITION_WRITE_SCOPE = "views:personal_definition:write"
ASSIGNMENT_READ_SCOPE = "views:assignment:read"
ASSIGNMENT_WRITE_SCOPE = "views:assignment:write"
SELECTION_READ_SCOPE = "views:selection:read"
SELECTION_WRITE_SCOPE = "views:selection:write"
SYSTEM_DEFINITION_READ_SCOPE = "views:system_definition:read"
SYSTEM_DEFINITION_WRITE_SCOPE = "views:system_definition:write"
SYSTEM_ASSIGNMENT_READ_SCOPE = "views:system_assignment:read"
SYSTEM_ASSIGNMENT_WRITE_SCOPE = "views:system_assignment:write"
def _permission(
scope: str,
label: str,
description: str,
*,
level: str = "tenant",
) -> PermissionDefinition:
module_id, resource, action = scope.split(":", 2)
return PermissionDefinition(
scope=scope,
label=label,
description=description,
category="Views",
level=level,
module_id=module_id,
resource=resource,
action=action,
)
PERMISSIONS = (
_permission(
DEFINITION_READ_SCOPE,
"View tenant Views",
"Read tenant and inherited View definitions and immutable revisions.",
),
_permission(
DEFINITION_WRITE_SCOPE,
"Manage tenant Views",
"Create, revise, publish, and archive tenant View definitions.",
),
_permission(
GROUP_DEFINITION_READ_SCOPE,
"View group Views",
"Read View definitions owned by groups the account belongs to.",
),
_permission(
GROUP_DEFINITION_WRITE_SCOPE,
"Manage group Views",
"Create, revise, publish, and archive Views owned by the account's groups.",
),
_permission(
PERSONAL_DEFINITION_READ_SCOPE,
"View personal Views",
"Read View definitions owned by the current account.",
),
_permission(
PERSONAL_DEFINITION_WRITE_SCOPE,
"Manage personal Views",
"Create, revise, publish, and archive Views owned by the current account.",
),
_permission(
ASSIGNMENT_READ_SCOPE,
"View tenant View assignments",
"Read tenant, group, and user View assignments.",
),
_permission(
ASSIGNMENT_WRITE_SCOPE,
"Manage tenant View assignments",
"Assign available, default, and required Views within a tenant.",
),
_permission(
SELECTION_READ_SCOPE,
"View effective View",
"Read the effective View projection for the current account.",
),
_permission(
SELECTION_WRITE_SCOPE,
"Select available Views",
"Select or leave an available View unless an administrator requires it.",
),
_permission(
SYSTEM_DEFINITION_READ_SCOPE,
"View system Views",
"Read system-wide View definitions and immutable revisions.",
level="system",
),
_permission(
SYSTEM_DEFINITION_WRITE_SCOPE,
"Manage system Views",
"Create, revise, publish, and archive system-wide View definitions.",
level="system",
),
_permission(
SYSTEM_ASSIGNMENT_READ_SCOPE,
"View system View assignments",
"Read system-wide View assignments.",
level="system",
),
_permission(
SYSTEM_ASSIGNMENT_WRITE_SCOPE,
"Manage system View assignments",
"Assign available, default, and required Views system-wide.",
level="system",
),
)
ROLE_TEMPLATES = (
RoleTemplate(
slug="view_manager",
name="View manager",
description="Design tenant Views and manage their assignments.",
permissions=(
DEFINITION_READ_SCOPE,
DEFINITION_WRITE_SCOPE,
GROUP_DEFINITION_READ_SCOPE,
GROUP_DEFINITION_WRITE_SCOPE,
PERSONAL_DEFINITION_READ_SCOPE,
PERSONAL_DEFINITION_WRITE_SCOPE,
ASSIGNMENT_READ_SCOPE,
ASSIGNMENT_WRITE_SCOPE,
SELECTION_READ_SCOPE,
SELECTION_WRITE_SCOPE,
),
),
RoleTemplate(
slug="view_designer",
name="View designer",
description="Design personal Views and reusable Views for assigned groups.",
permissions=(
GROUP_DEFINITION_READ_SCOPE,
GROUP_DEFINITION_WRITE_SCOPE,
PERSONAL_DEFINITION_READ_SCOPE,
PERSONAL_DEFINITION_WRITE_SCOPE,
SELECTION_READ_SCOPE,
SELECTION_WRITE_SCOPE,
),
),
RoleTemplate(
slug="view_user",
name="View user",
description="Use available Views and design Views for the current account.",
permissions=(
PERSONAL_DEFINITION_READ_SCOPE,
PERSONAL_DEFINITION_WRITE_SCOPE,
SELECTION_READ_SCOPE,
SELECTION_WRITE_SCOPE,
),
default_authenticated=True,
),
)
def _router(context: ModuleContext):
from govoplan_views.backend.runtime import configure_runtime
configure_runtime(registry=context.registry)
from govoplan_views.backend.router import router
return router
def _resolver(context: ModuleContext):
from govoplan_views.backend.capabilities import resolver_capability
from govoplan_views.backend.runtime import configure_runtime
configure_runtime(registry=context.registry)
return resolver_capability(context)
def _policy_impact_subjects(context: ModuleContext):
from govoplan_views.backend.impact_subjects import (
ViewsPolicyImpactSubjectProvider,
)
return ViewsPolicyImpactSubjectProvider(context.registry)
def _dsar_provider(_context: ModuleContext) -> ViewsDsarProvider:
return ViewsDsarProvider()
manifest = ModuleManifest(
id=MODULE_ID,
name=MODULE_NAME,
version=MODULE_VERSION,
optional_dependencies=("access", "admin", "policy", "workflow_engine"),
required_capabilities=(
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
),
provides_interfaces=(
ModuleInterfaceProvider(name="views.surface_contract", version="1.0.0"),
ModuleInterfaceProvider(name="views.resolver", version="0.1.0"),
ModuleInterfaceProvider(name=VIEWS_DSAR_CAPABILITY, version="0.1.0"),
),
permissions=PERMISSIONS,
role_templates=ROLE_TEMPLATES,
frontend=FrontendModule(
module_id=MODULE_ID,
package_name="@govoplan/views-webui",
view_surfaces=(
ViewSurface(
id="views.selector",
module_id=MODULE_ID,
kind="selector",
label="View selector",
description="Always-available selector for leaving optional Views.",
order=1,
required=True,
),
ViewSurface(
id="views.admin.system",
module_id=MODULE_ID,
kind="section",
label="System Views administration",
description="Edit system-wide Views and assignments.",
order=20,
),
ViewSurface(
id="views.admin.tenant",
module_id=MODULE_ID,
kind="section",
label="Tenant Views administration",
description="Edit tenant, group, and user Views and assignments.",
order=30,
),
ViewSurface(
id="views.settings.personal",
module_id=MODULE_ID,
kind="section",
label="Personal and group Views",
description="Design reusable Views owned by the account or its groups.",
order=40,
),
),
),
route_factory=_router,
migration_spec=MigrationSpec(
module_id=MODULE_ID,
metadata=Base.metadata,
script_location=str(Path(__file__).with_name("migrations") / "versions"),
retirement_supported=True,
retirement_provider=drop_table_retirement_provider(
view_models.ViewPreference,
view_models.ViewAssignment,
view_models.ViewRevision,
view_models.ViewDefinition,
label="Views",
),
retirement_notes=(
"Destructive retirement removes View definitions, revisions, "
"assignments, and user selections after a database snapshot."
),
),
uninstall_guard_providers=(
persistent_table_uninstall_guard(
view_models.ViewDefinition,
view_models.ViewAssignment,
label="Views",
),
),
capability_factories={
CAPABILITY_VIEWS_RESOLVER: _resolver,
f"{CAPABILITY_POLICY_IMPACT_SUBJECT_PREFIX}views": _policy_impact_subjects,
VIEWS_DSAR_CAPABILITY: _dsar_provider,
},
capability_documentation={
VIEWS_DSAR_CAPABILITY: CapabilityDocumentation(
label="Views data-subject request provider",
summary=(
"Exports and deletes account-owned View preferences while "
"retaining minimized institutional configuration attribution."
),
contract_version="0.1.0",
),
},
documentation=(
DocumentationTopic(
id="views.data-subject-requests",
title="Views data-subject requests",
summary=(
"Export or delete personal Views, assignments, and selections "
"without changing institutional projections or domain data."
),
body=(
"Views correlates one exact account in the active tenant and can "
"narrow the request to a definition, assignment, or preference. "
"The access package includes bounded personal definition and "
"revision presentation, user assignments, and the active-View "
"selection. Arbitrary assignment metadata is excluded and View "
"surfaces are identifiers only; the provider never traverses them "
"into feature data. Tenant and group configuration authored or "
"updated by the subject contributes minimized attribution only and "
"is retained as institutional accountability evidence. System-wide "
"Views are outside tenant-scoped requests. Erasure can delete an "
"exact personal selection, user assignment, or user-owned definition "
"after timestamp, revision, ownership, and dependent-record checks. "
"A personal definition that affects another account is sent to "
"manual review. Deleting a selection or assignment never deletes "
"the referenced institutional View. Repeated execution is unchanged."
),
layer="configured",
documentation_types=("admin", "user"),
audience=("user", "tenant_admin", "operator", "auditor"),
translations={
"de": {
"title": "Datenschutzanfragen für Ansichten",
"summary": "Persönliche Ansichten, Zuordnungen und Auswahlen exportieren oder löschen, ohne institutionelle Projektionen oder Fachdaten zu verändern.",
"body": (
"Views ordnet genau ein Konto im aktiven Mandanten zu und kann die Anfrage auf eine Definition, Zuordnung oder Einstellung begrenzen. Das Auskunftspaket enthält die begrenzte Darstellung persönlicher Definitionen und Revisionen, Benutzerzuordnungen sowie die aktive Ansichtsauswahl. "
"Beliebige Zuordnungsmetadaten sind ausgeschlossen; Oberflächen werden nur als Kennungen ausgegeben und niemals bis in Fachdaten verfolgt. Von der betroffenen Person erstellte oder aktualisierte Mandanten- und Gruppenkonfiguration trägt nur minimierte Zuordnungsdaten bei, die als institutioneller Verantwortungsnachweis erhalten bleiben. Systemweite Ansichten liegen außerhalb mandantenbezogener Anfragen. "
"Eine Löschung kann nach Prüfung von Zeitstempel, Revision, Eigentum und abhängigen Datensätzen eine genaue persönliche Auswahl, Benutzerzuordnung oder benutzereigene Definition entfernen. Eine persönliche Definition, von der ein anderes Konto abhängt, wird zur manuellen Prüfung weitergeleitet. Das Löschen einer Auswahl oder Zuordnung löscht niemals die referenzierte institutionelle Ansicht; wiederholte Ausführung verändert nichts."
),
}
},
related_modules=("core", "access", "quick_access"),
metadata={
"help_contexts": [
"views.selector",
"views.settings.personal",
"views.admin.tenant",
"privacy.data-subject-requests",
],
"consequence_classes": {
"delete_selection": (
"Removes the account selection; effective defaults apply again."
),
"delete_assignment": (
"Removes only the personal assignment, not its View definition."
),
"delete_personal_definition": (
"Removes the personal definition and revisions only when no "
"other account depends on it."
),
"retain_attribution": (
"Preserves minimized tenant/group configuration accountability."
),
},
},
),
DocumentationTopic(
id="views.interface-projections",
title="Task-focused Views",
summary=(
"Reduce the visible interface to the modules and functions needed "
"for a task without changing authorization."
),
body=(
"Views are versioned presentation projections. Modules announce "
"their selectable surfaces through the platform contract. System "
"and tenant administrators can publish Views and make them "
"available, default, or required at system, tenant, group, and "
"user scope. Required Views retain administration escape surfaces "
"so they can always be inspected and changed. The titlebar eye "
"opens the selector and is accented while a specialized View is "
"active. Hidden functions remain protected by their normal "
"permission checks. A revision may recommend Quick Access tools or "
"focus the rail to a task-specific subset. These fields affect presentation "
"only: unavailable, context-incompatible, or unauthorized tools stay absent, "
"and an unusable focus falls back to the normal effective rail with an explanation. "
"While a focus is active, All available tools temporarily restores that same permission-derived "
"rail without changing the View or saving an override. Workflow uses "
"the same behavior by resolving the exact immutable View revision."
" When Policy is enabled, Views contributes a bounded catalogue of "
"View definitions, actions, and registered surfaces to policy-impact "
"previews. The provider is tenant-filtered, honors an explicit limit, "
"and never grants Policy access to View implementation internals."
),
layer="available",
documentation_types=("admin", "user"),
audience=("administrator", "power_user", "workflow_designer"),
related_modules=("access", "admin", "policy", "workflow_engine"),
conditions=(
DocumentationCondition(
required_modules=("views", "access"),
any_scopes=(
SELECTION_READ_SCOPE,
DEFINITION_READ_SCOPE,
PERSONAL_DEFINITION_READ_SCOPE,
),
),
),
translations={
"de": {
"title": "Aufgabenbezogene Ansichten",
"summary": "Die sichtbare Oberfläche auf die für eine Aufgabe benötigten Module und Funktionen begrenzen, ohne Berechtigungen zu ändern.",
"body": (
"Ansichten sind versionierte Darstellungsprojektionen. Module melden ihre wählbaren Oberflächen über den Plattformvertrag. System- und Mandantenadministrationen können Ansichten veröffentlichen und sie auf System-, Mandanten-, Gruppen- und Benutzerebene als verfügbar, voreingestellt oder verpflichtend zuordnen. Verpflichtende Ansichten behalten Auswahl- und Administrationsauswege, damit sie stets geprüft und geändert werden können. "
"Das Augensymbol in der Titelleiste öffnet die Auswahl und wird hervorgehoben, solange eine spezialisierte Ansicht aktiv ist. Ausgeblendete Funktionen bleiben durch ihre normalen Berechtigungsprüfungen geschützt. Eine Revision darf Schnellzugriffswerkzeuge empfehlen oder die Leiste auf eine aufgabenbezogene Teilmenge fokussieren. Das ändert nur die Darstellung: nicht verfügbare, unpassende oder unberechtigte Werkzeuge bleiben verborgen; ein unbrauchbarer Fokus fällt mit Erklärung auf die normale wirksame Leiste zurück. "
"Alle verfügbaren Werkzeuge stellt vorübergehend dieselbe berechtigungsabgeleitete Leiste wieder her, ohne die Ansicht zu ändern oder eine Umgehung zu speichern. Workflow erhält dasselbe Verhalten durch Auflösung der genauen unveränderlichen Ansichtsrevision. Ist Policy aktiviert, liefert Views einen mandantenbezogenen und begrenzten Katalog von Definitionen, Aktionen und registrierten Oberflächen für Wirkungsvorschauen, ohne Zugriff auf Implementierungsdetails zu erteilen."
),
}
},
links=(
DocumentationLink(
label="Views administration",
href="/admin?section=system-views",
kind="runtime",
),
DocumentationLink(
label="Views API",
href="/api/v1/views/definitions",
kind="api",
),
),
metadata={
"kind": "workflow",
"help_contexts": [
"views.selector",
"views.admin.system",
"views.admin.tenant",
"views.settings.personal",
"views.admin.blocked",
],
"steps": [
"Open the View selector and review the effective View and its source.",
"Choose an available View or return to the permission-derived default.",
"Use the administration escape when a required View must be inspected or changed.",
],
},
order=18,
),
DocumentationTopic(
id="views.reference.fields-and-consequences",
title="View fields, assignments, and consequences",
summary=(
"Understand immutable revisions, assignment precedence, required "
"View safeguards, and the difference between visibility and access."
),
body=(
"A View definition owns immutable revisions of visible surface IDs and presentation metadata. "
"Publishing makes the latest revision assignable. Available assignments "
"let users opt in, defaults apply until changed, and required assignments "
"cannot be left. User and group assignments take precedence over tenant "
"and system assignments. Pinning preserves one published revision; an "
"unpinned assignment follows later publications. Required Views must keep "
"the selector and administration escape surfaces. Hiding a surface never "
"grants or revokes authorization. The surface catalogue is constrained by "
"the active tenant's module entitlement, so a View cannot expose a module "
"that system policy made unavailable or the tenant disabled. Saved references "
"to such surfaces remain in immutable revisions and are reported as stale. "
"Inherited definitions or assignments must be changed in their owning scope."
" Product-area grouping, ordering, and labels are presentation metadata in the same revision; "
"they cannot expose a hidden surface or grant authority. Grouped navigation is the sensible default, "
"while flat navigation preserves the complete authorized tool rail. Quick Access recommendation and "
"focus ids are likewise revisioned presentation metadata and never authorize a contribution. The runtime's "
"All available tools escape can only reveal contributions that already survived entitlement, policy, preference, "
"surface, context, and permission checks."
),
documentation_types=("admin",),
audience=("administrator", "power_user", "workflow_designer"),
related_modules=("access", "admin", "policy", "workflow_engine"),
translations={
"de": {
"title": "Felder, Zuordnungen und Folgen von Ansichten",
"summary": "Unveränderliche Revisionen, Zuordnungsrangfolge, Schutzvorgaben verpflichtender Ansichten und den Unterschied zwischen Sichtbarkeit und Zugriff verstehen.",
"body": (
"Eine Ansichtsdefinition enthält unveränderliche Revisionen sichtbarer Oberflächenkennungen und Darstellungsmetadaten. Durch Veröffentlichung wird die neueste Revision zuordenbar. Verfügbare Zuordnungen erlauben eine freiwillige Auswahl, Voreinstellungen gelten bis zu einer Änderung und verpflichtende Zuordnungen können nicht verlassen werden. Benutzer- und Gruppenzuordnungen haben Vorrang vor Mandanten- und Systemzuordnungen. "
"Eine Fixierung bewahrt genau eine veröffentlichte Revision; eine nicht fixierte Zuordnung folgt späteren Veröffentlichungen. Verpflichtende Ansichten müssen Auswahl- und Administrationsauswege erhalten. Das Ausblenden einer Oberfläche erteilt oder entzieht niemals eine Berechtigung. Der Oberflächenkatalog ist durch die Modulfreigabe des aktiven Mandanten begrenzt; eine Ansicht kann daher kein systemseitig oder mandantenseitig deaktiviertes Modul freigeben. Gespeicherte Verweise bleiben in unveränderlichen Revisionen erhalten und werden als veraltet gemeldet. Geerbte Definitionen und Zuordnungen müssen in ihrer besitzenden Ebene geändert werden. "
"Produktbereichsgruppen, Reihenfolge und Beschriftungen sind Darstellungsmetadaten derselben Revision und können weder verborgene Oberflächen freigeben noch Berechtigungen erteilen. Gruppierte Navigation ist die sinnvolle Voreinstellung; flache Navigation erhält die vollständige berechtigte Werkzeugleiste. Empfehlungen und Fokuskennungen für Schnellzugriff sind ebenfalls versionierte Darstellungsmetadaten. Der Ausweg Alle verfügbaren Werkzeuge kann nur Beiträge zeigen, die bereits Modulfreigabe, Richtlinie, Einstellung, Oberfläche, Kontext und Berechtigungsprüfung bestanden haben."
),
}
},
links=(
DocumentationLink(
label="Views administration",
href="/admin?section=system-views",
kind="runtime",
),
DocumentationLink(
label="View assignments API",
href="/api/v1/views/assignments",
kind="api",
),
),
metadata={
"kind": "reference",
"help_contexts": [
"views.field.name",
"views.field.description",
"views.field.surfaces",
"views.field.product-areas",
"views.field.navigation-layout",
"views.field.assignment-target",
"views.field.assignment-mode",
"views.field.assignment-priority",
"views.action.publish",
"views.action.archive",
],
"consequence_classes": {
"publish": "Creates the assignable immutable revision used by unpinned assignments.",
"required": "Constrains affected users while retaining selector and administration escape surfaces.",
"archive": "Deactivates optional assignments; required assignments must be removed first.",
"remove_assignment": "Stops the target from inheriting this assignment without deleting the View.",
},
},
order=19,
),
),
architecture=declared_module_architecture(
layer="governance_accountability",
kind="presentation",
maturity="vertical_slice",
documentation_ref="README.md",
test_ref="tests/test_views.py",
known_limits=(
"A View filters presentation only; modules still vary in the granularity of announced surfaces.",
),
owned_concepts=(
"view definition",
"view revision",
"view assignment",
"view selection",
),
non_owned_concepts=(
"authorization",
"module navigation",
"workflow definition",
"dashboard layout",
),
recovery_docs=("README.md",),
security_docs=("README.md",),
),
)
manifest = with_documentation_structured_translations(
manifest, locale="de", translations=GERMAN_STRUCTURED_TRANSLATIONS
)
def get_manifest() -> ModuleManifest:
return manifest
__all__ = [
"ASSIGNMENT_READ_SCOPE",
"ASSIGNMENT_WRITE_SCOPE",
"DEFINITION_READ_SCOPE",
"DEFINITION_WRITE_SCOPE",
"GROUP_DEFINITION_READ_SCOPE",
"GROUP_DEFINITION_WRITE_SCOPE",
"PERSONAL_DEFINITION_READ_SCOPE",
"PERSONAL_DEFINITION_WRITE_SCOPE",
"MODULE_ID",
"MODULE_VERSION",
"SELECTION_READ_SCOPE",
"SELECTION_WRITE_SCOPE",
"SYSTEM_ASSIGNMENT_READ_SCOPE",
"SYSTEM_ASSIGNMENT_WRITE_SCOPE",
"SYSTEM_DEFINITION_READ_SCOPE",
"SYSTEM_DEFINITION_WRITE_SCOPE",
"get_manifest",
"manifest",
]