From ab48e2e853ebaeebdf873e3d733ebc46cc551b93 Mon Sep 17 00:00:00 2001 From: patrick-motsch Date: Tue, 10 Feb 2026 00:10:07 +0100 Subject: [PATCH] enhanced generic navigation tree --- modules/datamodels/datamodelMembership.py | 2 +- modules/datamodels/datamodelRbac.py | 2 +- modules/datamodels/datamodelUam.py | 8 +++---- modules/interfaces/interfaceBootstrap.py | 29 +++++++++++++++++++++++ modules/interfaces/interfaceDbApp.py | 4 ++-- modules/routes/routeBilling.py | 2 +- modules/routes/routeDataMandates.py | 4 ++-- modules/routes/routeInvitations.py | 6 ++--- modules/routes/routeSystem.py | 2 +- 9 files changed, 44 insertions(+), 15 deletions(-) diff --git a/modules/datamodels/datamodelMembership.py b/modules/datamodels/datamodelMembership.py index e100b23c..5e8b8814 100644 --- a/modules/datamodels/datamodelMembership.py +++ b/modules/datamodels/datamodelMembership.py @@ -28,7 +28,7 @@ class UserMandate(BaseModel): ) mandateId: str = Field( description="FK → Mandate.id (CASCADE DELETE)", - json_schema_extra={"frontend_type": "select", "frontend_readonly": False, "frontend_required": True, "frontend_fk_source": "/api/mandates/", "frontend_fk_display_field": "name"} + json_schema_extra={"frontend_type": "select", "frontend_readonly": False, "frontend_required": True, "frontend_fk_source": "/api/mandates/", "frontend_fk_display_field": "label"} ) enabled: bool = Field( default=True, diff --git a/modules/datamodels/datamodelRbac.py b/modules/datamodels/datamodelRbac.py index 64ad56a4..978c3be6 100644 --- a/modules/datamodels/datamodelRbac.py +++ b/modules/datamodels/datamodelRbac.py @@ -55,7 +55,7 @@ class Role(BaseModel): mandateId: Optional[str] = Field( default=None, description="FK → Mandate.id (CASCADE DELETE). Null = Global/Template role.", - json_schema_extra={"frontend_type": "select", "frontend_readonly": True, "frontend_visible": True, "frontend_required": False, "frontend_fk_source": "/api/mandates/", "frontend_fk_display_field": "name"} + json_schema_extra={"frontend_type": "select", "frontend_readonly": True, "frontend_visible": True, "frontend_required": False, "frontend_fk_source": "/api/mandates/", "frontend_fk_display_field": "label"} ) featureInstanceId: Optional[str] = Field( default=None, diff --git a/modules/datamodels/datamodelUam.py b/modules/datamodels/datamodelUam.py index 6b7cdc06..22d94ebe 100644 --- a/modules/datamodels/datamodelUam.py +++ b/modules/datamodels/datamodelUam.py @@ -73,10 +73,10 @@ class Mandate(BaseModel): description="Name of the mandate", json_schema_extra={"frontend_type": "text", "frontend_readonly": False, "frontend_required": True} ) - description: Optional[str] = Field( + label: Optional[str] = Field( default=None, - description="Description of the mandate", - json_schema_extra={"frontend_type": "textarea", "frontend_readonly": False, "frontend_required": False} + description="Display label of the mandate", + json_schema_extra={"frontend_type": "text", "frontend_readonly": False, "frontend_required": False} ) enabled: bool = Field( default=True, @@ -104,7 +104,7 @@ registerModelLabels( { "id": {"en": "ID", "de": "ID", "fr": "ID"}, "name": {"en": "Name", "de": "Name", "fr": "Nom"}, - "description": {"en": "Description", "de": "Beschreibung", "fr": "Description"}, + "label": {"en": "Label", "de": "Label", "fr": "Libellé"}, "enabled": {"en": "Enabled", "de": "Aktiviert", "fr": "Activé"}, "isSystem": {"en": "System Mandate", "de": "System-Mandant", "fr": "Mandat système"}, }, diff --git a/modules/interfaces/interfaceBootstrap.py b/modules/interfaces/interfaceBootstrap.py index 8a58f352..3f678a2b 100644 --- a/modules/interfaces/interfaceBootstrap.py +++ b/modules/interfaces/interfaceBootstrap.py @@ -51,6 +51,9 @@ def initBootstrap(db: DatabaseConnector) -> None: # Initialize root mandate mandateId = initRootMandate(db) + # Migrate existing mandate records: description -> label + _migrateMandateDescriptionToLabel(db) + # Initialize system role TEMPLATES (mandateId=None, isSystemRole=True) initRoles(db) @@ -276,6 +279,32 @@ def initRootMandate(db: DatabaseConnector) -> Optional[str]: return mandateId +def _migrateMandateDescriptionToLabel(db: DatabaseConnector) -> None: + """ + Migration: Rename 'description' field to 'label' in all Mandate records. + Copies existing 'description' values to 'label' and removes the old field. + Safe to run multiple times (idempotent). + """ + allMandates = db.getRecordset(Mandate) + migratedCount = 0 + for mandateRecord in allMandates: + mandateId = mandateRecord.get("id") + hasDescription = "description" in mandateRecord and mandateRecord.get("description") is not None + hasLabel = "label" in mandateRecord and mandateRecord.get("label") is not None + + if hasDescription and not hasLabel: + # Copy description to label + updateData = {"label": mandateRecord["description"]} + db.recordModify(Mandate, mandateId, updateData) + migratedCount += 1 + logger.info(f"Migrated mandate {mandateId}: description -> label") + + if migratedCount > 0: + logger.info(f"Migrated {migratedCount} mandate(s) from description to label") + else: + logger.debug("No mandate description->label migration needed") + + def initAdminUser(db: DatabaseConnector, mandateId: Optional[str]) -> Optional[str]: """ Creates the Admin user if it doesn't exist. diff --git a/modules/interfaces/interfaceDbApp.py b/modules/interfaces/interfaceDbApp.py index 2ca8232b..d6e4c6c0 100644 --- a/modules/interfaces/interfaceDbApp.py +++ b/modules/interfaces/interfaceDbApp.py @@ -1444,7 +1444,7 @@ class AppObjects: return Mandate(**filteredMandates[0]) - def createMandate(self, name: str, description: str = None, enabled: bool = True) -> Mandate: + def createMandate(self, name: str, label: str = None, enabled: bool = True) -> Mandate: """ Creates a new mandate if user has permission. Automatically copies system template roles (admin, user, viewer) to the new mandate. @@ -1453,7 +1453,7 @@ class AppObjects: raise PermissionError("No permission to create mandates") # Create mandate data using model - mandateData = Mandate(name=name, description=description, enabled=enabled) + mandateData = Mandate(name=name, label=label, enabled=enabled) # Create mandate record createdRecord = self.db.recordCreate(Mandate, mandateData) diff --git a/modules/routes/routeBilling.py b/modules/routes/routeBilling.py index e785ed13..586cc6dd 100644 --- a/modules/routes/routeBilling.py +++ b/modules/routes/routeBilling.py @@ -355,7 +355,7 @@ def getBalanceForMandate( from modules.interfaces.interfaceDbApp import getInterface as getAppInterface appInterface = getAppInterface(ctx.user, mandateId=targetMandateId) mandate = appInterface.getMandate(targetMandateId) - mandateName = mandate.get("name", "") if mandate else "" + mandateName = (mandate.get("label") or mandate.get("name", "")) if mandate else "" return BillingBalanceResponse( mandateId=targetMandateId, diff --git a/modules/routes/routeDataMandates.py b/modules/routes/routeDataMandates.py index 8d2c4a2b..b74b6277 100644 --- a/modules/routes/routeDataMandates.py +++ b/modules/routes/routeDataMandates.py @@ -192,7 +192,7 @@ def create_mandate( ) # Get optional fields with defaults - description = mandateData.get('description') + label = mandateData.get('label') enabled = mandateData.get('enabled', True) appInterface = interfaceDbApp.getRootInterface() @@ -200,7 +200,7 @@ def create_mandate( # Create mandate newMandate = appInterface.createMandate( name=name, - description=description, + label=label, enabled=enabled ) diff --git a/modules/routes/routeInvitations.py b/modules/routes/routeInvitations.py index 01c395e2..2454db44 100644 --- a/modules/routes/routeInvitations.py +++ b/modules/routes/routeInvitations.py @@ -190,7 +190,7 @@ def create_invitation( from modules.connectors.connectorMessagingEmail import ConnectorMessagingEmail # Get mandate name for the email mandate = rootInterface.getMandate(str(context.mandateId)) - mandateName = mandate.name if mandate else "PowerOn" + mandateName = (mandate.label or mandate.name) if mandate else "PowerOn" emailConnector = ConnectorMessagingEmail() emailSubject = f"Einladung zu {mandateName}" @@ -249,7 +249,7 @@ def create_invitation( # Get mandate name for notification mandate = rootInterface.getMandate(str(context.mandateId)) - mandateName = mandate.mandateLabel if mandate and mandate.mandateLabel else "PowerOn" + mandateName = (mandate.label or mandate.name) if mandate else "PowerOn" inviterName = context.user.fullName or context.user.username createInvitationNotification( @@ -529,7 +529,7 @@ def validate_invitation( # Get mandate name mandate = rootInterface.getMandate(str(mandateId)) if mandateId else None if mandate: - mandateName = mandate.name + mandateName = mandate.label or mandate.name # Get role names roleIds = invitation.roleIds or [] diff --git a/modules/routes/routeSystem.py b/modules/routes/routeSystem.py index 3c8cdd3d..83f4ed85 100644 --- a/modules/routes/routeSystem.py +++ b/modules/routes/routeSystem.py @@ -153,7 +153,7 @@ def _buildDynamicBlock( mandateId = str(instance.mandateId) if mandateId not in mandatesMap: mandate = rootInterface.getMandate(mandateId) - mandateName = mandate.name if mandate and hasattr(mandate, 'name') else mandateId + mandateName = (mandate.label or mandate.name) if mandate else mandateId mandatesMap[mandateId] = { "id": mandateId, "uiLabel": mandateName,