feat: rebuild site content management

Replace legacy FAQ, downloads, and release flows with project and blog publishing. Add configurable branding and theme fields plus clean-start Compose seeding support.
This commit is contained in:
mohamad
2026-09-24 02:32:27 +03:30
parent 4fc4282f0f
commit 588d95f889
43 changed files with 703 additions and 1193 deletions
+10 -10
View File
@@ -103,37 +103,37 @@ class PageVideoTest(TestCase):
self.assertContains(response, "Intro")
self.assertContains(response, "youtube-nocookie.com/embed/dQw4w9WgXcQ")
def test_faq_page_video(self):
def test_membership_page_video(self):
PageVideo.objects.create(
page=PageVideo.PAGE_FAQ,
page=PageVideo.PAGE_CONTACT,
title="Tutorial",
video_source="youtube",
video_url="https://youtu.be/dQw4w9WgXcQ",
is_active=True,
)
response = self.client.get(reverse("pages:faq"))
response = self.client.get(reverse("pages:contact"))
self.assertContains(response, "Tutorial")
def test_page_shows_three_video_preview_and_archive_link(self):
self._create_page_videos(PageVideo.PAGE_FAQ, 8)
self._create_page_videos(PageVideo.PAGE_CONTACT, 8)
response = self.client.get(reverse("pages:faq"))
response = self.client.get(reverse("pages:contact"))
self.assertEqual(response.content.count(b"youtube-nocookie.com/embed"), 3)
self.assertContains(
response,
reverse(
"pages:video_archive",
kwargs={"library": PageVideo.PAGE_FAQ},
kwargs={"library": PageVideo.PAGE_CONTACT},
),
)
self.assertContains(response, "More videos")
def test_page_video_archive_is_paginated(self):
self._create_page_videos(PageVideo.PAGE_FAQ, 8)
self._create_page_videos(PageVideo.PAGE_CONTACT, 8)
archive_url = reverse(
"pages:video_archive",
kwargs={"library": PageVideo.PAGE_FAQ},
kwargs={"library": PageVideo.PAGE_CONTACT},
)
first_page = self.client.get(archive_url)
@@ -248,7 +248,7 @@ class VideoDescriptionFormatTest(TestCase):
def test_markdown_description_renders(self):
PageVideo.objects.create(
page=PageVideo.PAGE_FAQ,
page=PageVideo.PAGE_CONTACT,
title="Guide",
description="**Bold** intro",
description_format="markdown",
@@ -256,5 +256,5 @@ class VideoDescriptionFormatTest(TestCase):
video_url="https://youtu.be/dQw4w9WgXcQ",
is_active=True,
)
response = self.client.get(reverse("pages:faq"))
response = self.client.get(reverse("pages:contact"))
self.assertContains(response, "<strong>Bold</strong>")
+34 -27
View File
@@ -1,7 +1,8 @@
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone
from apps.pages.models import FAQEntry
from apps.pages.models import BlogPost, HomepageSection
class HomeViewTest(TestCase):
@@ -13,6 +14,16 @@ class HomeViewTest(TestCase):
response = self.client.get(reverse("pages:home"))
self.assertTemplateUsed(response, "pages/home.html")
def test_section_description_renders_markdown_and_html(self):
HomepageSection.objects.create(
section_type=HomepageSection.TYPE_FEATURES,
title="Rich section",
description="**Formatted** content",
description_format="markdown",
)
response = self.client.get(reverse("pages:home"))
self.assertContains(response, "<strong>Formatted</strong>")
class AboutViewTest(TestCase):
def test_about_returns_200(self):
@@ -24,34 +35,30 @@ class AboutViewTest(TestCase):
self.assertTemplateUsed(response, "pages/about.html")
class FAQViewTest(TestCase):
class BlogViewTest(TestCase):
def setUp(self):
FAQEntry.objects.create(
question="What is the license?",
answer="It is CC BY-NC-SA.",
order=1,
is_active=True,
)
FAQEntry.objects.create(
question="Hidden question",
answer="Hidden answer",
order=2,
is_active=False,
self.post = BlogPost.objects.create(
title="Research update",
slug="research-update",
excerpt="A short summary.",
content="## Results\n\n**Promising** findings.",
content_format="markdown",
status=BlogPost.STATUS_PUBLISHED,
published_at=timezone.now(),
)
def test_faq_returns_200(self):
response = self.client.get(reverse("pages:faq"))
self.assertEqual(response.status_code, 200)
def test_blog_list_and_detail_render_published_post(self):
listing = self.client.get(reverse("pages:blog_list"))
detail = self.client.get(self.post.get_absolute_url())
self.assertContains(listing, self.post.title)
self.assertContains(detail, "<h2>Results</h2>")
self.assertContains(detail, "<strong>Promising</strong>")
def test_faq_uses_correct_template(self):
response = self.client.get(reverse("pages:faq"))
self.assertTemplateUsed(response, "pages/faq.html")
def test_faq_only_shows_active_entries(self):
response = self.client.get(reverse("pages:faq"))
entries = response.context["faq_entries"]
self.assertEqual(entries.count(), 1)
self.assertEqual(entries.first().question, "What is the license?")
def test_draft_post_is_not_public(self):
self.post.status = BlogPost.STATUS_DRAFT
self.post.save(update_fields=["status"])
self.assertNotContains(self.client.get(reverse("pages:blog_list")), self.post.title)
self.assertEqual(self.client.get(self.post.get_absolute_url()).status_code, 404)
class ContactViewTest(TestCase):
@@ -69,7 +76,7 @@ class NavigationContextTest(TestCase):
urls = [
reverse("pages:home"),
reverse("pages:about"),
reverse("pages:faq"),
reverse("pages:blog_list"),
reverse("pages:contact"),
reverse("products:overview"),
]
@@ -85,7 +92,7 @@ class NavigationContextTest(TestCase):
urls = [
reverse("pages:home"),
reverse("pages:about"),
reverse("pages:faq"),
reverse("pages:blog_list"),
reverse("pages:contact"),
]
for url in urls: