Release v0.1.2

This commit is contained in:
2026-06-25 19:58:20 +02:00
parent 15794e920e
commit 02564047e9
73 changed files with 4073 additions and 478 deletions

View File

@@ -5,6 +5,7 @@ from dataclasses import dataclass
from sqlalchemy.orm import Session
from govoplan_core.admin.service import AdminConflictError, AdminValidationError, slugify
from govoplan_core.core.optional import reraise_unless_missing_package
from govoplan_core.db.models import (
ApiKey,
GovernanceTemplate,
@@ -17,7 +18,6 @@ from govoplan_core.db.models import (
UserGroupMembership,
UserRoleAssignment,
)
from govoplan_files.backend.db.models import FileAsset, FileFolder, FileShare
from govoplan_core.security.permissions import validate_tenant_permissions
SYSTEM_SETTINGS_ID = "global"
@@ -25,6 +25,15 @@ TEMPLATE_KINDS = {"group", "role"}
ASSIGNMENT_MODES = {"available", "required"}
def _file_models():
try:
from govoplan_files.backend.db.models import FileAsset, FileFolder, FileShare
except ModuleNotFoundError as exc:
reraise_unless_missing_package(exc, "govoplan_files")
return None
return FileAsset, FileFolder, FileShare
@dataclass(frozen=True)
class EffectiveTenantGovernance:
allow_custom_groups: bool
@@ -246,11 +255,17 @@ def _remove_materialized_template(session: Session, item: GovernanceTemplate, te
return
membership_count = session.query(UserGroupMembership).filter(UserGroupMembership.group_id == group.id).count()
role_count = session.query(GroupRoleAssignment).filter(GroupRoleAssignment.group_id == group.id).count()
owned_asset_count = session.query(FileAsset).filter(FileAsset.owner_group_id == group.id).count()
owned_folder_count = session.query(FileFolder).filter(FileFolder.owner_group_id == group.id).count()
shared_asset_count = session.query(FileShare).filter(
FileShare.target_type == "group", FileShare.target_id == group.id
).count()
file_models = _file_models()
owned_asset_count = 0
owned_folder_count = 0
shared_asset_count = 0
if file_models is not None:
FileAsset, FileFolder, FileShare = file_models
owned_asset_count = session.query(FileAsset).filter(FileAsset.owner_group_id == group.id).count()
owned_folder_count = session.query(FileFolder).filter(FileFolder.owner_group_id == group.id).count()
shared_asset_count = session.query(FileShare).filter(
FileShare.target_type == "group", FileShare.target_id == group.id
).count()
if membership_count or role_count or owned_asset_count or owned_folder_count or shared_asset_count:
raise AdminConflictError(
f"Cannot remove {item.name!r} from the tenant while its managed group has members, roles, files, folders or shares."

View File

@@ -9,6 +9,7 @@ from dataclasses import dataclass
from sqlalchemy import func
from sqlalchemy.orm import Session
from govoplan_core.core.optional import reraise_unless_missing_package
from govoplan_core.db.models import (
Account,
ApiKey,
@@ -548,16 +549,25 @@ def role_assignment_counts(session: Session, role_id: str) -> tuple[int, int]:
)
def _optional_model(module_name: str, model_name: str):
try:
module = __import__(module_name, fromlist=[model_name])
except ModuleNotFoundError as exc:
reraise_unless_missing_package(exc, module_name.split(".", 1)[0])
return None
return getattr(module, model_name)
def tenant_counts(session: Session, tenant_id: str) -> dict[str, int]:
from govoplan_campaign.backend.db.models import Campaign
from govoplan_files.backend.db.models import FileAsset
Campaign = _optional_model("govoplan_campaign.backend.db.models", "Campaign")
FileAsset = _optional_model("govoplan_files.backend.db.models", "FileAsset")
return {
"users": session.query(User).filter(User.tenant_id == tenant_id).count(),
"active_users": session.query(User).filter(User.tenant_id == tenant_id, User.is_active.is_(True)).count(),
"groups": session.query(Group).filter(Group.tenant_id == tenant_id).count(),
"campaigns": session.query(Campaign).filter(Campaign.tenant_id == tenant_id).count(),
"files": session.query(FileAsset).filter(FileAsset.tenant_id == tenant_id).count(),
"campaigns": session.query(Campaign).filter(Campaign.tenant_id == tenant_id).count() if Campaign is not None else 0,
"files": session.query(FileAsset).filter(FileAsset.tenant_id == tenant_id).count() if FileAsset is not None else 0,
"api_keys": session.query(ApiKey).filter(ApiKey.tenant_id == tenant_id, ApiKey.revoked_at.is_(None)).count(),
}