feat(permission): extract PermissionDef into shared apps.permission_def
Move PermissionDef out of TENANT_APPS into a new SHARED app so all tenants read a single global definition table in public schema. - new app apps.permission_def (label=fonrey_permission_def) - db_table preserved as permission_defs (no rename) - FK refs updated: fonrey_permission.PermissionDef -> fonrey_permission_def.PermissionDef - migrations: permission_def/0001_initial creates table in public, permission/0004 drops the now-orphan table from tenant schemas
This commit is contained in:
0
apps/permission_def/__init__.py
Normal file
0
apps/permission_def/__init__.py
Normal file
8
apps/permission_def/apps.py
Normal file
8
apps/permission_def/apps.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PermissionDefConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "apps.permission_def"
|
||||
label = "fonrey_permission_def"
|
||||
verbose_name = "权限定义(全局共享)"
|
||||
46
apps/permission_def/migrations/0001_initial.py
Normal file
46
apps/permission_def/migrations/0001_initial.py
Normal file
@@ -0,0 +1,46 @@
|
||||
# Generated by Django 4.2.16 on 2026-04-30 04:41
|
||||
|
||||
import django.contrib.postgres.fields
|
||||
from django.db import migrations, models
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PermissionDef',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('code', models.CharField(help_text='规则:{module}.{sub_module}.{action}[.{qualifier}]', max_length=150, unique=True, verbose_name='权限编码')),
|
||||
('module', models.CharField(choices=[('home', '首页'), ('property', '房源'), ('new_house', '新房'), ('client', '客源'), ('transaction', '交易'), ('data', '数据'), ('marketing', '营销'), ('hr', '人事OA'), ('contract', '合同'), ('trinet', '三网'), ('system', '系统'), ('mobile', '移动端'), ('smart_store', '智能门店'), ('recharge', '在线充值')], help_text='home/property/new_house/client/transaction/data/marketing/hr/contract/trinet/system/mobile/smart_store/recharge', max_length=50, verbose_name='一级模块')),
|
||||
('sub_module', models.CharField(blank=True, default='', help_text='如「二手&租赁」「商圈精耕」', max_length=50, verbose_name='二级模块')),
|
||||
('group_name', models.CharField(help_text='如「私客基础权限」「联系人基础权限」', max_length=100, verbose_name='分组标题')),
|
||||
('name', models.CharField(max_length=200, verbose_name='显示名称')),
|
||||
('description', models.TextField(blank=True, default='', verbose_name='权限作用描述')),
|
||||
('value_type', models.CharField(choices=[('boolean', '开关型'), ('scope', '范围型'), ('integer', '数值型')], help_text='BOOLEAN=开关型 / SCOPE=范围型 / INTEGER=数值型', max_length=20, verbose_name='权限值类型')),
|
||||
('scope_choices', models.JSONField(blank=True, default=list, help_text='仅 SCOPE 类型有效,可选枚举 code 列表,如 ["none","self","store","company"]', verbose_name='可选范围')),
|
||||
('integer_min', models.IntegerField(blank=True, help_text='仅 INTEGER 类型有效', null=True, verbose_name='最小值')),
|
||||
('integer_max', models.IntegerField(blank=True, help_text='仅 INTEGER 类型有效;NULL=无上限(业务上 0 通常代表不限制)', null=True, verbose_name='最大值')),
|
||||
('default_value', models.JSONField(default=dict, help_text='系统最小默认值,格式 {"v": <value>}', verbose_name='默认值')),
|
||||
('max_allowed_categories', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=50), blank=True, default=list, help_text='允许配置此权限的角色类别列表,空数组=所有类别均可', size=None, verbose_name='可配置角色类别')),
|
||||
('sort_order', models.PositiveIntegerField(default=0, help_text='分组内排序', verbose_name='排序顺序')),
|
||||
('is_active', models.BooleanField(default=True, help_text='下线权限项置 FALSE,历史记录保留', verbose_name='是否启用')),
|
||||
('is_deprecated', models.BooleanField(default=False, help_text='不再推荐使用但保持兼容', verbose_name='是否废弃')),
|
||||
('version', models.PositiveIntegerField(default=1, help_text='变更时递增,用于缓存失效', verbose_name='定义版本')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '权限定义',
|
||||
'verbose_name_plural': '权限定义',
|
||||
'db_table': 'permission_defs',
|
||||
'indexes': [models.Index(condition=models.Q(('is_active', True)), fields=['module', 'sub_module', 'sort_order'], name='idx_perm_defs_module'), models.Index(condition=models.Q(('is_active', True)), fields=['is_active'], name='idx_perm_defs_active')],
|
||||
},
|
||||
),
|
||||
]
|
||||
0
apps/permission_def/migrations/__init__.py
Normal file
0
apps/permission_def/migrations/__init__.py
Normal file
3
apps/permission_def/models/__init__.py
Normal file
3
apps/permission_def/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from apps.permission_def.models.permission_def import PermissionDef
|
||||
|
||||
__all__ = ["PermissionDef"]
|
||||
117
apps/permission_def/models/permission_def.py
Normal file
117
apps/permission_def/models/permission_def.py
Normal file
@@ -0,0 +1,117 @@
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.db import models
|
||||
|
||||
from core.enums import PermissionModule, PermissionValueType
|
||||
from core.models.base import TimeStampedModel
|
||||
|
||||
|
||||
class PermissionDef(TimeStampedModel):
|
||||
code = models.CharField(
|
||||
max_length=150,
|
||||
unique=True,
|
||||
verbose_name="权限编码",
|
||||
help_text='规则:{module}.{sub_module}.{action}[.{qualifier}]',
|
||||
)
|
||||
module = models.CharField(
|
||||
max_length=50,
|
||||
choices=PermissionModule.choices,
|
||||
verbose_name="一级模块",
|
||||
help_text="home/property/new_house/client/transaction/data/marketing/hr/contract/trinet/system/mobile/smart_store/recharge",
|
||||
)
|
||||
sub_module = models.CharField(
|
||||
max_length=50,
|
||||
blank=True,
|
||||
default="",
|
||||
verbose_name="二级模块",
|
||||
help_text='如「二手&租赁」「商圈精耕」',
|
||||
)
|
||||
group_name = models.CharField(
|
||||
max_length=100,
|
||||
verbose_name="分组标题",
|
||||
help_text='如「私客基础权限」「联系人基础权限」',
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=200,
|
||||
verbose_name="显示名称",
|
||||
)
|
||||
description = models.TextField(
|
||||
blank=True,
|
||||
default="",
|
||||
verbose_name="权限作用描述",
|
||||
)
|
||||
value_type = models.CharField(
|
||||
max_length=20,
|
||||
choices=PermissionValueType.choices,
|
||||
verbose_name="权限值类型",
|
||||
help_text="BOOLEAN=开关型 / SCOPE=范围型 / INTEGER=数值型",
|
||||
)
|
||||
scope_choices = models.JSONField(
|
||||
default=list,
|
||||
blank=True,
|
||||
verbose_name="可选范围",
|
||||
help_text='仅 SCOPE 类型有效,可选枚举 code 列表,如 ["none","self","store","company"]',
|
||||
)
|
||||
integer_min = models.IntegerField(
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="最小值",
|
||||
help_text="仅 INTEGER 类型有效",
|
||||
)
|
||||
integer_max = models.IntegerField(
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="最大值",
|
||||
help_text="仅 INTEGER 类型有效;NULL=无上限(业务上 0 通常代表不限制)",
|
||||
)
|
||||
default_value = models.JSONField(
|
||||
default=dict,
|
||||
verbose_name="默认值",
|
||||
help_text='系统最小默认值,格式 {"v": <value>}',
|
||||
)
|
||||
max_allowed_categories = ArrayField(
|
||||
models.CharField(max_length=50),
|
||||
default=list,
|
||||
blank=True,
|
||||
verbose_name="可配置角色类别",
|
||||
help_text="允许配置此权限的角色类别列表,空数组=所有类别均可",
|
||||
)
|
||||
sort_order = models.PositiveIntegerField(
|
||||
default=0,
|
||||
verbose_name="排序顺序",
|
||||
help_text="分组内排序",
|
||||
)
|
||||
is_active = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name="是否启用",
|
||||
help_text="下线权限项置 FALSE,历史记录保留",
|
||||
)
|
||||
is_deprecated = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name="是否废弃",
|
||||
help_text="不再推荐使用但保持兼容",
|
||||
)
|
||||
version = models.PositiveIntegerField(
|
||||
default=1,
|
||||
verbose_name="定义版本",
|
||||
help_text="变更时递增,用于缓存失效",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
db_table = "permission_defs"
|
||||
verbose_name = "权限定义"
|
||||
verbose_name_plural = "权限定义"
|
||||
indexes = [
|
||||
models.Index(
|
||||
fields=["module", "sub_module", "sort_order"],
|
||||
name="idx_perm_defs_module",
|
||||
condition=models.Q(is_active=True),
|
||||
),
|
||||
models.Index(
|
||||
fields=["is_active"],
|
||||
name="idx_perm_defs_active",
|
||||
condition=models.Q(is_active=True),
|
||||
),
|
||||
]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.code} ({self.value_type})"
|
||||
Reference in New Issue
Block a user