- infrasynth.configs: typed multi-tenant config store (registry, service, secrets, cache) + public config_changed/config_reset signals and API - emit the declared-but-dead signals (features flags/overrides, scheduler task_completed/task_failed, tenancy tenant_updated, audit model_changed) and per-model audit field exclusions - security: permission catalog (security_permission), Django-style model-derived AutoPermission, PermissionRegistry, RoleAssignment, global-or-tenant Grant/Revoke, catalog API - consolidate the permission surface: PermissionRegistry only (drop the settings dict), IsAuthenticatedAndPermitted aliases HybridPermission, require_permission replaced by required_permissions + require_all - packaging: add [build-system]; add Forgejo publish workflow (.forgejo)
49 lines
1.6 KiB
Python
49 lines
1.6 KiB
Python
"""Configuration storage model.
|
|
|
|
A single table stores both the platform default (``tenant IS NULL``) and a
|
|
tenant's override (non-null ``tenant``) for the same key — the same shape as
|
|
``features.FeatureFlag`` (``PLAN.md`` §2.0). Secret values are stored as a Fernet
|
|
token (a JSON string); encryption/decryption is the service's concern and the
|
|
model stays dumb.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from django.conf import settings
|
|
from django.db import models
|
|
from django.db.models import Q
|
|
|
|
from infrasynth.tenancy.mixins import GlobalOrTenantModel
|
|
|
|
__all__ = ["ConfigValue"]
|
|
|
|
|
|
class ConfigValue(GlobalOrTenantModel):
|
|
"""A configuration value. ``tenant IS NULL`` = platform default, non-null = tenant override."""
|
|
|
|
key = models.CharField(max_length=200, db_index=True)
|
|
value = models.JSONField(default=dict)
|
|
updated_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name="+",
|
|
)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
db_table = "configs_value"
|
|
constraints = [
|
|
models.UniqueConstraint(fields=["tenant", "key"], name="uniq_config_key_per_tenant"),
|
|
models.UniqueConstraint(
|
|
fields=["key"],
|
|
condition=Q(tenant__isnull=True),
|
|
name="uniq_global_config_key",
|
|
),
|
|
]
|
|
indexes = [models.Index(fields=["tenant_id", "key"])]
|
|
|
|
def __str__(self) -> str:
|
|
scope = self.tenant_id if self.tenant_id is not None else "global"
|
|
return f"{self.key}@{scope}"
|