20 KiB
Fonrey 项目骨架搭建实施报告 — v2
版本:v2.0
报告日期:2026-04-30
实施范围:Phase 1 配置 → Phase 2 数据模型 → Phase 3 前端/Docker 脚手架 → Phase 4.0 模型 verbose_name → Phase 4.1 字段 verbose_name/help_text → Phase 5 PermissionDef 拆分 + 154 条 seed + 7 内置角色 + 矩阵 + Lookup 默认值 + 租户自动 seed
实施依据:prompt/提示词模板/创建项目骨架提示词_v2.3.md、PRD/权限管理/PERMISSION_SEED_MVP_BATCH1.md、PRD/权限管理/权限管理模块PRD.md §5.5.2、PRD/权限管理/角色权限矩阵.md、DATA_MODEL/DATA_MODEL_SETTING.md §2.3
项目根目录:/mnt/c/project/fonrey/
Git HEAD:aaf3981(main 分支,已 push 到 origin/main)
v1 → v2 增量:14 个 commit(79c3cf2 … aaf3981),覆盖 Phase 4.0、4.1、5
一、自 v1 以来的执行摘要
v1 报告(2026-04-29)覆盖 Phase 1–3 骨架。v2 在不改动 v1 的前提下追加 Phase 4.0、4.1、5 的实施记录与最终交付状态。
1.1 Phase 4.0 — 模型级 verbose_name(commit 79c3cf2)
为全部 74 个具体 ORM 模型(不含抽象基类)补齐 Meta.verbose_name / verbose_name_plural 中文显示名,对齐 DATA_MODEL_*.md。
1.2 Phase 4.1 — 字段级 verbose_name + help_text(commits 3638fc0 … 8faa68b,9 commit)
为全部 781 个字段补齐中文 verbose_name 与 help_text,按 9 个业务模块串行落盘:
| 顺序 | 模块 | commit |
|---|---|---|
| 1 | property | 3638fc0 |
| 2 | client | e67b07a |
| 3 | complex | a3800bf |
| 4 | org | f185127 |
| 5 | account | b57070f |
| 6 | permission | 9ef6eb6 |
| 7 | setting | 289ec43 |
| 8 | region | e3b26ce |
| 9 | tenant | 8faa68b |
随后单独一个 commit d00ff12 生成对应的 9 份 verbose_name/help_text Alter migration(共 3880 行)。
1.3 Phase 5 — 权限种子与租户自动初始化
| 子任务 | commit | 说明 |
|---|---|---|
| 拆 PermissionDef 到 SHARED app | b9245cd |
新建 apps.permission_def(label fonrey_permission_def),保留表名 permission_defs;fonrey_permission.PermissionDef FK 字符串改为 fonrey_permission_def.PermissionDef |
| 154 条 PermissionDef seed + 7 内置角色 + 154×7 矩阵 + Lookup 默认值 + 租户 post_save 自动 seed | aaf3981 |
data migration + 三个 service + 一个 signal |
1.4 最终验证
python manage.py check✅ 0 issuespython manage.py makemigrations --dry-run✅ No changes detected- 154 条 PermissionDef 全部就位(migration 文件 grep
"code":计数 = 154) - 154 条 RolePermission 矩阵全部就位(service 文件 matrix dict 键数 = 154)
- main 已 push 到 origin/main
二、v1 → v2 顶层变化
| 维度 | v1 (2026-04-29) | v2 (2026-04-30) |
|---|---|---|
| App 数 | 10 | 11(新增 apps.permission_def) |
| ORM 模型数 | 77 | 77(PermissionDef 从 permission 迁移到 permission_def) |
| Migration 文件数 | 12 | 33 |
| 业务代码 LoC(不含 migrations) | ~7332 | ~8095 |
| Phase 4 verbose_name/help_text | 未做 | ✅ 全部 781 字段 + 74 模型 |
| PermissionDef seed | 未做(v1 列入"未交付") | ✅ 154 条 |
| 内置角色 + 矩阵 | 未做(v1 列入"未交付") | ✅ 7 角色 + 154×7 |
| LookupItem 默认值 | 未做(v1 列入"未交付") | ✅ |
| 租户自动 seed | 未做 | ✅ apps.tenant.signals post_save |
三、Phase 4.0/4.1 实施细节
3.1 verbose_name 与 help_text 来源
权威源为 /mnt/d/Workspace/nexus/Project/fonrey/DATA_MODEL/DATA_MODEL_*.md(9 个模块文件 + ENUMS.md v2.2)。补齐策略:
- 模型
Meta.verbose_name取 PRD 中文表名(如verbose_name = "房源") - 字段
verbose_name取 PRD 字段中文标题 - 字段
help_text取 PRD 字段 "说明" 列;ENUM 字段统一采用code=中文对照格式(如"public=公立 / private=私立") - BinaryField 加密手机号字段统一标注"AES-256-GCM 密文"
3.2 verbose_name migration 策略
由于 verbose_name/help_text 不影响 schema,全部归并为 9 个 AlterField migration(每模块一份),不与原有 schema migration 混用。d00ff12 一次性生成全部 9 份,dry-run 后 manage.py check 与 makemigrations --dry-run 双双干净。
3.3 受影响模型
74 个具体模型全部覆盖(不含抽象基类 UUIDPrimaryKeyModel / TimeStampedModel / SoftDeleteModel / AuditedModel)。
四、Phase 5 实施细节
4.1 PermissionDef 拆 SHARED 的动机与实现
问题:v1 阶段 PermissionDef 在 apps.permission(TENANT_APPS)。这意味着每个新租户都要复制全部 154 条权限定义;权限矩阵升级时需要在每个 schema 重复 migrate;管理员视角无法对全局权限做统一编辑。
方案:拆出独立 SHARED app apps.permission_def(label fonrey_permission_def),保留物理表名 permission_defs(避免 RENAME)。所有租户的 Role.permission_def / StaffPermissionOverride.permission_def FK 跨 schema 指向 public.permission_defs(django-tenants 默认 search_path 包含 public,FK 自然解析)。
FK 字符串改写:
| 文件 | 行 | 原 | 新 |
|---|---|---|---|
apps/permission/models/role.py |
94 | "fonrey_permission.PermissionDef" |
"fonrey_permission_def.PermissionDef" |
apps/permission/models/staff_perm.py |
89 | "fonrey_permission.PermissionDef" |
"fonrey_permission_def.PermissionDef" |
Migration 顺序(fresh DB,无历史数据):
permission_def/0001_initial:在 public schemaCREATE TABLE permission_defspermission/0004_alter_..._delete_permissiondef:依赖permission_def/0001,对每个 tenant schema 既AlterField(FK 字符串切换,Django state 一致性)也DeleteModel(PermissionDef)(清理 tenant schema 中本不应存在的孤儿表)permission_def/0002_seed_permission_defs:在 public schema bulk_create 154 条
SHARED_APPS 顺序(config/settings/base.py):
SHARED_APPS = [
"django_tenants",
"apps.tenant",
"apps.release",
"apps.permission_def", # 新增
"shared",
...
]
4.2 154 条 PermissionDef seed
文件:apps/permission_def/migrations/0002_seed_permission_defs.py(2358 行)
实现:
def seed(apps, schema_editor):
PermissionDef = apps.get_model("fonrey_permission_def", "PermissionDef")
PermissionDef.objects.bulk_create([PermissionDef(**d) for d in PERMISSION_DEFS])
def unseed(apps, schema_editor):
PermissionDef = apps.get_model("fonrey_permission_def", "PermissionDef")
PermissionDef.objects.filter(code__in=[d["code"] for d in PERMISSION_DEFS]).delete()
公共元数据(每条都含):is_active=True, is_deprecated=False, is_system=True, version=1。
按权威源 PERMISSION_SEED_MVP_BATCH1.md 共 3 批:
| 批 | 模块 | 条数 | 主代码前缀 |
|---|---|---|---|
| 1 | property | 66 | property.listing.*、property.contact.*、property.address.*、property.key.*、property.commission.*、property.image.* |
| 2 | client | 36 | client.list.*、client.contact.*、client.viewing.*、client.match.*、client.transaction.* |
| 3 | home + complex + org | 52 | home.*、complex.*、org.* |
| 合计 | 154 |
module 字段使用 PermissionModule enum:org.* 代码 → module="hr"(PRD 组织人事即 HR 模块);complex.* 代码 → module="property"(小区是房源子集)。
4.3 7 个内置角色 + 154×7 矩阵 service
文件:apps/permission/services/seed_default_roles.py(218 行)
7 角色映射到 PermissionRoleCategory 枚举(仅 agent / store_manager / director / operator / custom 5 选项):
| 角色名 | category |
|---|---|
| 置业顾问 | agent |
| 店管 | store_manager |
| 区管 | custom |
| 区总 | custom |
| 副总 | custom |
| 总经 | director |
| 其他职能 | operator |
矩阵符号转写:
| PRD 符号 | 落库 value |
|---|---|
✓ |
{"v": True} |
✗ |
{"v": False} |
本人 / 本部 / 全部 / — |
{"v": "self"} / {"v": "dept"} / {"v": "all"} / {"v": "none"} |
| 整型 N | {"v": N} |
∞ |
{"v": -1} |
入口:def seed_default_roles(schema_name: str) -> None。调用方在 schema_context 内调用即可(参数 schema_name 仅作日志 hint)。
实现:Role.objects.get_or_create(name=..., defaults={...}) 7 次 → 遍历 154 个 code,按 {code: PermissionDef} 建表,组装 7×154 个 RolePermission → bulk_create(ignore_conflicts=True)。
幂等保证:get_or_create + bulk_create(ignore_conflicts=True);对缺失 code 仅 logger.warning 跳过、不抛错。
4.4 LookupItem 默认值 service
文件:apps/setting/services/seed_default_lookups.py(113 行)
依据 DATA_MODEL_SETTING.md §2.3,注入:
- 3 个
LookupGroup(按模块) - 各组的
LookupItem默认枚举值 - 1 个
TenantSetting兜底默认行 FieldRequirementRule默认规则
入口:def seed_default_lookups(schema_name: str) -> None。同样依赖调用方提供 schema 上下文。
4.5 租户自动 seed signal
文件:apps/tenant/signals.py(36 行)
def _register():
Tenant = apps.get_model("tenant", "Tenant")
@receiver(post_save, sender=Tenant)
def on_tenant_created(sender, instance, created, **kwargs):
if not created or instance.schema_name == "public":
return
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)
注册:apps/tenant/apps.py ready() 调用 signals._register()(lazy 注册避开 Django app loading 早期 model 引用)。
容错:try/except 包裹 seed 调用,失败仅记录日志,不阻断租户创建本身。
4.6 Phase 5 关键决策
| 决策 | 原因 |
|---|---|
| PermissionDef 拆出 SHARED 而非 RENAME 表 | 避免破坏式迁移,保持表名 permission_defs |
| seed 用 RunPython data migration 而非 fixtures | 项目偏好,便于 --reverse 与 apps.get_model 历史模型 |
| 角色 + 矩阵 + Lookup 用 service 函数而非 data migration | 三者写入 tenant schema,需 schema_context;migration 只走 default 连接 |
| tenant post_save 自动调用 service | 用户明确选择"新建租户时自动 seed",避免 ops 手动跑命令 |
不 seed staff_data_scopes 表 |
用户明确指示;scope 类 PermissionDef 的 default_value 已承担兜底 |
| Platform admin 角色不在本批 | 用户明确指示,归 apps.admin_console(待建) |
signal 用 lazy _register() 而非模块级 @receiver |
避免 ready() 早期 apps.get_model 不可用 |
五、最新目录结构(v2 增量标注)
apps/
├── __init__.py
├── account/ (4 模型)
├── client/ (11 模型)
├── complex/ (10 模型)
├── org/ (11 模型)
├── permission/ (6 模型 + services/seed_default_roles.py) ← v2: 6 模型(PermissionDef 已迁出)
├── permission_def/ (1 模型) ← v2 新增 SHARED app
├── property/ (23 模型)
├── region/ (5 模型)
├── release/ (0 模型)
├── setting/ (4 模型 + services/seed_default_lookups.py) ← v2: 新增 service
└── tenant/ (2 模型 + signals.py) ← v2: 新增 signal
模型计数:77(与 v1 一致;PermissionDef 从 permission 迁到 permission_def)。
六、Migration 总览(33 份)
| App | Migration | 用途 |
|---|---|---|
| account | 0001 / 0002 | 初始 + AUTH_USER_MODEL 切换 |
| account | 0003 / 0004 | Phase 4.0 / 4.1 verbose_name + help_text |
| client | 0001 | 初始 |
| client | 0002 | 分区表 + 触发器 |
| client | 0003 / 0004 | Phase 4.0 / 4.1 |
| complex | 0001 | 初始 |
| complex | 0002 | pg_trgm + search_vector |
| complex | 0003 / 0004 | Phase 4.0 / 4.1 |
| org | 0001 | 初始 |
| org | 0002 / 0003 | Phase 4.0 / 4.1 |
| permission | 0001 / 0002 / 0003 | 初始 + Phase 4.0/4.1 |
| permission | 0004 | v2 新增:FK 切换到 fonrey_permission_def + DeleteModel(PermissionDef) |
| permission_def | 0001 | v2 新增:CREATE TABLE permission_defs(public schema) |
| permission_def | 0002 | v2 新增:154 条 PermissionDef bulk_create |
| property | 0001 | 初始 |
| property | 0002 | 分区表 + 触发器 |
| property | 0003 / 0004 | Phase 4.0 / 4.1 |
| region | 0001 / 0002 / 0003 | 初始 + Phase 4.0/4.1 |
| setting | 0001 / 0002 / 0003 | 初始 + Phase 4.0/4.1 |
| tenant | 0001 / 0002 | 初始 + Phase 4.0/4.1 |
合计 33 个 migration。makemigrations --dry-run 干净。
七、Git 提交历史(v1 → v2 增量)
aaf3981 feat(permission): seed 154 PermissionDefs + 7 builtin roles + matrix + lookups + tenant auto-seed
b9245cd feat(permission): extract PermissionDef into shared apps.permission_def
5dedd19 docker file & docker compose change
d00ff12 feat(migrations): add Phase 4.0+4.1 verbose_name/help_text migrations
8faa68b feat(tenant): add Chinese verbose_name/help_text to tenant models (Phase 4.1 part 9/9)
e3b26ce feat(region): add Chinese verbose_name/help_text to region models (Phase 4.1 part 8/9)
289ec43 feat(setting): add Chinese verbose_name/help_text to setting models (Phase 4.1 part 7/9)
9ef6eb6 feat(permission): add Chinese verbose_name/help_text to permission models (Phase 4.1 part 6/9)
b57070f feat(account): add Chinese verbose_name and help_text to all account fields (Phase 4.1 part 5/9)
f185127 feat(org): add Chinese verbose_name and help_text to all org fields (Phase 4.1 part 4/9)
a3800bf feat(complex): add Chinese verbose_name and help_text to all complex fields (Phase 4.1 part 3/9)
e67b07a feat(client): add Chinese verbose_name and help_text to all client fields (Phase 4.1 part 2/9)
3638fc0 feat(property): add Chinese verbose_name and help_text to all property fields (Phase 4.1)
79c3cf2 feat(models): add Chinese verbose_name to all 74 models (Phase 4.0)
每次 commit 后 manage.py check 0 issues。所有 commit 已 push 到 origin/main(5dedd19..aaf3981)。
八、未交付清单(v2 视角)
v1 列出的"未交付项"在 v2 的状态:
| v1 列项 | v2 状态 |
|---|---|
| ~300 条 PermissionDef 种子 | ✅ 已交付 154 条(MVP Batch 1);Batch 2/3 待补 |
| 内置角色 + 默认 DataScope 种子 | 🟡 7 内置角色 + 矩阵已交付;DataScope 不 seed(用户明确指示,scope 类 PermissionDef default_value 承担兜底) |
| Setting LookupItem 默认值 | ✅ 已交付 |
Celery partition_maintenance_task |
⏸ 仍未做,建议上线前 1 周落地 |
| API_CONTRACT 第 1–4 项 | ⏸ Phase 6+ 业务端点开发时同步推进 |
| OpenAPI 实际生成 + schemathesis 实际运行 | ⏸ 同上 |
| Heroicons SVG 资源文件 | ⏸ Phase 6+ UI 开发时落地 |
| static/vendor/ JS(htmx/alpine) | ⏸ 同上 |
v2 新增/识别的待办:
| 项 | 优先级 | 说明 |
|---|---|---|
| Platform admin 角色 + 路由 + 视图 | 中 | 独立任务,归未来 apps.admin_console(SHARED) |
| PermissionDef MVP Batch 2/3(合同/交易/数据/营销/移动端等模块) | 中 | 沿用 0002_seed_permission_defs 同模式追加 data migration |
seed_default_roles 中未使用的 schema_context import |
低 | 微清理,不阻断 |
| signal 失败重试或离线补偿命令 | 中 | 当前 try/except + log;建议补 manage.py reseed_tenant <schema> 命令 |
九、关键约束遵守审计(v2 增量)
v1 §11 全部约束在 v2 仍然遵守。v2 新增约束:
| 约束 | 遵守状态 | 证据 |
|---|---|---|
| 不引入 docstring/无谓注释(hook 强制) | ✅ | Phase 4.1 + 5 全部新文件 grep ^\s*# 与 docstring 极少,仅保留必要的 BDD/regex 注释 |
| 不修改 Dockerfile / docker-compose.yml | ✅ | Phase 5 期间未触动;用户独立 commit 5dedd19 覆盖 docker 改动 |
| 不 commit 未明确授权的 untracked 文件 | ✅ | 仅 commit 计划内文件,每次 commit 列表精确 |
manage.py check 每次 commit 后 0 issues |
✅ | 14 个 commit 全部验证 |
makemigrations --dry-run 收尾时 No changes detected |
✅ | aaf3981 后实际跑过 |
| PermissionDef 公共元数据完整(is_system / version=1 等) | ✅ | seed migration 154 条全部含 |
| 不 seed staff_data_scopes | ✅ | 仅 PermissionDef + Role + RolePermission + Lookup |
| Platform admin 不混入本批 | ✅ | 7 角色不含 platform_admin |
| FK 跨 SHARED/TENANT 字符串引用正确 | ✅ | manage.py check 通过即证 |
十、下一步建议(v2 视角)
10.1 Phase 6 启动前必做
- 决定 vendor JS 加载方式(npm install 还是直接放置静态文件)
- 准备 Heroicons SVG 库
- 实现
apps.admin_console(SHARED) + Platform admin 内置角色
10.2 Phase 6 业务模块开发与契约闭环
- 第一个真实业务端点(建议从
release/client_update起步)落地后立即跑spectacular+schemathesis,闭环 API_CONTRACT 7 项 - 在每个业务视图 PR 中强制
@extend_schema - 每新增 PermissionDef 需要同步追加
permission_def/000N_seed_*.py,且新建 RolePermission 时使用seed_default_roles增量化 - 撰写
manage.py reseed_tenant <schema>命令用于 signal 失败补偿
10.3 上线前 1 周
- 实现 Celery
partition_maintenance_task(property_follow_logs / property_photos / client_follow_logs 月度滚动) - 用真实 32 字节随机值替换
.env占位的PHONE_ENCRYPTION_KEY,托管至 Vault/Secret Manager - 验证租户创建 → signal 自动 seed 全链路(含异常路径:seed 失败时的补偿命令)
10.4 可选
- pre-commit 钩子(ruff + black + isort + django-check)
- GitHub Actions CI(lint + test + spectacular dry-run + makemigrations --dry-run 必须 No changes)
十一、附录:v2 时点验证命令
cd /mnt/c/project/fonrey
.venv/bin/python manage.py check # System check identified no issues (0 silenced)
.venv/bin/python manage.py makemigrations --dry-run # No changes detected
grep -c '"code":' apps/permission_def/migrations/0002_seed_permission_defs.py # 154
grep -c '^\s*"[a-z_]*\.[a-z_.]*":' apps/permission/services/seed_default_roles.py # 154
git log --oneline 94d1602..HEAD | wc -l # 14
报告完(v2.0 — 2026-04-30)