feat: 新增隐私政策内容页(apps.core.SimpleContentPage)及seed命令,更新设计文档

This commit is contained in:
2026-08-06 17:17:07 +08:00
parent 9e60cabd5e
commit b05d2b7f52
9 changed files with 173 additions and 3 deletions
View File
@@ -0,0 +1,93 @@
"""
创建/更新"隐私政策"页面(apps.core.SimpleContentPage),挂载在首页下。
用于满足 PIPL 合规清单要求(详见 documents/设计方案分析与完善版.md §2.15)。
用法:
python manage.py seed_privacy_policy
"""
from django.core.management.base import BaseCommand, CommandError
from apps.core.models import SimpleContentPage
from apps.home.models import HomePage
PRIVACY_POLICY_BODY = """
<h2>一、引言</h2>
<p>本公司(以下简称"我们")非常重视用户的个人信息保护。本《隐私政策》适用于用户访问本网站及使用本网站提供的各项服务(包括但不限于在线留言、需求咨询表单提交等)。请在使用本网站前仔细阅读并充分理解本政策的全部内容。</p>
<h2>二、我们收集的信息</h2>
<p>当您通过本网站的留资表单(如产品咨询、方案咨询等)主动提交信息时,我们可能收集以下类型的个人信息:</p>
<ul>
<li>姓名</li>
<li>联系电话</li>
<li>电子邮箱</li>
<li>所在公司/单位名称</li>
<li>您在留言/需求描述中主动提供的其他信息</li>
</ul>
<p>除上述信息外,我们不会主动收集与服务无关的个人敏感信息。</p>
<h2>三、我们如何使用您的信息</h2>
<p>我们收集上述信息仅用于以下目的:</p>
<ul>
<li>响应并处理您通过表单提交的咨询或需求;</li>
<li>与您联系以提供售前/售后服务、产品或方案介绍;</li>
<li>改善本网站的内容与用户体验;</li>
<li>履行法律法规要求的其他义务。</li>
</ul>
<p>未经您的同意,我们不会将您的个人信息用于上述目的以外的用途,也不会向无关第三方出售、共享您的个人信息。</p>
<h2>四、信息的存储与保护</h2>
<p>您的个人信息将存储于中国大陆境内的服务器,存储期限为实现收集目的所必需的期限,法律法规另有规定的除外。我们采取符合行业标准的技术措施(如访问控制、传输加密等)保护您的个人信息安全,防止信息被误用、未经授权访问、泄露、篡改或损毁。</p>
<h2>五、Cookie 的使用</h2>
<p>为提升用户体验,本网站可能使用 Cookie 或同类技术记录您的浏览偏好等非身份识别信息。您可以通过浏览器设置拒绝或清除 Cookie,但这可能影响部分网站功能的正常使用。</p>
<h2>六、您的权利</h2>
<p>依据《中华人民共和国个人信息保护法》(PIPL)等相关法律法规,您对我们持有的您的个人信息享有以下权利:</p>
<ul>
<li><strong>查阅权</strong>:您有权查阅我们收集的与您相关的个人信息;</li>
<li><strong>更正权</strong>:如您发现我们持有的个人信息有误,可要求我们更正;</li>
<li><strong>删除权</strong>:在符合法律规定的情形下,您可要求我们删除您的个人信息;</li>
<li><strong>撤回同意权</strong>:您可随时撤回此前提供个人信息时给予的同意。</li>
</ul>
<p>如需行使上述权利,请通过本政策"联系我们"部分列明的方式与我们联系,我们将在收到请求后的合理期限内予以处理。</p>
<h2>七、未成年人信息保护</h2>
<p>本网站主要面向企业客户提供产品与服务信息,不主动面向未成年人收集个人信息。如您是未成年人的监护人并发现其向我们提供了个人信息,请及时与我们联系,我们将尽快采取相应措施删除相关数据。</p>
<h2>八、政策的更新</h2>
<p>我们可能会根据法律法规变化或业务调整不时修订本政策,修订后的政策将在本页面公布并自公布之日起生效。如政策发生重大变更,我们会以网站公告等适当方式通知用户。</p>
<h2>九、联系我们</h2>
<p>如您对本隐私政策有任何疑问、意见或投诉,或需要行使您的个人信息相关权利,请通过网站留言表单与我们联系,我们将尽快回复处理。</p>
<p><em>本政策最近更新日期:2026 年 8 月。</em></p>
""".strip()
class Command(BaseCommand):
help = "创建或更新隐私政策页面(挂载于首页下)"
def handle(self, *args, **options):
home_page = HomePage.objects.live().first() or HomePage.objects.first()
if home_page is None:
raise CommandError(
"未找到 HomePage 实例,请先在 Wagtail 后台创建首页后再运行本命令。"
)
page = SimpleContentPage.objects.child_of(home_page).filter(slug="privacy-policy").first()
if page is None:
page = SimpleContentPage(
title="隐私政策",
slug="privacy-policy",
body=PRIVACY_POLICY_BODY,
seo_title_override="隐私政策",
seo_description_override="了解我们如何收集、使用和保护您的个人信息。",
)
home_page.add_child(instance=page)
page.save_revision().publish()
self.stdout.write(self.style.SUCCESS(f"已创建隐私政策页面:{page.url}"))
else:
page.body = PRIVACY_POLICY_BODY
page.save()
page.save_revision().publish()
self.stdout.write(self.style.SUCCESS(f"已更新隐私政策页面:{page.url}"))
+34
View File
@@ -0,0 +1,34 @@
# Generated by Django 6.0.5 on 2026-08-06 09:11
import django.db.models.deletion
import wagtail.fields
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('wagtailcore', '0097_baselogentry_uuid_action_timestamp_indexes'),
('wagtailimages', '0027_image_description'),
]
operations = [
migrations.CreateModel(
name='SimpleContentPage',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
('seo_title_override', models.CharField(blank=True, help_text='留空则使用页面标题', max_length=70, verbose_name='SEO 标题')),
('seo_description_override', models.CharField(blank=True, max_length=160, verbose_name='SEO 描述')),
('canonical_url', models.URLField(blank=True, verbose_name='Canonical URL')),
('schema_json', models.JSONField(blank=True, default=dict, verbose_name='结构化数据 (JSON-LD)')),
('body', wagtail.fields.RichTextField(blank=True, verbose_name='正文内容')),
('og_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image', verbose_name='社交分享图')),
],
options={
'verbose_name': '通用内容页',
},
bases=('wagtailcore.page',),
),
]
+17
View File
@@ -4,6 +4,7 @@
""" """
from django.db import models from django.db import models
from wagtail.admin.panels import FieldPanel from wagtail.admin.panels import FieldPanel
from wagtail.fields import RichTextField
from wagtail.models import Page from wagtail.models import Page
@@ -36,3 +37,19 @@ class SEOablePage(Page):
class Meta: class Meta:
abstract = True abstract = True
class SimpleContentPage(SEOablePage):
"""通用富文本内容页,用于隐私政策、服务条款等法务/说明类页面。"""
body = RichTextField(blank=True, verbose_name="正文内容")
content_panels = SEOablePage.content_panels + [
FieldPanel("body"),
]
parent_page_types = ["home.HomePage"]
subpage_types = []
class Meta:
verbose_name = "通用内容页"
@@ -0,0 +1,9 @@
{% load wagtailcore_tags %}
<!doctype html>
<html lang="zh-hans">
<head><meta charset="utf-8"><title>{{ page.title }}</title></head>
<body>
<h1>{{ page.title }}</h1>
{{ page.body|richtext }}
</body>
</html>
+15
View File
@@ -2,6 +2,7 @@
import pytest import pytest
from apps.core.blocks import FormBlock from apps.core.blocks import FormBlock
from apps.core.models import SimpleContentPage
from apps.forms.models import FormDefinition, FormDefinitionField from apps.forms.models import FormDefinition, FormDefinitionField
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
@@ -60,3 +61,17 @@ def test_form_block_api_representation_handles_empty_form():
block = FormBlock() block = FormBlock()
result = block.get_api_representation({"form": None}) result = block.get_api_representation({"form": None})
assert result == {"form": None} assert result == {"form": None}
def test_simple_content_page_can_be_created_under_home(home_page):
"""隐私政策等法务页面应可作为 HomePage 的子页面创建,并保留富文本内容。"""
page = SimpleContentPage(
title="隐私政策",
slug="privacy-policy",
body="<p>我们重视您的个人信息保护。</p>",
)
home_page.add_child(instance=page)
page.refresh_from_db()
assert SimpleContentPage.objects.live().descendant_of(home_page).count() == 1
assert "个人信息保护" in page.body
+1
View File
@@ -19,6 +19,7 @@ class HomePage(SEOablePage):
"products.ProductIndexPage", "products.ProductIndexPage",
"solutions.SolutionIndexPage", "solutions.SolutionIndexPage",
"cases.CaseStudyIndexPage", "cases.CaseStudyIndexPage",
"core.SimpleContentPage",
] ]
class Meta: class Meta:
+4 -3
View File
@@ -338,7 +338,7 @@ CI 中要求单元测试覆盖率不低于 70%,核心 `apps/forms`(涉及线
- [x] 完成 ICP 备案(域名 + 服务器均已在国内云厂商完成备案) - [x] 完成 ICP 备案(域名 + 服务器均已在国内云厂商完成备案)
- [x] 页脚展示备案号并链接工信部备案查询(`frontend/components/layout/Footer.tsx`,实际备案号 冀ICP备2025130506号-1 - [x] 页脚展示备案号并链接工信部备案查询(`frontend/components/layout/Footer.tsx`,实际备案号 冀ICP备2025130506号-1
- [ ] 隐私政策 / Cookie 同意条符合 PIPL 要求 - [x] 隐私政策内容已上线(`apps.core.SimpleContentPage` 通用富文本内容页模型 + `seed_privacy_policy` 管理命令 + 前端 `/privacy-policy` 页面 + 页脚链接,内容覆盖 PIPL 要求的收集目的/用途/用户权利/Cookie 说明等),但 Cookie 同意条控件本身尚未开发
- [ ] 字体/地图/验证码/CDN 均替换为可在国内正常访问的服务 - [ ] 字体/地图/验证码/CDN 均替换为可在国内正常访问的服务
- [ ] 表单收集的个人信息需明确告知用途并支持删除请求(PIPL 数据主体权利) - [ ] 表单收集的个人信息需明确告知用途并支持删除请求(PIPL 数据主体权利)
@@ -361,7 +361,8 @@ CI 中要求单元测试覆盖率不低于 70%,核心 `apps/forms`(涉及线
- [x] `apps/forms``FormDefinition`/`FormDefinitionField`/`Lead`Snippet 方式)+ 提交 API`/api/v1/custom/leads/`,限流 + 邮件通知) - [x] `apps/forms``FormDefinition`/`FormDefinitionField`/`Lead`Snippet 方式)+ 提交 API`/api/v1/custom/leads/`,限流 + 邮件通知)
- [x] Wagtail API v2 挂载、分页/过滤/排序(Wagtail 内置) - [x] Wagtail API v2 挂载、分页/过滤/排序(Wagtail 内置)
- [x] Next.js 前端脚手架(独立仓库):Header/Footer/layout、首页、博客/产品/案例/解决方案列表与详情页、`BlockRenderer`、ISR + revalidate route`lint`/`build` 已验证通过 - [x] Next.js 前端脚手架(独立仓库):Header/Footer/layout、首页、博客/产品/案例/解决方案列表与详情页、`BlockRenderer`、ISR + revalidate route`lint`/`build` 已验证通过
- [x] pytest 基础测试框架(pytest-django + factory_boy):`pytest.ini` + `conftest.py`root_page/home_page fixtures),为 `apps/core`SEOablePage 字段、FormBlock API 表示)、`apps/products`、`apps/cases`、`apps/forms`(线索提交 API)编写了基础单元测试,共 10 个用例均通过 - [x] pytest 基础测试框架(pytest-django + factory_boy):`pytest.ini` + `conftest.py`root_page/home_page fixtures),为 `apps/core`SEOablePage 字段、FormBlock API 表示、SimpleContentPage 页面树)、`apps/products`、`apps/cases`、`apps/forms`(线索提交 API)编写了基础单元测试,共 11 个用例均通过
- [x] `apps/core.SimpleContentPage`:通用富文本法务/说明类页面模型,用于隐私政策等内容,配套 `seed_privacy_policy` management command 用于幂等创建/更新隐私政策页面(需在 HomePage 实例存在后手动运行)
未完成(待规划排期): 未完成(待规划排期):
- [ ] Snippet`TeamMember`/`Testimonial`/`Partner`/`NavigationMenu`/`SiteSettings``BaseSiteSetting`)均未创建 - [ ] Snippet`TeamMember`/`Testimonial`/`Partner`/`NavigationMenu`/`SiteSettings``BaseSiteSetting`)均未创建
@@ -370,7 +371,7 @@ CI 中要求单元测试覆盖率不低于 70%,核心 `apps/forms`(涉及线
- [ ] RBAC 落地(Wagtail `Group` + `GroupPagePermission` 实际配置)未开始 - [ ] RBAC 落地(Wagtail `Group` + `GroupPagePermission` 实际配置)未开始
- [ ] 测试覆盖率仍不完整(已有基础单元测试,但集成测试、前端组件测试、E2E 均未编写,当前无 CI 流水线) - [ ] 测试覆盖率仍不完整(已有基础单元测试,但集成测试、前端组件测试、E2E 均未编写,当前无 CI 流水线)
- [ ] 生产安全 settings(§2.11 中 `SECURE_*`/CORS/CSRF 白名单等)未在 `production.py` 中逐项落实确认 - [ ] 生产安全 settings(§2.11 中 `SECURE_*`/CORS/CSRF 白名单等)未在 `production.py` 中逐项落实确认
- [ ] ICP 备案已完成,页脚已展示实际备案号(冀ICP备2025130506号-1);隐私政策/Cookie 合规清单(§2.15其余项尚未启动 - [ ] ICP 备案已完成,页脚已展示实际备案号(冀ICP备2025130506号-1);隐私政策内容已上线,Cookie 同意条控件(§2.15尚未开发
- [ ] 中文分词、OpenSearch 集成(Phase 2 提前项)未开始 - [ ] 中文分词、OpenSearch 集成(Phase 2 提前项)未开始
### 2.17 风险与备选方案 ### 2.17 风险与备选方案