Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bca590e53 | ||
|
|
64d6638c60 |
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/tenancy-webui",
|
"name": "@govoplan/tenancy-webui",
|
||||||
"version": "0.1.15",
|
"version": "0.1.17",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "webui/src/index.ts",
|
"main": "webui/src/index.ts",
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"README.md"
|
"README.md"
|
||||||
],
|
],
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@govoplan/core-webui": "^0.1.15",
|
"@govoplan/core-webui": "^0.1.17",
|
||||||
"lucide-react": "^1.23.0",
|
"lucide-react": "^1.23.0",
|
||||||
"react": ">=19.2.7 <20",
|
"react": ">=19.2.7 <20",
|
||||||
"react-dom": ">=19.2.7 <20"
|
"react-dom": ">=19.2.7 <20"
|
||||||
|
|||||||
+2
-2
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "govoplan-tenancy"
|
name = "govoplan-tenancy"
|
||||||
version = "0.1.15"
|
version = "0.1.17"
|
||||||
description = "GovOPlaN tenancy platform module."
|
description = "GovOPlaN tenancy platform module."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
authors = [{ name = "GovOPlaN" }]
|
authors = [{ name = "GovOPlaN" }]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"govoplan-core>=0.1.15",
|
"govoplan-core>=0.1.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from govoplan_core.core.principal_cache import invalidate_auth_principals
|
|||||||
from govoplan_core.core.runtime import get_registry
|
from govoplan_core.core.runtime import get_registry
|
||||||
from govoplan_core.db.session import get_session
|
from govoplan_core.db.session import get_session
|
||||||
from govoplan_core.i18n import (
|
from govoplan_core.i18n import (
|
||||||
|
REFERENCE_LANGUAGE_CODE,
|
||||||
i18n_settings,
|
i18n_settings,
|
||||||
normalize_enabled_language_codes,
|
normalize_enabled_language_codes,
|
||||||
system_enabled_language_codes,
|
system_enabled_language_codes,
|
||||||
@@ -686,7 +687,7 @@ def _normalized_optional_text(value: str | None) -> str | None:
|
|||||||
|
|
||||||
|
|
||||||
def _normalized_tenant_locale(value: str) -> str:
|
def _normalized_tenant_locale(value: str) -> str:
|
||||||
return value.strip() or "en"
|
return value.strip() or REFERENCE_LANGUAGE_CODE
|
||||||
|
|
||||||
|
|
||||||
def _apply_tenant_governance_updates(session: Session, tenant: Tenant, payload: TenantUpdateRequest) -> None:
|
def _apply_tenant_governance_updates(session: Session, tenant: Tenant, payload: TenantUpdateRequest) -> None:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from typing import Any, Literal
|
|||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
|
|
||||||
from govoplan_core.api.v1.schemas import DeltaDeletedItem
|
from govoplan_core.api.v1.schemas import DeltaDeletedItem
|
||||||
|
from govoplan_core.i18n import REFERENCE_LANGUAGE_CODE
|
||||||
|
|
||||||
|
|
||||||
class TenantAdminItem(BaseModel):
|
class TenantAdminItem(BaseModel):
|
||||||
@@ -13,7 +14,11 @@ class TenantAdminItem(BaseModel):
|
|||||||
slug: str = Field(min_length=1, max_length=100)
|
slug: str = Field(min_length=1, max_length=100)
|
||||||
name: str = Field(min_length=1, max_length=255)
|
name: str = Field(min_length=1, max_length=255)
|
||||||
description: str | None = None
|
description: str | None = None
|
||||||
default_locale: str = Field(default="en", min_length=1, max_length=20)
|
default_locale: str = Field(
|
||||||
|
default=REFERENCE_LANGUAGE_CODE,
|
||||||
|
min_length=1,
|
||||||
|
max_length=20,
|
||||||
|
)
|
||||||
settings: dict[str, Any] = Field(default_factory=dict)
|
settings: dict[str, Any] = Field(default_factory=dict)
|
||||||
allow_custom_groups: bool | None = None
|
allow_custom_groups: bool | None = None
|
||||||
allow_custom_roles: bool | None = None
|
allow_custom_roles: bool | None = None
|
||||||
@@ -58,7 +63,7 @@ class TenantCreateRequest(BaseModel):
|
|||||||
name: str
|
name: str
|
||||||
owner_account_id: str | None = None
|
owner_account_id: str | None = None
|
||||||
description: str | None = None
|
description: str | None = None
|
||||||
default_locale: str = "en"
|
default_locale: str = REFERENCE_LANGUAGE_CODE
|
||||||
settings: dict[str, Any] = Field(default_factory=dict)
|
settings: dict[str, Any] = Field(default_factory=dict)
|
||||||
allow_custom_groups: bool | None = None
|
allow_custom_groups: bool | None = None
|
||||||
allow_custom_roles: bool | None = None
|
allow_custom_roles: bool | None = None
|
||||||
@@ -124,7 +129,11 @@ class TenantSettingsItem(BaseModel):
|
|||||||
id: str
|
id: str
|
||||||
slug: str
|
slug: str
|
||||||
name: str
|
name: str
|
||||||
default_locale: str = Field(default="en", min_length=1, max_length=20)
|
default_locale: str = Field(
|
||||||
|
default=REFERENCE_LANGUAGE_CODE,
|
||||||
|
min_length=1,
|
||||||
|
max_length=20,
|
||||||
|
)
|
||||||
available_languages: list[dict[str, Any]] = Field(default_factory=list)
|
available_languages: list[dict[str, Any]] = Field(default_factory=list)
|
||||||
system_enabled_language_codes: list[str] = Field(default_factory=list)
|
system_enabled_language_codes: list[str] = Field(default_factory=list)
|
||||||
enabled_language_codes: list[str] = Field(default_factory=list)
|
enabled_language_codes: list[str] = Field(default_factory=list)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ def _route_factory(context: ModuleContext):
|
|||||||
manifest = ModuleManifest(
|
manifest = ModuleManifest(
|
||||||
id="tenancy",
|
id="tenancy",
|
||||||
name="Tenancy",
|
name="Tenancy",
|
||||||
version="0.1.15",
|
version="0.1.17",
|
||||||
required_capabilities=(
|
required_capabilities=(
|
||||||
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
|
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
|
||||||
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
|
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
|
||||||
@@ -60,7 +60,7 @@ manifest = ModuleManifest(
|
|||||||
id="tenancy.lifecycle-and-settings",
|
id="tenancy.lifecycle-and-settings",
|
||||||
title="Administer tenant lifecycle and settings",
|
title="Administer tenant lifecycle and settings",
|
||||||
summary="Tenancy adds explicit tenant creation, activation, context resolution, and tenant-owned settings over Core's shared scope storage.",
|
summary="Tenancy adds explicit tenant creation, activation, context resolution, and tenant-owned settings over Core's shared scope storage.",
|
||||||
body="A tenant is a concrete administrative and data boundary. Tenant lifecycle changes must preserve ownership and recovery guarantees for module-owned records. Tenancy contributes system tenant management and tenant settings to the shared administration workspace; without this module, the Core and Access baseline can operate in single-scope compatibility mode. Core-reserved module entitlement settings are managed only through the Admin module's tenant-module policy endpoints and are preserved when generic tenant settings are replaced.",
|
body="A tenant is a concrete administrative and data boundary. Tenant lifecycle changes must preserve ownership and recovery guarantees for module-owned records. New tenants default to the German reference language unless the administrator selects another enabled system language; existing tenant and user preferences remain unchanged. Tenancy contributes system tenant management and tenant settings to the shared administration workspace; without this module, the Core and Access baseline can operate in single-scope compatibility mode. Core-reserved module entitlement settings are managed only through the Admin module's tenant-module policy endpoints and are preserved when generic tenant settings are replaced.",
|
||||||
documentation_types=("admin",),
|
documentation_types=("admin",),
|
||||||
audience=("system_admin", "tenant_admin", "operator"),
|
audience=("system_admin", "tenant_admin", "operator"),
|
||||||
related_modules=("access", "admin", "audit"),
|
related_modules=("access", "admin", "audit"),
|
||||||
@@ -83,7 +83,7 @@ manifest = ModuleManifest(
|
|||||||
id="tenancy.reference.admin-fields",
|
id="tenancy.reference.admin-fields",
|
||||||
title="Tenant administration fields and consequences",
|
title="Tenant administration fields and consequences",
|
||||||
summary="Tenant identity, ownership, locale, governance overrides, and lifecycle state have different mutation and recovery consequences.",
|
summary="Tenant identity, ownership, locale, governance overrides, and lifecycle state have different mutation and recovery consequences.",
|
||||||
body="A tenant slug is immutable after creation and identifies the administrative boundary. The initial owner receives the protected tenant-owner role. Locale and enabled languages are bounded by system language packages. Governance overrides may narrow a system allowance but cannot loosen a system denial. Suspension keeps tenant-owned data and audit evidence while preventing normal use; an operator must switch away from the active tenant before suspending it.",
|
body="A tenant slug is immutable after creation and identifies the administrative boundary. The initial owner receives the protected tenant-owner role. German is the reference and new-tenant default; locale and enabled languages are bounded by system language packages and may be changed explicitly. Governance overrides may narrow a system allowance but cannot loosen a system denial. Suspension keeps tenant-owned data and audit evidence while preventing normal use; an operator must switch away from the active tenant before suspending it.",
|
||||||
documentation_types=("admin",),
|
documentation_types=("admin",),
|
||||||
audience=("system_admin", "tenant_admin", "operator"),
|
audience=("system_admin", "tenant_admin", "operator"),
|
||||||
related_modules=("access", "admin", "audit"),
|
related_modules=("access", "admin", "audit"),
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ from govoplan_tenancy.backend.api.v1.routes import (
|
|||||||
_apply_tenant_status_update,
|
_apply_tenant_status_update,
|
||||||
_require_tenant_update_permissions,
|
_require_tenant_update_permissions,
|
||||||
)
|
)
|
||||||
from govoplan_tenancy.backend.api.v1.schemas import TenantUpdateRequest
|
from govoplan_tenancy.backend.api.v1.schemas import (
|
||||||
|
TenantCreateRequest,
|
||||||
|
TenantSettingsItem,
|
||||||
|
TenantUpdateRequest,
|
||||||
|
)
|
||||||
from govoplan_core.core.module_entitlements import MODULE_ENTITLEMENTS_KEY
|
from govoplan_core.core.module_entitlements import MODULE_ENTITLEMENTS_KEY
|
||||||
from govoplan_tenancy.backend.lifecycle import (
|
from govoplan_tenancy.backend.lifecycle import (
|
||||||
TENANT_EVENT_CREATED,
|
TENANT_EVENT_CREATED,
|
||||||
@@ -35,6 +39,15 @@ class FakePrincipal:
|
|||||||
|
|
||||||
|
|
||||||
class TenantLifecycleContractTests(unittest.TestCase):
|
class TenantLifecycleContractTests(unittest.TestCase):
|
||||||
|
def test_new_tenant_contracts_use_german_reference_default(self) -> None:
|
||||||
|
tenant = TenantCreateRequest(slug="example", name="Example")
|
||||||
|
|
||||||
|
self.assertEqual("de", tenant.default_locale)
|
||||||
|
self.assertEqual(
|
||||||
|
"de",
|
||||||
|
TenantSettingsItem(id="tenant-1", slug="example", name="Example").default_locale,
|
||||||
|
)
|
||||||
|
|
||||||
def test_lifecycle_event_names_are_stable(self) -> None:
|
def test_lifecycle_event_names_are_stable(self) -> None:
|
||||||
self.assertEqual("tenant.created", tenant_lifecycle_event_type("created"))
|
self.assertEqual("tenant.created", tenant_lifecycle_event_type("created"))
|
||||||
self.assertEqual("tenant.suspended", tenant_lifecycle_event_type("suspended"))
|
self.assertEqual("tenant.suspended", tenant_lifecycle_event_type("suspended"))
|
||||||
@@ -119,7 +132,7 @@ class TenantUpdateHelperTests(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual("New tenant", tenant.name)
|
self.assertEqual("New tenant", tenant.name)
|
||||||
self.assertIsNone(tenant.description)
|
self.assertIsNone(tenant.description)
|
||||||
self.assertEqual("en", tenant.default_locale)
|
self.assertEqual("de", tenant.default_locale)
|
||||||
self.assertEqual({"theme": "contrast"}, tenant.settings)
|
self.assertEqual({"theme": "contrast"}, tenant.settings)
|
||||||
|
|
||||||
def test_tenant_content_update_preserves_reserved_module_entitlements(self) -> None:
|
def test_tenant_content_update_preserves_reserved_module_entitlements(self) -> None:
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/tenancy-webui",
|
"name": "@govoplan/tenancy-webui",
|
||||||
"version": "0.1.15",
|
"version": "0.1.17",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@govoplan/core-webui": "^0.1.15",
|
"@govoplan/core-webui": "^0.1.17",
|
||||||
"lucide-react": "^1.23.0",
|
"lucide-react": "^1.23.0",
|
||||||
"react": ">=19.2.7 <20",
|
"react": ">=19.2.7 <20",
|
||||||
"react-dom": ">=19.2.7 <20"
|
"react-dom": ">=19.2.7 <20"
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ const fallback: TenantSettingsItem = {
|
|||||||
id: "",
|
id: "",
|
||||||
slug: "",
|
slug: "",
|
||||||
name: "",
|
name: "",
|
||||||
default_locale: "en",
|
default_locale: "de",
|
||||||
available_languages: [
|
available_languages: [
|
||||||
{ code: "en", label: "English", native_label: "English" },
|
{ code: "de", label: "German", native_label: "Deutsch" },
|
||||||
{ code: "de", label: "German", native_label: "Deutsch" }
|
{ code: "en", label: "English", native_label: "English" }
|
||||||
],
|
],
|
||||||
system_enabled_language_codes: ["en", "de"],
|
system_enabled_language_codes: ["de", "en"],
|
||||||
enabled_language_codes: ["en", "de"],
|
enabled_language_codes: ["de", "en"],
|
||||||
settings: {}
|
settings: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const emptyDraft: TenantDraft = {
|
|||||||
name: "",
|
name: "",
|
||||||
ownerAccountId: "",
|
ownerAccountId: "",
|
||||||
description: "",
|
description: "",
|
||||||
defaultLocale: "en",
|
defaultLocale: "de",
|
||||||
isActive: true,
|
isActive: true,
|
||||||
customGroups: "inherit",
|
customGroups: "inherit",
|
||||||
customRoles: "inherit",
|
customRoles: "inherit",
|
||||||
@@ -147,7 +147,7 @@ export default function TenantsPanel({
|
|||||||
name: tenant.name,
|
name: tenant.name,
|
||||||
ownerAccountId: "",
|
ownerAccountId: "",
|
||||||
description: tenant.description || "",
|
description: tenant.description || "",
|
||||||
defaultLocale: tenant.default_locale || "en",
|
defaultLocale: tenant.default_locale || "de",
|
||||||
isActive: tenant.is_active,
|
isActive: tenant.is_active,
|
||||||
customGroups: fromOverride(tenant.allow_custom_groups),
|
customGroups: fromOverride(tenant.allow_custom_groups),
|
||||||
customRoles: fromOverride(tenant.allow_custom_roles),
|
customRoles: fromOverride(tenant.allow_custom_roles),
|
||||||
|
|||||||
Reference in New Issue
Block a user