T-104 M2M 持久化修复:seed_data.py save() + 持久化测试(21 tests)

This commit is contained in:
ila
2026-07-06 22:39:34 +08:00
parent db7dcb8f51
commit 5604e2cde5
3 changed files with 74 additions and 1 deletions
+51 -1
View File
@@ -19,6 +19,19 @@ from core.models import (
class Command(BaseCommand):
help = "Seed database with user-confirmed initial content"
def add_arguments(self, parser):
parser.add_argument(
"--clear",
action="store_true",
help="Delete existing content pages before seeding",
)
def _clear_existing(self):
for model in [ArticlePage, SkeletonProjectPage, ProjectIndexPage,
ScenarioPage, ScenarioIndexPage, ArticleIndexPage]:
model.objects.all().delete()
self.stdout.write(" Cleared existing content pages")
def _create_snippets(self):
languages = [
("Python", "python"),
@@ -50,6 +63,13 @@ class Command(BaseCommand):
def _create_pages(self):
home = HomePage.objects.get(slug="home")
if ScenarioIndexPage.objects.filter(slug="scenarios").exists():
scenario_index = ScenarioIndexPage.objects.get(slug="scenarios")
project_index = ProjectIndexPage.objects.get(slug="projects")
article_index = ArticleIndexPage.objects.get(slug="articles")
self.stdout.write(" Page tree already exists, skipping")
return scenario_index, project_index, article_index
scenario_index = ScenarioIndexPage(title="Scenarios", slug="scenarios")
home.add_child(instance=scenario_index)
@@ -68,7 +88,7 @@ class Command(BaseCommand):
article_index = ArticleIndexPage(title="Articles", slug="articles")
home.add_child(instance=article_index)
self.stdout.write(" Page tree created")
self.stdout.write(" Page tree ready")
return scenario_index, project_index, article_index
def _get_snippets(self):
@@ -226,6 +246,10 @@ class Command(BaseCommand):
]
for proj in projects:
if SkeletonProjectPage.objects.filter(slug=proj["slug"]).exists():
self.stdout.write(f" Skipping existing project: {proj['title']}")
continue
scenarios = proj.pop("scenarios")
languages = proj.pop("languages")
frameworks = proj.pop("frameworks")
@@ -239,6 +263,7 @@ class Command(BaseCommand):
project.frameworks.add(*frameworks)
if databases:
project.databases.add(*databases)
project.save()
self.stdout.write(f" Created project: {proj['title']}")
def _create_articles(self, article_index):
@@ -297,13 +322,25 @@ class Command(BaseCommand):
]
for art in articles:
if ArticlePage.objects.filter(slug=art["slug"]).exists():
self.stdout.write(f" Skipping existing article: {art['title']}")
continue
related = art.pop("related_projects")
article = ArticlePage(**art)
article_index.add_child(instance=article)
article.related_projects.add(*related)
article.save()
self.stdout.write(f" Created article: {art['title']}")
def handle(self, *args, **options):
if options["clear"]:
self._clear_existing()
if not options["clear"] and ScenarioIndexPage.objects.filter(slug="scenarios").exists():
self.stdout.write("Seed data already exists. Use --clear to reset.")
return
self.stdout.write("Creating snippets...")
self._create_snippets()
@@ -320,6 +357,19 @@ class Command(BaseCommand):
# Verify
self.stdout.write("")
self.stdout.write("=== Seed verification ===")
for proj in SkeletonProjectPage.objects.all():
sc = proj.scenarios.count()
la = proj.languages.count()
status = "OK" if sc >= 1 and la >= 1 else "MISSING_M2M"
self.stdout.write(
f" {proj.title}: scenarios={sc}, languages={la} [{status}]"
)
for art in ArticlePage.objects.all():
rp = art.related_projects.count()
status = "OK" if rp >= 1 else "MISSING_M2M"
self.stdout.write(
f" {art.title}: related_projects={rp} [{status}]"
)
self.stdout.write(
f"Scenarios: {ScenarioPage.objects.count()} "
f"(≥3: {ScenarioPage.objects.count() >= 3})"
+10
View File
@@ -161,6 +161,16 @@ class SkeletonProjectPageTests(PageTreeMixin, TestCase):
except ValidationError:
self.fail("full_clean() should pass with all M2M set")
def test_m2m_persists_after_save_and_reload(self):
project = self._make_project()
project.languages.add(self.lang)
project.scenarios.add(self.scenario)
project.save()
reloaded = SkeletonProjectPage.objects.get(pk=project.pk)
self.assertGreaterEqual(reloaded.scenarios.count(), 1)
self.assertGreaterEqual(reloaded.languages.count(), 1)
class ArticlePageTests(PageTreeMixin, TestCase):
@classmethod
+13
View File
@@ -246,3 +246,16 @@
- 阻塞:无。
- 下一步:T-201 实现基础页面框架(Phase 2 前台 MVP)。
## 2026-07-06 T-104 seed_data M2M 持久化修复
- 状态:DONE
- 变更:
- `core/management/commands/seed_data.py`:`add_child()` 后 M2M `.add()` 仅在内存操作 cluster,不持久化到 DB;新增 `project.save()` / `article.save()` 确保关联写入
- 添加 `--clear` 参数和重复运行保护
- `core/tests.py`:新增 `test_m2m_persists_after_save_and_reload` 测试——save() 后从 DB 重新加载,断言 `scenarios.count() >= 1` 和 `languages.count() >= 1`
- 验证:
- `manage.py seed_data --clear`:5 个项目全部 `scenarios≥1, languages≥1 [OK]`,2 篇文章全部 `related_projects=1 [OK]`
- `manage.py test core home`:21 tests passed(新增 1 个持久化测试)
- 阻塞:无。
- 下一步:T-201 实现基础页面框架(Phase 2 前台 MVP)。