Implement category taxonomy and navigation (Issue #35) #36

Merged
mark merged 5 commits from feature/category-navigation-system into main 2026-03-03 13:03:27 +00:00
4 changed files with 27 additions and 14 deletions
Showing only changes of commit 04a55844fd - Show all commits

View File

@@ -49,10 +49,7 @@ class HomePage(Page):
id__in=ArticlePage.objects.live().public().values_list("tags__id", flat=True) id__in=ArticlePage.objects.live().public().values_list("tags__id", flat=True)
).distinct().order_by("name") ).distinct().order_by("name")
) )
category_ids = ArticlePage.objects.live().public().values_list("category_id", flat=True) ctx["available_categories"] = Category.objects.filter(show_in_nav=True).order_by("sort_order", "name")
ctx["available_categories"] = Category.objects.filter(show_in_nav=True, id__in=category_ids).order_by(
"sort_order", "name"
)
return ctx return ctx
@@ -76,9 +73,7 @@ class ArticleIndexPage(RoutablePageMixin, Page):
def get_listing_context(self, request, active_category=None): def get_listing_context(self, request, active_category=None):
tag_slug = request.GET.get("tag") tag_slug = request.GET.get("tag")
articles = self.get_articles() articles = self.get_articles()
all_articles = articles available_categories = Category.objects.order_by("sort_order", "name")
category_ids = all_articles.values_list("category_id", flat=True)
available_categories = Category.objects.filter(id__in=category_ids).order_by("sort_order", "name")
category_links = [ category_links = [
{"category": category, "url": self.get_category_url(category)} {"category": category, "url": self.get_category_url(category)}
for category in available_categories for category in available_categories
@@ -111,9 +106,7 @@ class ArticleIndexPage(RoutablePageMixin, Page):
@route(r"^category/(?P<category_slug>[-\w]+)/$") @route(r"^category/(?P<category_slug>[-\w]+)/$")
def category_listing(self, request, category_slug): def category_listing(self, request, category_slug):
category_ids = self.get_articles().values_list("category_id", flat=True) category = get_object_or_404(Category, slug=category_slug)
category_qs = Category.objects.filter(id__in=category_ids)
category = get_object_or_404(category_qs, slug=category_slug)
return self.render(request, context_overrides=self.get_listing_context(request, active_category=category)) return self.render(request, context_overrides=self.get_listing_context(request, active_category=category))
def get_context(self, request, *args, **kwargs): def get_context(self, request, *args, **kwargs):

View File

@@ -233,3 +233,14 @@ def test_article_index_category_route_supports_tag_filter(client, home_page):
assert resp.status_code == 200 assert resp.status_code == 200
assert "Keep Me" in html assert "Keep Me" in html
assert "Drop Me" not in html assert "Drop Me" not in html
@pytest.mark.django_db
def test_article_index_category_route_allows_empty_existing_category(client, home_page):
index = ArticleIndexPage(title="Articles", slug="articles")
home_page.add_child(instance=index)
Category.objects.create(name="Opinion", slug="opinion")
resp = client.get("/articles/category/opinion/")
assert resp.status_code == 200
assert "No articles found." in resp.content.decode()

View File

@@ -4,7 +4,7 @@ from django import template
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from wagtail.models import Site from wagtail.models import Site
from apps.blog.models import ArticleIndexPage, TagMetadata from apps.blog.models import ArticleIndexPage, Category, TagMetadata
from apps.core.models import SiteSettings from apps.core.models import SiteSettings
from apps.legal.models import LegalPage from apps.legal.models import LegalPage
@@ -58,9 +58,7 @@ def get_categories_nav(context):
index_page = index_qs.first() index_page = index_qs.first()
if not index_page: if not index_page:
return [] return []
categories = index_page.get_listing_context( categories = Category.objects.filter(show_in_nav=True).order_by("sort_order", "name")
request, active_category=None
)["available_categories"].filter(show_in_nav=True)
return [ return [
{ {
"name": category.name, "name": category.name,

View File

@@ -37,3 +37,14 @@ def test_categories_nav_tag_renders_category_link(client, home_page):
resp = client.get("/") resp = client.get("/")
assert resp.status_code == 200 assert resp.status_code == 200
assert "/articles/category/reviews/" in resp.content.decode() assert "/articles/category/reviews/" in resp.content.decode()
@pytest.mark.django_db
def test_categories_nav_tag_includes_empty_nav_category(client, home_page):
index = ArticleIndexPage(title="Articles", slug="articles")
home_page.add_child(instance=index)
Category.objects.create(name="Benchmarks", slug="benchmarks", show_in_nav=True)
resp = client.get("/")
assert resp.status_code == 200
assert "/articles/category/benchmarks/" in resp.content.decode()