From b897447296e1b85ed38100af6acdd674b02423c5 Mon Sep 17 00:00:00 2001 From: Mark <162816078+markashton480@users.noreply.github.com> Date: Tue, 3 Mar 2026 20:37:58 +0000 Subject: [PATCH] Address PR review feedback - Gate e2e-admin superuser behind E2E_MODE env var (security) - Add status and tag filters to ArticleFilterSet - Set default_ordering to -published_date on listing viewset - Add summary to ArticlePage.search_fields for search support - Add 4 new tests for filters, ordering, and search fields Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- apps/blog/models.py | 5 ++- apps/blog/tests/test_admin_experience.py | 41 +++++++++++++++++++ apps/blog/wagtail_hooks.py | 28 +++++++++++++ .../management/commands/seed_e2e_content.py | 6 ++- docker-compose.yml | 1 + 5 files changed, 78 insertions(+), 3 deletions(-) diff --git a/apps/blog/models.py b/apps/blog/models.py index c237d8e..91c729c 100644 --- a/apps/blog/models.py +++ b/apps/blog/models.py @@ -15,6 +15,7 @@ from wagtail.admin.panels import FieldPanel, ObjectList, PageChooserPanel, Tabbe from wagtail.contrib.routable_page.models import RoutablePageMixin, route from wagtail.fields import RichTextField, StreamField from wagtail.models import Page +from wagtail.search import index from wagtailseo.models import SeoMixin from apps.blog.blocks import ARTICLE_BODY_BLOCKS @@ -232,7 +233,9 @@ class ArticlePage(SeoMixin, Page): ] ) - search_fields = Page.search_fields + search_fields = Page.search_fields + [ + index.SearchField("summary"), + ] def save(self, *args: Any, **kwargs: Any) -> None: if not self.category_id: diff --git a/apps/blog/tests/test_admin_experience.py b/apps/blog/tests/test_admin_experience.py index 035099c..5b1cc34 100644 --- a/apps/blog/tests/test_admin_experience.py +++ b/apps/blog/tests/test_admin_experience.py @@ -232,3 +232,44 @@ def test_article_edit_page_has_tabbed_interface(client, django_user_model, home_ assert "Metadata" in content assert "Publishing" in content assert "SEO" in content + + +@pytest.mark.django_db +@override_settings(ALLOWED_HOSTS=["testserver", "localhost", "127.0.0.1"]) +def test_articles_listing_has_status_filter(client, django_user_model, home_page): + """The Articles listing should accept status filter parameter.""" + admin = django_user_model.objects.create_superuser( + username="admin", email="admin@example.com", password="admin-pass" + ) + client.force_login(admin) + response = client.get("/cms/articles/?status=live") + assert response.status_code == 200 + + +@pytest.mark.django_db +@override_settings(ALLOWED_HOSTS=["testserver", "localhost", "127.0.0.1"]) +def test_articles_listing_has_tag_filter(client, django_user_model, home_page): + """The Articles listing should accept tag filter parameter.""" + admin = django_user_model.objects.create_superuser( + username="admin", email="admin@example.com", password="admin-pass" + ) + client.force_login(admin) + response = client.get("/cms/articles/?tag=1") + assert response.status_code == 200 + + +@pytest.mark.django_db +def test_article_listing_default_ordering(): + """ArticlePageListingViewSet should default to -published_date ordering.""" + from apps.blog.wagtail_hooks import ArticlePageListingViewSet + + assert ArticlePageListingViewSet.default_ordering == "-published_date" + + +@pytest.mark.django_db +def test_article_search_fields_include_summary(): + """ArticlePage.search_fields should index the summary field.""" + field_names = [ + f.field_name for f in ArticlePage.search_fields if hasattr(f, "field_name") + ] + assert "summary" in field_names diff --git a/apps/blog/wagtail_hooks.py b/apps/blog/wagtail_hooks.py index 18cbdad..288ce40 100644 --- a/apps/blog/wagtail_hooks.py +++ b/apps/blog/wagtail_hooks.py @@ -1,4 +1,5 @@ import django_filters +from taggit.models import Tag from wagtail import hooks from wagtail.admin.filters import WagtailFilterSet from wagtail.admin.ui.components import Component @@ -11,6 +12,12 @@ from wagtail.snippets.views.snippets import SnippetViewSet from apps.authors.models import Author from apps.blog.models import ArticlePage, Category, TagMetadata +STATUS_CHOICES = [ + ("live", "Published"), + ("draft", "Draft"), + ("scheduled", "Scheduled"), +] + class TagMetadataViewSet(SnippetViewSet): model = TagMetadata @@ -35,6 +42,17 @@ register_snippet(CategoryViewSet) # ── Articles page listing ──────────────────────────────────────────────────── +class StatusFilter(django_filters.ChoiceFilter): + def filter(self, qs, value): # noqa: A003 + if value == "live": + return qs.filter(live=True) + if value == "draft": + return qs.filter(live=False, go_live_at__isnull=True) + if value == "scheduled": + return qs.filter(live=False, go_live_at__isnull=False) + return qs + + class ArticleFilterSet(WagtailFilterSet): category = django_filters.ModelChoiceFilter( queryset=Category.objects.all(), @@ -44,6 +62,15 @@ class ArticleFilterSet(WagtailFilterSet): queryset=Author.objects.all(), empty_label="All authors", ) + status = StatusFilter( + choices=STATUS_CHOICES, + empty_label="All statuses", + ) + tag = django_filters.ModelChoiceFilter( + field_name="tags", + queryset=Tag.objects.all(), + empty_label="All tags", + ) class Meta: model = ArticlePage @@ -66,6 +93,7 @@ class ArticlePageListingViewSet(PageListingViewSet): PageStatusColumn("status", sort_key="live"), ] filterset_class = ArticleFilterSet + default_ordering = "-published_date" @hooks.register("register_admin_viewset") diff --git a/apps/core/management/commands/seed_e2e_content.py b/apps/core/management/commands/seed_e2e_content.py index 068c049..e830c0d 100644 --- a/apps/core/management/commands/seed_e2e_content.py +++ b/apps/core/management/commands/seed_e2e_content.py @@ -1,5 +1,7 @@ from __future__ import annotations +import os + from django.contrib.auth import get_user_model from django.core.management.base import BaseCommand from taggit.models import Tag @@ -200,8 +202,8 @@ class Command(BaseCommand): ] ) - # Admin user for E2E admin tests - if not User.objects.filter(username="e2e-admin").exists(): + # Admin user for E2E admin tests — only when E2E_MODE is set + if os.environ.get("E2E_MODE") and not User.objects.filter(username="e2e-admin").exists(): User.objects.create_superuser( username="e2e-admin", email="e2e-admin@example.com", diff --git a/docker-compose.yml b/docker-compose.yml index 50e3f11..7811989 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,6 +24,7 @@ services: EMAIL_BACKEND: django.core.mail.backends.console.EmailBackend DEFAULT_FROM_EMAIL: hello@nohypeai.com NEWSLETTER_PROVIDER: buttondown + E2E_MODE: "1" depends_on: db: condition: service_healthy