Files
fonrey/apps/permission/models/staff_perm.py
ishenwei 9a7d06b34e feat: scaffold Django multi-tenant project with 5 of 9 apps
Phase 1 scaffolding: config/, core/, base models, AES-256-GCM phone encryption, enums mirror

apps.tenant: Tenant + Domain (django-tenants)

apps.org: 11 models (OrgUnit hierarchy, Staff, audit logs)

apps.account: 4 models (UserAccount as AUTH_USER_MODEL, login/password tracking)

apps.permission: 7 models (RBAC + overrides + datascope + append-only changelog)

apps.region: 5 models (District, BusinessArea, MetroLine, MetroStation, School)

All migrations generated, manage.py check passes
2026-04-29 17:01:55 +08:00

201 lines
6.3 KiB
Python

from django.db import models
from core.enums import (
PermissionChangeAction,
PermissionChangeTargetType,
PermissionDataScopeType,
PermissionOverrideMode,
)
from core.models.base import TimeStampedModel, UUIDPrimaryKeyModel
class StaffRole(UUIDPrimaryKeyModel):
staff = models.ForeignKey(
"org.Staff",
on_delete=models.CASCADE,
related_name="staff_roles",
)
role = models.ForeignKey(
"fonrey_permission.Role",
on_delete=models.PROTECT,
related_name="staff_links",
)
is_primary = models.BooleanField(default=False)
assigned_at = models.DateTimeField(auto_now_add=True)
assigned_by = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="staff_role_assignments_made",
)
valid_from = models.DateField(null=True, blank=True)
valid_until = models.DateField(null=True, blank=True)
class Meta:
db_table = "staff_roles"
constraints = [
models.UniqueConstraint(
fields=["staff", "role"],
name="uq_staff_roles",
),
models.UniqueConstraint(
fields=["staff"],
condition=models.Q(is_primary=True),
name="uq_staff_roles_primary",
),
]
indexes = [
models.Index(fields=["role"], name="idx_staff_roles_role"),
]
def __str__(self) -> str:
marker = " [primary]" if self.is_primary else ""
return f"{self.staff_id}{self.role_id}{marker}"
class StaffPermissionOverride(UUIDPrimaryKeyModel):
staff = models.ForeignKey(
"org.Staff",
on_delete=models.CASCADE,
related_name="permission_overrides",
)
permission_def = models.ForeignKey(
"fonrey_permission.PermissionDef",
on_delete=models.PROTECT,
related_name="staff_overrides",
)
value = models.JSONField()
override_mode = models.CharField(
max_length=10,
choices=PermissionOverrideMode.choices,
default=PermissionOverrideMode.REPLACE,
)
reason = models.TextField(blank=True, default="")
modified_by = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="staff_overrides_modified",
)
modified_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "staff_permission_overrides"
constraints = [
models.UniqueConstraint(
fields=["staff", "permission_def"],
name="uq_staff_overrides",
),
]
indexes = [
models.Index(fields=["staff"], name="idx_staff_overrides_staff"),
]
class StaffDataScope(UUIDPrimaryKeyModel):
staff = models.ForeignKey(
"org.Staff",
on_delete=models.CASCADE,
related_name="data_scopes",
)
scope_type = models.CharField(
max_length=20,
choices=PermissionDataScopeType.choices,
)
org_unit = models.ForeignKey(
"org.OrgUnit",
null=True,
blank=True,
on_delete=models.PROTECT,
related_name="data_scope_grants",
)
is_readable = models.BooleanField(default=True)
is_writable = models.BooleanField(default=False)
granted_by = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="data_scopes_granted",
)
granted_at = models.DateTimeField(auto_now_add=True)
expires_at = models.DateTimeField(null=True, blank=True)
reason = models.TextField(blank=True, default="")
class Meta:
db_table = "staff_data_scopes"
indexes = [
models.Index(fields=["staff"], name="idx_data_scopes_staff"),
models.Index(fields=["org_unit"], name="idx_data_scopes_org"),
models.Index(
fields=["expires_at"],
name="idx_data_scopes_expires",
condition=models.Q(expires_at__isnull=False),
),
]
class PermissionChangeLog(UUIDPrimaryKeyModel):
target_type = models.CharField(
max_length=30,
choices=PermissionChangeTargetType.choices,
)
target_id = models.UUIDField()
staff = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="permission_change_logs_affecting",
)
role = models.ForeignKey(
"fonrey_permission.Role",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="change_logs",
)
permission_code = models.CharField(max_length=150, blank=True, default="")
action = models.CharField(max_length=20, choices=PermissionChangeAction.choices)
old_value = models.JSONField(null=True, blank=True)
new_value = models.JSONField(null=True, blank=True)
operator = models.ForeignKey(
"org.Staff",
on_delete=models.PROTECT,
related_name="permission_changes_operated",
)
operator_ip = models.GenericIPAddressField(null=True, blank=True)
user_agent = models.TextField(blank=True, default="")
reason = models.TextField(blank=True, default="")
operated_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "permission_change_logs"
ordering = ["-operated_at"]
indexes = [
models.Index(
fields=["staff", "-operated_at"],
name="idx_perm_log_staff",
condition=models.Q(staff__isnull=False),
),
models.Index(
fields=["role", "-operated_at"],
name="idx_perm_log_role",
condition=models.Q(role__isnull=False),
),
models.Index(
fields=["target_type", "target_id", "-operated_at"],
name="idx_perm_log_target",
),
models.Index(
fields=["operator", "-operated_at"],
name="idx_perm_log_operator",
),
models.Index(fields=["-operated_at"], name="idx_perm_log_time"),
]
def delete(self, *args, **kwargs):
raise NotImplementedError("PermissionChangeLog is append-only and cannot be deleted.")