From 14eae576432357021918de4a2c90c583008a840c Mon Sep 17 00:00:00 2001 From: ila Date: Mon, 6 Jul 2026 16:23:44 +0800 Subject: [PATCH] Add page-tree decision and Wagtail M2M implementation note - 04-architecture 3.1: add ProjectIndexPage; authoritative page tree (project pages live under /projects/, never under scenario pages); ScenarioPage renders projects via query; filtering lives in ProjectIndexPage.get_context(), no separate Django view; Page M2M fields must use modelcluster ParentalManyToManyField - 06-tasks: sync T-102 row/detail and T-204 quick-table accordingly - routes: point project list page back to the 3.1 tree - progress.md: append maintenance record Co-Authored-By: Claude Fable 5 --- docs/04-architecture.md | 20 ++++++++++++++++++++ docs/06-tasks.md | 6 ++++-- docs/routes.md | 1 + progress.md | 12 ++++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/docs/04-architecture.md b/docs/04-architecture.md index ed953ce..46b8c46 100644 --- a/docs/04-architecture.md +++ b/docs/04-architecture.md @@ -61,12 +61,32 @@ SQLite db.sqlite3 | `HomePage` | Page | 首页,展示场景入口、推荐骨架、最新文章。 | | `ScenarioIndexPage` | Page | 场景总览页。 | | `ScenarioPage` | Page | 单个场景页,如 SaaS、管理后台、API 服务。 | +| `ProjectIndexPage` | Page | 项目列表页(`/projects/`),承载筛选与分页。 | | `SkeletonProjectPage` | Page | 骨架项目详情页。 | | `ArticleIndexPage` | Page | 文章列表。 | | `ArticlePage` | Page | 评测、对比、避坑指南等内容文章。 | 模型归属:`HomePage` 在 `home` app(`wagtail start` 生成,沿用不改名);其余 Page、Snippet 和文章模型集中在 `core` app(见本文第六节)。 +**页面树结构(唯一权威)**: + +```text +HomePage (/) +├── ScenarioIndexPage (/scenarios/) +│ └── ScenarioPage (/scenarios/{slug}/) +├── ProjectIndexPage (/projects/) +│ └── SkeletonProjectPage (/projects/{slug}/) +└── ArticleIndexPage (/articles/) + └── ArticlePage (/articles/{slug}/) +``` + +- 项目详情页统一挂在 `ProjectIndexPage` 下,**不挂在场景页下**:一个项目属于多个场景(多对多),而页面树上只能有一个父节点。 +- `ScenarioPage` 没有子项目页;它通过查询 `SkeletonProjectPage.objects.live().filter(scenarios=...)` 渲染本场景的项目列表。 +- 列表筛选与分页在 `ProjectIndexPage.get_context()` 中做服务端查询实现(参数契约见 3.4),**不另建独立 Django view**。 +- `/languages/{slug}/`、`/frameworks/{slug}/` MVP 由筛选参数替代,不建 Page(见 `routes.md`)。 + +**Wagtail 实现注意**:Page 模型上的多对多字段(`scenarios`、`languages`、`frameworks`、`databases`、`features`)必须用 modelcluster 的 `ParentalManyToManyField`,不能用 Django 普通 `ManyToManyField`,否则后台编辑、草稿和预览会出错。 + ### 3.2 Snippet / 辅助模型 | 模型 | 说明 | diff --git a/docs/06-tasks.md b/docs/06-tasks.md index 5bc5a7b..8e7a7bb 100644 --- a/docs/06-tasks.md +++ b/docs/06-tasks.md @@ -31,7 +31,7 @@ | ID | 任务 | 依赖 | 验收要点 | 状态 | | --- | --- | --- | --- | --- | | T-101 | 建立场景、语言、框架、数据库、功能标签模型 | T-003 | Wagtail 后台可维护这些 Snippet;迁移文件生成并通过 migrate | TODO | -| T-102 | 建立骨架项目详情模型 | T-101 | `SkeletonProjectPage` 严格按 `04-architecture.md` §3.3 表和必填/默认附注实现;评分字段带 0-5 校验器;总分由分项计算 | TODO | +| T-102 | 建立骨架项目详情模型 | T-101 | `SkeletonProjectPage` 严格按 `04-architecture.md` §3.3 表和必填/默认附注实现;多对多用 `ParentalManyToManyField`;同时建 `ProjectIndexPage`;评分字段带 0-5 校验器;总分由分项计算 | TODO | | T-103 | 建立文章模型 | T-101 | 可维护文章列表和详情;文章可链接骨架项目 | TODO | | T-104 | 准备首批种子内容 | T-102, T-103 | 至少有 3 个场景、5 个骨架项目、2 篇文章;候选项目、评分和评语须经人工确认,禁止模型编造(见详单) | TODO | @@ -101,6 +101,8 @@ ### T-102 骨架项目详情模型 - `SkeletonProjectPage`(`core` app):字段、必填/可空/默认严格按 `04-architecture.md` §3.3 表和附注实现,不增不减。 +- **多对多字段(`scenarios`、`languages`、`frameworks`、`databases`、`features`)必须用 modelcluster 的 `ParentalManyToManyField`**,不能用普通 `ManyToManyField`(见 §3.1 Wagtail 实现注意)。 +- 同时建 `ProjectIndexPage`(项目详情页在页面树上的父节点,见 §3.1 页面树;本轮只建模型,筛选逻辑留给 T-204)。 - 6 个评分字段带 `MinValueValidator(0)` / `MaxValueValidator(5)`;`total_score` 为 property(6 项之和,0-30)。 - 后台按 5 组分面板:基本信息 / 分类标签 / 评分 / 评测内容 / 展示控制。 - 验证单元测试至少 3 个:合法对象 `full_clean()` 通过;某评分设为 6 时 `full_clean()` 抛 `ValidationError`;`total_score` 等于 6 项之和。`manage.py test` 全部通过。 @@ -124,7 +126,7 @@ | T-201 | `base.html` 含 ``;纯 CSS 无构建链路;无超视口固定宽度容器 | `curl /` 返回 200;各页面模板均继承 `base.html` | | T-202 | 首页三区块全部来自数据库查询(`is_featured`、最新文章),不硬编码内容 | 测试断言 featured 项目标题出现在 `/` 响应中 | | T-203 | 场景页只列已发布(live)项目;分页每页 20;空场景渲染 `empty_state.html` | 测试两用例:有项目场景(200 且含项目名)、空场景(200 且含空状态文案) | -| T-204 | 严格按 `04-architecture.md` §3.4 契约;搜索用 ORM `icontains` | 测试 4 用例:`language` 过滤、`min_score` 过滤、`q` 搜索、非法参数被忽略 | +| T-204 | 严格按 `04-architecture.md` §3.4 契约;筛选实现在 `ProjectIndexPage.get_context()`;搜索用 ORM `icontains` | 测试 4 用例:`language` 过滤、`min_score` 过滤、`q` 搜索、非法参数被忽略 | | T-205 | 展示 §3.3 全部展示字段;`is_sponsored=True` 时渲染 `sponsored_badge.html` | 测试断言详情页含评分、推荐理由;赞助用例含 Sponsored 标识 | | T-206 | 文章详情可点击进入关联项目详情页 | 测试断言文章页响应含项目详情链接 | | T-301 | title/description 用 Wagtail 自带 `seo_title` / `search_description`;sitemap 用 `wagtail.contrib.sitemaps` | `curl /sitemap.xml` 返回 200 且含项目 URL;首页响应含 `