Compare commits
2 Commits
5dedd19c0a
...
aaf398196a
| Author | SHA1 | Date | |
|---|---|---|---|
| aaf398196a | |||
| b9245cd891 |
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 4.2.16 on 2026-04-30 04:44
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fonrey_permission_def', '0001_initial'),
|
||||
('fonrey_permission', '0003_alter_permissionchangelog_action_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='rolepermission',
|
||||
name='permission_def',
|
||||
field=models.ForeignKey(help_text='RESTRICT 防止删除仍被引用的权限项', on_delete=django.db.models.deletion.PROTECT, related_name='role_assignments', to='fonrey_permission_def.permissiondef', verbose_name='权限定义'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='staffpermissionoverride',
|
||||
name='permission_def',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='staff_overrides', to='fonrey_permission_def.permissiondef', verbose_name='被覆盖权限项'),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='PermissionDef',
|
||||
),
|
||||
]
|
||||
@@ -1,4 +1,3 @@
|
||||
from apps.permission.models.permission_def import PermissionDef
|
||||
from apps.permission.models.role import Role, RolePermission
|
||||
from apps.permission.models.staff_perm import (
|
||||
PermissionChangeLog,
|
||||
@@ -9,7 +8,6 @@ from apps.permission.models.staff_perm import (
|
||||
|
||||
__all__ = [
|
||||
"PermissionChangeLog",
|
||||
"PermissionDef",
|
||||
"Role",
|
||||
"RolePermission",
|
||||
"StaffDataScope",
|
||||
|
||||
@@ -91,7 +91,7 @@ class RolePermission(TimeStampedModel):
|
||||
help_text="稀疏存储:角色删除时级联清理权限值",
|
||||
)
|
||||
permission_def = models.ForeignKey(
|
||||
"fonrey_permission.PermissionDef",
|
||||
"fonrey_permission_def.PermissionDef",
|
||||
on_delete=models.PROTECT,
|
||||
related_name="role_assignments",
|
||||
verbose_name="权限定义",
|
||||
|
||||
@@ -86,7 +86,7 @@ class StaffPermissionOverride(UUIDPrimaryKeyModel):
|
||||
help_text="员工删除时级联删除覆盖记录",
|
||||
)
|
||||
permission_def = models.ForeignKey(
|
||||
"fonrey_permission.PermissionDef",
|
||||
"fonrey_permission_def.PermissionDef",
|
||||
on_delete=models.PROTECT,
|
||||
related_name="staff_overrides",
|
||||
verbose_name="被覆盖权限项",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
from apps.permission.services.seed_default_roles import seed_default_roles
|
||||
|
||||
__all__ = ["seed_default_roles"]
|
||||
|
||||
218
apps/permission/services/seed_default_roles.py
Normal file
218
apps/permission/services/seed_default_roles.py
Normal file
@@ -0,0 +1,218 @@
|
||||
import logging
|
||||
|
||||
from django_tenants.utils import schema_context
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_ROLES = [
|
||||
{"name": "置业顾问", "category": "agent"},
|
||||
{"name": "店管", "category": "store_manager"},
|
||||
{"name": "区管", "category": "custom"},
|
||||
{"name": "区总", "category": "custom"},
|
||||
{"name": "副总", "category": "custom"},
|
||||
{"name": "总经", "category": "director"},
|
||||
{"name": "其他职能", "category": "operator"},
|
||||
]
|
||||
|
||||
_T = True
|
||||
_F = False
|
||||
_SELF = "self"
|
||||
_DEPT = "dept"
|
||||
_ALL = "all"
|
||||
_NONE = "none"
|
||||
|
||||
_MATRIX = {
|
||||
"property.listing.create": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.view_scope": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.listing.view_public": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.view_private": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.set_public": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.set_private": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.set_locked": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.set_special": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.delete": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.restore": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.export": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.edit_description": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.view_deal": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.price_read": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.view_history": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.view_owner_others": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.set_protected": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.view_protected": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.listing.change_keeper": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.listing.merge_duplicate": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.status_sold": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.grade_set_a": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.listing.grade_set_e": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"property.contact.view_phone": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.contact.view_phone_limit": [20, -1, -1, -1, -1, -1, 0],
|
||||
"property.contact.add_contact": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.contact.edit_core": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.contact.edit_basic": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.contact.delete_contact": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"property.contact.view_cert": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.contact.view_operation_log":[_F, _T, _T, _T, _T, _T, _F],
|
||||
"property.address.view_detail": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.address.view_limit": [10, -1, -1, -1, -1, -1, 0],
|
||||
"property.address.edit": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.key.create": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.key.edit": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.key.return": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.key.view_password": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.key.view_number": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.key.borrow": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.key.give_back": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.key.delete": [_NONE, _SELF, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.key.export": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"property.survey.create_photo": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.survey.download_photo": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.survey.delete_photo": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.survey.create": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.survey.view": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.survey.upload_video": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.survey.download_video": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.survey.play_video": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.mandate.create": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.mandate.renew": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.mandate.view": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.mandate.revoke": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.mandate.export": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"property.follow.view_scope": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.follow.hide": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.follow.view_hidden": [_NONE, _SELF, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.follow.pin": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.attachment.create": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.attachment.view": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.attachment.edit": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.attachment.download": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"property.attachment.delete": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"property.showing.view_scope": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.private.create": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"client.private.view": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.private.view_protected": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.private.edit": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.private.edit_protected": [_SELF, _SELF, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.private.set_protected": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.private.to_public": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.private.export": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"client.public.view": [_NONE, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.public.to_private": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"client.public.edit": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"client.public.change_status": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"client.deal.view": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.deal.view_public": [_NONE, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.deal.re_transaction": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"client.deal.export": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"client.contact.view_phone_private": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.contact.view_phone_protected":[_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.contact.view_phone_public": [_NONE, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.contact.view_phone_limit": [20, -1, -1, -1, -1, -1, 0],
|
||||
"client.contact.edit_contact": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.contact.edit_phone": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.mgmt.delete": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"client.mgmt.to_deal": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"client.mgmt.change_staff": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.mgmt.batch_change_staff": [_NONE, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.mgmt.view_operation_log": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"client.mgmt.merge_private": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"client.showing.create": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"client.showing.view": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.showing.edit": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.archive.view": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"client.archive.import": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"client.archive.view_phone": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"client.archive.delete": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"client.archive.view_log": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"home.dashboard.view_version": [_T, _T, _T, _T, _T, _T, _T],
|
||||
"home.dashboard.personal_rank": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"home.dashboard.dept_rank": [_NONE, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"home.dashboard.manage_praise": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"complex.view": [_T, _T, _T, _T, _T, _T, _T],
|
||||
"complex.view_structure": [_T, _T, _T, _T, _T, _T, _T],
|
||||
"complex.create": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"complex.create_unit": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"complex.edit": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"complex.edit_unit": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"complex.delete": [_F, _F, _T, _T, _T, _T, _F],
|
||||
"complex.delete_unit": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"complex.delete_with_property": [_F, _F, _F, _T, _T, _T, _F],
|
||||
"complex.merge": [_F, _F, _T, _T, _T, _T, _F],
|
||||
"complex.move_unit": [_F, _F, _T, _T, _T, _T, _F],
|
||||
"complex.lock": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"complex.view_deal": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"complex.view_deal_detail": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"complex.view_address_scope": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"complex.region_manage": [_F, _F, _T, _T, _T, _T, _F],
|
||||
"complex.material.view_photo": [_T, _T, _T, _T, _T, _T, _T],
|
||||
"complex.material.manage_photo": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"complex.material.delete_photo": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"complex.material.download_photo": [_T, _T, _T, _T, _T, _T, _T],
|
||||
"complex.material.view_attachment": [_T, _T, _T, _T, _T, _T, _T],
|
||||
"complex.material.manage_attachment": [_T, _T, _T, _T, _T, _T, _F],
|
||||
"complex.material.download_attachment":[_T, _T, _T, _T, _T, _T, _T],
|
||||
"complex.material.delete_attachment": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"complex.material.view_surrounding": [_T, _T, _T, _T, _T, _T, _T],
|
||||
"complex.feedback.view": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"complex.feedback.handle": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"org.view_structure": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _SELF],
|
||||
"org.view_dept": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"org.edit_dept": [_F, _F, _T, _T, _T, _T, _F],
|
||||
"org.view_staff": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"org.edit_staff": [_F, _F, _T, _T, _T, _T, _F],
|
||||
"org.edit_staff_detail": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"org.freeze_account": [_F, _F, _T, _T, _T, _T, _F],
|
||||
"org.import_staff": [_F, _F, _T, _T, _T, _T, _F],
|
||||
"org.export_staff": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"org.view_permission": [_F, _F, _F, _T, _T, _T, _F],
|
||||
"org.edit_permission": [_F, _F, _F, _T, _T, _T, _F],
|
||||
"org.export_permission": [_F, _F, _F, _T, _T, _T, _F],
|
||||
"org.edit_position": [_F, _F, _F, _T, _T, _T, _F],
|
||||
"org.edit_role": [_F, _F, _F, _T, _T, _T, _F],
|
||||
"org.view_store_list": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"org.export_store_list": [_F, _F, _T, _T, _T, _T, _F],
|
||||
"org.view_contact_book": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _SELF],
|
||||
"org.transfer_business": [_NONE, _DEPT, _DEPT, _ALL, _ALL, _ALL, _NONE],
|
||||
"org.resign_apply": [_SELF, _DEPT, _DEPT, _ALL, _ALL, _ALL, _SELF],
|
||||
"org.invite_onboard": [_F, _T, _T, _T, _T, _T, _F],
|
||||
"org.view_contact_phone_limit": [5, -1, -1, -1, -1, -1, 5],
|
||||
}
|
||||
|
||||
|
||||
def seed_default_roles(schema_name: str) -> None:
|
||||
from django.apps import apps
|
||||
|
||||
Role = apps.get_model("fonrey_permission", "Role")
|
||||
RolePermission = apps.get_model("fonrey_permission", "RolePermission")
|
||||
PermissionDef = apps.get_model("fonrey_permission_def", "PermissionDef")
|
||||
|
||||
perm_map = {p.code: p for p in PermissionDef.objects.all()}
|
||||
|
||||
roles = []
|
||||
for role_def in _ROLES:
|
||||
role, _ = Role.objects.get_or_create(
|
||||
name=role_def["name"],
|
||||
defaults={
|
||||
"category": role_def["category"],
|
||||
"is_system_builtin": True,
|
||||
"is_active": True,
|
||||
},
|
||||
)
|
||||
roles.append(role)
|
||||
|
||||
rp_objects = []
|
||||
for code, values in _MATRIX.items():
|
||||
perm = perm_map.get(code)
|
||||
if perm is None:
|
||||
logger.warning("PermissionDef not found: %s", code)
|
||||
continue
|
||||
for role, val in zip(roles, values):
|
||||
rp_objects.append(
|
||||
RolePermission(
|
||||
role=role,
|
||||
permission_def=perm,
|
||||
value={"v": val},
|
||||
)
|
||||
)
|
||||
|
||||
RolePermission.objects.bulk_create(rp_objects, ignore_conflicts=True)
|
||||
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')],
|
||||
},
|
||||
),
|
||||
]
|
||||
2358
apps/permission_def/migrations/0002_seed_permission_defs.py
Normal file
2358
apps/permission_def/migrations/0002_seed_permission_defs.py
Normal file
File diff suppressed because it is too large
Load Diff
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"]
|
||||
@@ -0,0 +1,3 @@
|
||||
from apps.setting.services.seed_default_lookups import seed_default_lookups
|
||||
|
||||
__all__ = ["seed_default_lookups"]
|
||||
|
||||
113
apps/setting/services/seed_default_lookups.py
Normal file
113
apps/setting/services/seed_default_lookups.py
Normal file
@@ -0,0 +1,113 @@
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_GROUPS = [
|
||||
{"module": "client", "key": "source", "label_zh": "客源来源", "description": "客源从何处获取,用于来源渠道分析", "sort_order": 1},
|
||||
{"module": "client", "key": "follow_purpose", "label_zh": "跟进目的", "description": "客源跟进时选择的目的分类", "sort_order": 2},
|
||||
{"module": "property", "key": "source", "label_zh": "房源来源", "description": "房源从何处获取", "sort_order": 3},
|
||||
]
|
||||
|
||||
_ITEMS = {
|
||||
("client", "source"): [
|
||||
{"value": "store_reception", "label_zh": "门店接待", "sort_order": 1},
|
||||
{"value": "old_client_referral", "label_zh": "老客户转介绍", "sort_order": 2},
|
||||
{"value": "stationed_dispatch", "label_zh": "驻守派单", "sort_order": 3},
|
||||
{"value": "walk_in", "label_zh": "上门", "sort_order": 4},
|
||||
{"value": "online_58", "label_zh": "网络-58同城", "sort_order": 5},
|
||||
{"value": "online_anjuke", "label_zh": "网络-安居客", "sort_order": 6},
|
||||
{"value": "wechat", "label_zh": "微信", "sort_order": 7},
|
||||
{"value": "friend_referral", "label_zh": "朋友介绍", "sort_order": 8},
|
||||
],
|
||||
("client", "follow_purpose"): [
|
||||
{"value": "callback", "label_zh": "回拨", "sort_order": 1},
|
||||
{"value": "push_property", "label_zh": "推房", "sort_order": 2},
|
||||
{"value": "showing", "label_zh": "带看", "sort_order": 3},
|
||||
{"value": "maintain", "label_zh": "维护", "sort_order": 4},
|
||||
{"value": "other", "label_zh": "其他", "sort_order": 5},
|
||||
],
|
||||
("property", "source"): [
|
||||
{"value": "proactive_development", "label_zh": "主动开发", "sort_order": 1},
|
||||
{"value": "owner_walk_in", "label_zh": "业主上门", "sort_order": 2},
|
||||
{"value": "old_client_referral", "label_zh": "老客户转介绍", "sort_order": 3},
|
||||
{"value": "online_inquiry", "label_zh": "网络来电", "sort_order": 4},
|
||||
],
|
||||
}
|
||||
|
||||
_TENANT_SETTINGS = [
|
||||
{"category": "client", "key": "duplicate_check_scope", "value": {"v": "self"}, "value_type": "enum"},
|
||||
]
|
||||
|
||||
_FIELD_RULES = [
|
||||
{"module": "property", "entity_type": "residential", "trade_status": "sale", "field_key": "orientation", "requirement": "optional"},
|
||||
{"module": "property", "entity_type": "residential", "trade_status": "sale", "field_key": "decoration", "requirement": "optional"},
|
||||
{"module": "property", "entity_type": "residential", "trade_status": "sale", "field_key": "floor", "requirement": "optional"},
|
||||
{"module": "property", "entity_type": "residential", "trade_status": "sale", "field_key": "building_area", "requirement": "required"},
|
||||
{"module": "property", "entity_type": "residential", "trade_status": "sale", "field_key": "inner_area", "requirement": "optional"},
|
||||
{"module": "property", "entity_type": "residential", "trade_status": "sale", "field_key": "room_layout", "requirement": "required"},
|
||||
{"module": "property", "entity_type": "residential", "trade_status": "rent", "field_key": "decoration", "requirement": "optional"},
|
||||
{"module": "property", "entity_type": "residential", "trade_status": "rent", "field_key": "floor", "requirement": "optional"},
|
||||
{"module": "property", "entity_type": "residential", "trade_status": "rent", "field_key": "building_area", "requirement": "required"},
|
||||
{"module": "property", "entity_type": "residential", "trade_status": "rent", "field_key": "room_layout", "requirement": "required"},
|
||||
]
|
||||
|
||||
|
||||
def seed_default_lookups(schema_name: str) -> None:
|
||||
from django.apps import apps
|
||||
|
||||
LookupGroup = apps.get_model("fonrey_setting", "LookupGroup")
|
||||
LookupItem = apps.get_model("fonrey_setting", "LookupItem")
|
||||
TenantSetting = apps.get_model("fonrey_setting", "TenantSetting")
|
||||
FieldRequirementRule = apps.get_model("fonrey_setting", "FieldRequirementRule")
|
||||
|
||||
group_map = {}
|
||||
for gd in _GROUPS:
|
||||
group, _ = LookupGroup.objects.get_or_create(
|
||||
module=gd["module"],
|
||||
key=gd["key"],
|
||||
defaults={
|
||||
"label_zh": gd["label_zh"],
|
||||
"description": gd["description"],
|
||||
"sort_order": gd["sort_order"],
|
||||
},
|
||||
)
|
||||
group_map[(gd["module"], gd["key"])] = group
|
||||
|
||||
item_objects = []
|
||||
for (module, key), items in _ITEMS.items():
|
||||
group = group_map[(module, key)]
|
||||
for item in items:
|
||||
item_objects.append(
|
||||
LookupItem(
|
||||
group=group,
|
||||
value=item["value"],
|
||||
label_zh=item["label_zh"],
|
||||
is_system=True,
|
||||
is_active=True,
|
||||
sort_order=item["sort_order"],
|
||||
)
|
||||
)
|
||||
LookupItem.objects.bulk_create(item_objects, ignore_conflicts=True)
|
||||
|
||||
ts_objects = [
|
||||
TenantSetting(
|
||||
category=ts["category"],
|
||||
key=ts["key"],
|
||||
value=ts["value"],
|
||||
value_type=ts["value_type"],
|
||||
)
|
||||
for ts in _TENANT_SETTINGS
|
||||
]
|
||||
TenantSetting.objects.bulk_create(ts_objects, ignore_conflicts=True)
|
||||
|
||||
rule_objects = [
|
||||
FieldRequirementRule(
|
||||
module=r["module"],
|
||||
entity_type=r["entity_type"],
|
||||
trade_status=r["trade_status"],
|
||||
field_key=r["field_key"],
|
||||
requirement=r["requirement"],
|
||||
)
|
||||
for r in _FIELD_RULES
|
||||
]
|
||||
FieldRequirementRule.objects.bulk_create(rule_objects, ignore_conflicts=True)
|
||||
@@ -5,3 +5,8 @@ class TenantConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "apps.tenant"
|
||||
label = "tenant"
|
||||
|
||||
def ready(self):
|
||||
from apps.tenant import signals # noqa: F401
|
||||
|
||||
signals._register()
|
||||
|
||||
36
apps/tenant/signals.py
Normal file
36
apps/tenant/signals.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import logging
|
||||
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_tenant_model():
|
||||
from django.apps import apps
|
||||
return apps.get_model("tenant", "Tenant")
|
||||
|
||||
|
||||
def _register():
|
||||
Tenant = _get_tenant_model()
|
||||
|
||||
@receiver(post_save, sender=Tenant)
|
||||
def on_tenant_created(sender, instance, created, **kwargs):
|
||||
if not created:
|
||||
return
|
||||
if instance.schema_name == "public":
|
||||
return
|
||||
|
||||
from django_tenants.utils import schema_context
|
||||
|
||||
from apps.permission.services import seed_default_roles
|
||||
from apps.setting.services import seed_default_lookups
|
||||
|
||||
try:
|
||||
with schema_context(instance.schema_name):
|
||||
seed_default_roles(instance.schema_name)
|
||||
seed_default_lookups(instance.schema_name)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"Failed to seed defaults for tenant %s", instance.schema_name
|
||||
)
|
||||
@@ -15,6 +15,7 @@ SHARED_APPS = [
|
||||
"django_tenants", # MUST be first per django-tenants
|
||||
"apps.tenant",
|
||||
"apps.release",
|
||||
"apps.permission_def",
|
||||
"shared",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.auth",
|
||||
|
||||
Reference in New Issue
Block a user