infrasynth-backend-kit/infrasynth/configs/models.py
jcv-dev a2930426b4 feat: tenant configs, live signals, and automatic permission management
- 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)
2026-09-29 17:06:54 -05:00

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}"