diff --git a/core/tests.py b/core/tests.py index 69b703a..3cfe024 100644 --- a/core/tests.py +++ b/core/tests.py @@ -444,4 +444,44 @@ class ArticlePageTests(PageTreeMixin, TestCase): article.related_projects.add(project) self.assertEqual(article.related_projects.count(), 1) - self.assertEqual(article.related_projects.first().title, "Related Project") \ No newline at end of file + self.assertEqual(article.related_projects.first().title, "Related Project") + + +class SEOTests(PageTreeMixin, TestCase): + @classmethod + def setUpTestData(cls): + cls.setUpPageTree() + cls.lang = Language.objects.create(name="Rust", slug="rust") + + def test_homepage_meta_description(self): + resp = self.client.get("/") + self.assertContains(resp, '`,但当前核心页面的 `search_description` 均为空。 +- 复测页面: + - `/` + - `/projects/` + - `/scenarios/crm/` + - `/projects/djangocrm/` + - `/articles/` + +复测结果:上述页面均返回 200,但均未输出 ``,也没有 `rel="canonical"`。 + +任务要求是“核心页面有 title、description、canonical 或等价设置;sitemap 可用”。当前只有 title 和 sitemap 可用,description/canonical 未达标。 + +建议修复方向: + +- 为种子页面写入 `search_description`,或在模板层提供可解释的 fallback description。 +- 明确 canonical 的处理方式:输出 ``,或在文档中说明采用的 Wagtail 等价机制并用测试覆盖。 +- 增加测试断言首页、项目详情页、场景页、文章页含 meta description,并断言 sitemap 含项目 URL。 + +### P1:根目录 `test_sitemap.py` 是非规范测试文件,导入即写数据库并发请求 + +- 影响任务:T-301 / 验证基线 +- 位置:`test_sitemap.py:1` 至 `test_sitemap.py:17` +- 现象: + - 文件位于仓库根目录,命名匹配 Django test discovery。 + - 模块顶层直接执行 `django.setup()`、`Site.objects.get_or_create(...)` 和 `Client().get("/sitemap.xml")`。 + - `manage.py test` 运行时会打印 `Site created/updated`、`sitemap.xml: 200` 等输出。 + +这不是稳定的测试写法。测试发现阶段导入模块就修改数据库,会污染开发数据库,也会让测试基线依赖导入副作用。 + +建议修复方向: + +- 删除根目录 `test_sitemap.py`。 +- 把 sitemap 验证迁移到 `core/tests.py` 或新的 app 测试文件中,使用 `TestCase` 和断言。 + +### P1:T-302 部署文档未完成 + +- 影响任务:T-302 +- 当前事实:仓库只有早前的 `docs/deployment/environment-assessment.md`,它是 Lightsail 环境评估,不是可复制执行的部署手册。 +- 缺口: + - 没有完整 Gunicorn systemd service 示例。 + - 没有 Nginx server block 示例。 + - 没有静态文件、media、环境变量、迁移、superuser、备份、重启/回滚步骤。 + +T-302 要求“2 核 2G VPS 上 Gunicorn + Nginx + SQLite 的部署步骤清楚”,当前未满足。 + +### P1:T-303 SQLite 上线检查未完成 + +- 影响任务:T-303 +- 缺口: + - 未记录 JSON1 验证命令和结果。 + - 未记录 WAL 启用命令和 `PRAGMA journal_mode;` 证据。 + - 未记录备份策略。 + - 未记录迁移 PostgreSQL 的触发条件。 + +任务要求这些内容写入部署文档;当前没有。 + +### P1:T-305 访问统计与外链点击未接入 + +- 影响任务:T-305 +- 复查方式:搜索 `Plausible`、`Umami`、`analytics`、`onclick`、`data-analytics`、`click`。 +- 结果:代码没有轻量统计脚本,没有外链点击事件标记或处理逻辑。 + +T-305 未实现。 + +### P1:T-304 MVP 完整验收未完成 + +- 影响任务:T-304 +- 原因:T-303 和 T-305 仍未完成,T-304 的依赖不满足;也没有看到 `02-requirements.md` P0 完整验收记录。 + +T-304 未达标。 + +## 已完成部分 + +### Sitemap 基础可用 + +- 位置: + - `skelet/settings/base.py:56` 至 `skelet/settings/base.py:58` + - `skelet/urls.py:18` 至 `skelet/urls.py:23` + - `skelet/sitemaps.py:1` 至 `skelet/sitemaps.py:22` +- 复测:`/sitemap.xml` 返回 200,并包含项目、文章、场景 URL。 + +这一部分满足 T-301 的 sitemap 子项。 + +## 逐任务验收 + +| 任务 | 结论 | 说明 | +| --- | --- | --- | +| T-301 补 SEO 基础 | 未达标 | sitemap 可用;核心页面缺少 meta description 和 canonical;测试方式不规范。 | +| T-302 补部署文档 | 未达标 | 没有 Gunicorn + Nginx + SQLite 的可执行部署手册。 | +| T-303 SQLite 上线检查 | 未达标 | 没有 JSON1、WAL、备份、PostgreSQL 迁移触发条件记录。 | +| T-305 接入轻量访问统计与外链点击记录 | 未达标 | 未接入 Plausible/Umami 或等价方案,也没有外链点击统计。 | +| T-304 MVP 完整验收 | 未达标 | 依赖任务未完成;没有 P0 完整验收记录。 | + +## 验证记录 + +已执行: + +```bash +.venv/bin/python3.12.exe manage.py check +.venv/bin/python3.12.exe manage.py makemigrations --check --dry-run +.venv/bin/python3.12.exe manage.py test +.venv/bin/python3.12.exe manage.py showmigrations sites +.venv/bin/python3.12.exe manage.py shell -c "from wagtail.models import Page; qs=Page.objects.live().public().specific(); print([(p.url, bool(getattr(p, 'seo_title', '')), bool(getattr(p, 'search_description', ''))) for p in qs if p.url in ['/', '/projects/', '/scenarios/crm/', '/projects/djangocrm/', '/articles/']])" +.venv/bin/python3.12.exe manage.py shell -c "from django.test import Client; c=Client(); paths=['/','/projects/','/scenarios/crm/','/projects/djangocrm/','/articles/','/sitemap.xml']; [print(p, c.get(p).status_code, (' {% if page.search_description %} + {% elif page.specific.summary %} + + {% else %} + {% endif %} {% if request.in_preview_panel %} + {% else %} + {% if page %} + + {% endif %} {% endif %} diff --git a/skelet/urls.py b/skelet/urls.py index c2e8a0c..452900e 100644 --- a/skelet/urls.py +++ b/skelet/urls.py @@ -1,35 +1,35 @@ from django.conf import settings from django.urls import include, path from django.contrib import admin +from django.contrib.sitemaps.views import sitemap from wagtail.admin import urls as wagtailadmin_urls from wagtail import urls as wagtail_urls from wagtail.documents import urls as wagtaildocs_urls from search import views as search_views +from skelet.sitemaps import sitemaps urlpatterns = [ path("django-admin/", admin.site.urls), path("admin/", include(wagtailadmin_urls)), path("documents/", include(wagtaildocs_urls)), path("search/", search_views.search, name="search"), + path( + "sitemap.xml", + sitemap, + {"sitemaps": sitemaps}, + name="wagtail_sitemap", + ), ] - if settings.DEBUG: from django.conf.urls.static import static from django.contrib.staticfiles.urls import staticfiles_urlpatterns - # Serve static and media files from development server urlpatterns += staticfiles_urlpatterns() urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns = urlpatterns + [ - # For anything not caught by a more specific rule above, hand over to - # Wagtail's page serving mechanism. This should be the last pattern in - # the list: path("", include(wagtail_urls)), - # Alternatively, if you want Wagtail pages to be served from a subpath - # of your site, rather than the site root: - # path("pages/", include(wagtail_urls)), ]