diff --git a/apps/core/management/__init__.py b/apps/core/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/core/management/commands/__init__.py b/apps/core/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/core/management/commands/seed_privacy_policy.py b/apps/core/management/commands/seed_privacy_policy.py new file mode 100644 index 0000000..6d69056 --- /dev/null +++ b/apps/core/management/commands/seed_privacy_policy.py @@ -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 = """ +

一、引言

+

本公司(以下简称"我们")非常重视用户的个人信息保护。本《隐私政策》适用于用户访问本网站及使用本网站提供的各项服务(包括但不限于在线留言、需求咨询表单提交等)。请在使用本网站前仔细阅读并充分理解本政策的全部内容。

+ +

二、我们收集的信息

+

当您通过本网站的留资表单(如产品咨询、方案咨询等)主动提交信息时,我们可能收集以下类型的个人信息:

+ +

除上述信息外,我们不会主动收集与服务无关的个人敏感信息。

+ +

三、我们如何使用您的信息

+

我们收集上述信息仅用于以下目的:

+ +

未经您的同意,我们不会将您的个人信息用于上述目的以外的用途,也不会向无关第三方出售、共享您的个人信息。

+ +

四、信息的存储与保护

+

您的个人信息将存储于中国大陆境内的服务器,存储期限为实现收集目的所必需的期限,法律法规另有规定的除外。我们采取符合行业标准的技术措施(如访问控制、传输加密等)保护您的个人信息安全,防止信息被误用、未经授权访问、泄露、篡改或损毁。

+ +

五、Cookie 的使用

+

为提升用户体验,本网站可能使用 Cookie 或同类技术记录您的浏览偏好等非身份识别信息。您可以通过浏览器设置拒绝或清除 Cookie,但这可能影响部分网站功能的正常使用。

+ +

六、您的权利

+

依据《中华人民共和国个人信息保护法》(PIPL)等相关法律法规,您对我们持有的您的个人信息享有以下权利:

+ +

如需行使上述权利,请通过本政策"联系我们"部分列明的方式与我们联系,我们将在收到请求后的合理期限内予以处理。

+ +

七、未成年人信息保护

+

本网站主要面向企业客户提供产品与服务信息,不主动面向未成年人收集个人信息。如您是未成年人的监护人并发现其向我们提供了个人信息,请及时与我们联系,我们将尽快采取相应措施删除相关数据。

+ +

八、政策的更新

+

我们可能会根据法律法规变化或业务调整不时修订本政策,修订后的政策将在本页面公布并自公布之日起生效。如政策发生重大变更,我们会以网站公告等适当方式通知用户。

+ +

九、联系我们

+

如您对本隐私政策有任何疑问、意见或投诉,或需要行使您的个人信息相关权利,请通过网站留言表单与我们联系,我们将尽快回复处理。

+ +

本政策最近更新日期:2026 年 8 月。

+""".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}")) diff --git a/apps/core/migrations/0001_initial.py b/apps/core/migrations/0001_initial.py new file mode 100644 index 0000000..9d573d9 --- /dev/null +++ b/apps/core/migrations/0001_initial.py @@ -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',), + ), + ] diff --git a/apps/core/models.py b/apps/core/models.py index 315a87e..f70e501 100644 --- a/apps/core/models.py +++ b/apps/core/models.py @@ -4,6 +4,7 @@ """ from django.db import models from wagtail.admin.panels import FieldPanel +from wagtail.fields import RichTextField from wagtail.models import Page @@ -36,3 +37,19 @@ class SEOablePage(Page): class Meta: 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 = "通用内容页" diff --git a/apps/core/templates/core/simple_content_page.html b/apps/core/templates/core/simple_content_page.html new file mode 100644 index 0000000..7c7d49b --- /dev/null +++ b/apps/core/templates/core/simple_content_page.html @@ -0,0 +1,9 @@ +{% load wagtailcore_tags %} + + +{{ page.title }} + +

{{ page.title }}

+ {{ page.body|richtext }} + + diff --git a/apps/core/tests.py b/apps/core/tests.py index 9ec4687..bf83f00 100644 --- a/apps/core/tests.py +++ b/apps/core/tests.py @@ -2,6 +2,7 @@ import pytest from apps.core.blocks import FormBlock +from apps.core.models import SimpleContentPage from apps.forms.models import FormDefinition, FormDefinitionField pytestmark = pytest.mark.django_db @@ -60,3 +61,17 @@ def test_form_block_api_representation_handles_empty_form(): block = FormBlock() result = block.get_api_representation({"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="

我们重视您的个人信息保护。

", + ) + home_page.add_child(instance=page) + + page.refresh_from_db() + assert SimpleContentPage.objects.live().descendant_of(home_page).count() == 1 + assert "个人信息保护" in page.body diff --git a/apps/home/models.py b/apps/home/models.py index eab4f43..3bf883c 100644 --- a/apps/home/models.py +++ b/apps/home/models.py @@ -19,6 +19,7 @@ class HomePage(SEOablePage): "products.ProductIndexPage", "solutions.SolutionIndexPage", "cases.CaseStudyIndexPage", + "core.SimpleContentPage", ] class Meta: diff --git a/documents/设计方案分析与完善版.md b/documents/设计方案分析与完善版.md index 6401543..7ab46ad 100644 --- a/documents/设计方案分析与完善版.md +++ b/documents/设计方案分析与完善版.md @@ -338,7 +338,7 @@ CI 中要求单元测试覆盖率不低于 70%,核心 `apps/forms`(涉及线 - [x] 完成 ICP 备案(域名 + 服务器均已在国内云厂商完成备案) - [x] 页脚展示备案号并链接工信部备案查询(`frontend/components/layout/Footer.tsx`,实际备案号 冀ICP备2025130506号-1) -- [ ] 隐私政策 / Cookie 同意条符合 PIPL 要求 +- [x] 隐私政策内容已上线(`apps.core.SimpleContentPage` 通用富文本内容页模型 + `seed_privacy_policy` 管理命令 + 前端 `/privacy-policy` 页面 + 页脚链接,内容覆盖 PIPL 要求的收集目的/用途/用户权利/Cookie 说明等),但 Cookie 同意条控件本身尚未开发 - [ ] 字体/地图/验证码/CDN 均替换为可在国内正常访问的服务 - [ ] 表单收集的个人信息需明确告知用途并支持删除请求(PIPL 数据主体权利) @@ -361,7 +361,8 @@ CI 中要求单元测试覆盖率不低于 70%,核心 `apps/forms`(涉及线 - [x] `apps/forms`:`FormDefinition`/`FormDefinitionField`/`Lead`(Snippet 方式)+ 提交 API(`/api/v1/custom/leads/`,限流 + 邮件通知) - [x] Wagtail API v2 挂载、分页/过滤/排序(Wagtail 内置) - [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`)均未创建 @@ -370,7 +371,7 @@ CI 中要求单元测试覆盖率不低于 70%,核心 `apps/forms`(涉及线 - [ ] RBAC 落地(Wagtail `Group` + `GroupPagePermission` 实际配置)未开始 - [ ] 测试覆盖率仍不完整(已有基础单元测试,但集成测试、前端组件测试、E2E 均未编写,当前无 CI 流水线) - [ ] 生产安全 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 提前项)未开始 ### 2.17 风险与备选方案