feat: 新增Snippet全局内容模型(TeamMember/Testimonial/Partner/NavigationMenu/SiteSettings)

- 新增 apps.core.TeamMember/Testimonial/Partner:简单 register_snippet,含 order 排序字段
- 新增 apps.core.NavigationMenu+NavigationMenuItem:ClusterableModel+Orderable+InlinePanel,
  支持内部页面或外部链接二选一
- 新增 apps.core.SiteSettings:接入 wagtail.contrib.settings + BaseSiteSetting,
  存放公司信息/ICP备案号/社交账号等全局配置
- 新增只读 API:/api/v1/custom/core/{team,testimonials,partners,navigation,site-settings}/
- 补充 apps/core/tests.py 单元测试,pytest 用例由 11 个增至 24 个,全部通过
- 更新设计文档 §2.16 Phase 1 进度清单
This commit is contained in:
2026-08-07 13:06:22 +08:00
parent b04de9f5c1
commit 8fbbd2e6b0
9 changed files with 628 additions and 5 deletions
@@ -0,0 +1,110 @@
# Generated by Django 6.0.5 on 2026-08-07 03:14
import django.db.models.deletion
import modelcluster.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
('wagtailcore', '0097_baselogentry_uuid_action_timestamp_indexes'),
('wagtailimages', '0027_image_description'),
]
operations = [
migrations.CreateModel(
name='NavigationMenu',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='前端按此标识获取菜单,如 main(主导航)、footer(页脚导航)', max_length=50, unique=True, verbose_name='菜单标识')),
],
options={
'verbose_name': '导航菜单',
'verbose_name_plural': '导航菜单',
},
),
migrations.CreateModel(
name='NavigationMenuItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
('label', models.CharField(max_length=50, verbose_name='文案')),
('link_url', models.CharField(blank=True, help_text='留空则使用内部页面链接', max_length=255, verbose_name='外部链接')),
('open_in_new_tab', models.BooleanField(default=False, verbose_name='新标签页打开')),
('link_page', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailcore.page', verbose_name='内部页面')),
('menu', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='core.navigationmenu')),
],
options={
'ordering': ['sort_order'],
'abstract': False,
},
),
migrations.CreateModel(
name='Partner',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, verbose_name='名称')),
('website_url', models.URLField(blank=True, verbose_name='官网链接')),
('order', models.IntegerField(default=0, verbose_name='排序')),
('logo', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image', verbose_name='Logo')),
],
options={
'verbose_name': '合作伙伴',
'verbose_name_plural': '合作伙伴',
'ordering': ['order', 'id'],
},
),
migrations.CreateModel(
name='SiteSettings',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('company_name', models.CharField(blank=True, max_length=100, verbose_name='公司全称')),
('contact_phone', models.CharField(blank=True, max_length=30, verbose_name='联系电话')),
('contact_email', models.EmailField(blank=True, max_length=254, verbose_name='联系邮箱')),
('address', models.CharField(blank=True, max_length=255, verbose_name='公司地址')),
('icp_number', models.CharField(blank=True, max_length=50, verbose_name='ICP备案号')),
('icp_url', models.URLField(default='https://beian.miit.gov.cn/', verbose_name='备案查询链接')),
('wechat_account', models.CharField(blank=True, max_length=100, verbose_name='微信公众号名称')),
('weibo_url', models.URLField(blank=True, verbose_name='微博主页')),
('site', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to='wagtailcore.site')),
('wechat_qrcode', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image', verbose_name='微信公众号二维码')),
],
options={
'verbose_name': '站点设置',
},
),
migrations.CreateModel(
name='TeamMember',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='姓名')),
('role', models.CharField(blank=True, max_length=100, verbose_name='职位')),
('bio', models.TextField(blank=True, verbose_name='简介')),
('order', models.IntegerField(default=0, verbose_name='排序')),
('photo', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image', verbose_name='照片')),
],
options={
'verbose_name': '团队成员',
'verbose_name_plural': '团队成员',
'ordering': ['order', 'id'],
},
),
migrations.CreateModel(
name='Testimonial',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quote', models.TextField(verbose_name='推荐语')),
('author_name', models.CharField(max_length=50, verbose_name='客户姓名')),
('author_title', models.CharField(blank=True, max_length=100, verbose_name='客户职位/公司')),
('order', models.IntegerField(default=0, verbose_name='排序')),
('author_photo', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image', verbose_name='客户头像')),
],
options={
'verbose_name': '客户证言',
'verbose_name_plural': '客户证言',
'ordering': ['order', 'id'],
},
),
]