"""租户范围模型的抽象基类。 所有业务表都应该继承 TenantScopedModel,自动获得: - tenant_id 外键 - org_id 外键(可选,预留多组织) - 标准审计字段(created_at, updated_at, created_by, updated_by, is_deleted) - manager `for_tenant(tenant)` 自动加过滤 """ from django.conf import settings from django.db import models class TenantScopedQuerySet(models.QuerySet): def for_tenant(self, tenant): return self.filter(tenant=tenant) def for_org(self, org): return self.filter(org=org) def active(self): return self.filter(is_deleted=False) class TenantScopedModel(models.Model): tenant = models.ForeignKey( "core.Tenant", on_delete=models.PROTECT, related_name="+", help_text="所属租户", ) org = models.ForeignKey( "core.Org", null=True, blank=True, on_delete=models.PROTECT, related_name="+", help_text="所属组织(多机构预留)", ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) updated_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) # 软删除预留 is_deleted = models.BooleanField(default=False) deleted_at = models.DateTimeField(null=True, blank=True) # 扩展 JSON 字段:存自定义属性/第三方对接数据/AI 抽取结果 ext_data = models.JSONField(default=dict, blank=True) # 来源渠道(预留 AI Agent / 电商对接):manual/api/web/miniprogram/ai_agent source_channel = models.CharField(max_length=32, blank=True, default="manual") objects = TenantScopedQuerySet.as_manager() class Meta: abstract = True indexes = [ models.Index(fields=["tenant", "-created_at"]), models.Index(fields=["tenant", "is_deleted"]), ]