Skip to content

Commit 69029dc

Browse files
authored
Merge pull request #1170 from WesternFriend/tags-n1-query
Enhance article templates and optimize database queries for tagged pages
2 parents 8b06c2e + f588b10 commit 69029dc

File tree

8 files changed

+168
-53
lines changed

8 files changed

+168
-53
lines changed

library/models.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,22 +72,21 @@ class LibraryItem(DrupalFields, Page): # type: ignore
7272

7373
@classmethod
7474
def get_queryset(cls):
75-
related_fields = [
75+
related_prefetch = [
7676
"authors__author",
7777
"topics__topic",
78-
"item_audience",
79-
"item_genre",
80-
"item_medium",
81-
"item_time_period",
82-
"tags__tag",
78+
"tags",
8379
]
8480
return (
85-
super()
86-
.get_queryset()
87-
.filter(live=True)
88-
.prefetch_related(
89-
*related_fields,
81+
cls.objects.live()
82+
.defer_streamfields()
83+
.select_related(
84+
"item_audience",
85+
"item_genre",
86+
"item_medium",
87+
"item_time_period",
9088
)
89+
.prefetch_related(*related_prefetch)
9190
)
9291

9392
content_panels = Page.content_panels + [

magazine/models.py

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -299,25 +299,13 @@ def get_context(
299299
) -> dict:
300300
context = super().get_context(request)
301301

302-
context["articles"] = (
303-
MagazineArticle.objects.filter(
304-
department__title=self.title,
305-
)
306-
.live()
307-
.prefetch_related(
308-
"authors__author",
309-
"issue",
310-
)
311-
)
312-
313302
articles = (
314303
MagazineArticle.objects.filter(
315304
department__title=self.title,
316305
)
317306
.live()
318307
.prefetch_related(
319308
"authors__author",
320-
"issue",
321309
)
322310
)
323311

@@ -365,9 +353,16 @@ class MagazineArticle(DrupalFields, Page): # type: ignore
365353

366354
@classmethod
367355
def get_queryset(cls):
368-
"""Prefetch authors and tags for performance."""
369-
related_fields = ["authors__author", "tags__tag", "department"]
370-
return super().get_queryset().prefetch_related(*related_fields)
356+
"""Optimize related fetches for listings.
357+
358+
Note: Use cls.objects rather than super().get_queryset() because the
359+
base class does not provide a classmethod get_queryset.
360+
"""
361+
return (
362+
cls.objects.defer_streamfields()
363+
.select_related("department")
364+
.prefetch_related("authors__author", "tags")
365+
)
371366

372367
class Meta:
373368
verbose_name = "Magazine Article"
Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{% load wagtailcore_tags %}
1+
{% load i18n wagtailcore_tags %}
22

33
<article class="card bg-base-100 shadow-sm hover:shadow-md transition-shadow mb-4">
44
<div class="card-body">
@@ -8,29 +8,42 @@ <h3 class="card-title">
88
</a>
99
</h3>
1010

11-
{% if article.authors.count %}
12-
<div class="mb-2">
13-
<span class="font-medium">Author(s):</span>
14-
{% for author in article.authors.all %}
15-
{% if author.author.live %}
16-
<a href="{% pageurl author.author %}">{{ author.author }}</a>{% if not forloop.last %},{% endif %}
17-
{% else %}
18-
{{ author.author }}{% if not forloop.last %},{% endif %}
19-
{% endif %}
20-
{% endfor %}
21-
</div>
22-
{% endif %}
11+
{% with author_links=article.authors.all %}
12+
{% if author_links %}
13+
<div class="mb-2">
14+
<span id="authors-label-{{ article.pk }}" class="font-medium">{% translate "Authors" %}:</span>
15+
<ul class="inline list-none p-0 m-0" aria-labelledby="authors-label-{{ article.pk }}">
16+
{% for author in author_links %}
17+
<li class="inline">
18+
{% if author.author.live %}
19+
<a href="{% pageurl author.author %}">{{ author.author }}</a>
20+
{% else %}
21+
{{ author.author }}
22+
{% endif %}
23+
{% if not forloop.last %}, {% endif %}
24+
</li>
25+
{% endfor %}
26+
</ul>
27+
</div>
28+
{% endif %}
29+
{% endwith %}
2330

2431
<div class="prose">
2532
{{ article.teaser|richtext }}
2633
</div>
2734

28-
<div class="card-actions">
29-
<span>
30-
Issue: <a href="{% pageurl article.get_parent %}" class="link">
31-
{{ article.get_parent }} ({{ article.get_parent.specific.publication_date|date:"F Y" }})
32-
</a>
33-
</span>
34-
</div>
35+
{% with issue=article.get_parent.specific %}
36+
<div class="card-actions">
37+
<span>
38+
<span class="font-medium">{% translate "Issue" %}:</span>
39+
<a href="{% pageurl issue %}" class="link">{{ issue }}</a>
40+
{% if issue.publication_date %}
41+
(<time datetime="{{ issue.publication_date|date:'Y-m-d' }}">
42+
{{ issue.publication_date|date:"F Y" }}
43+
</time>)
44+
{% endif %}
45+
</span>
46+
</div>
47+
{% endwith %}
3548
</div>
3649
</article>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{% include "magazine/magazine_article_summary.html" with article=entity %}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{% load wagtailcore_tags %}
2+
<article class="card bg-base-100 shadow-sm hover:shadow-md transition-shadow mb-4">
3+
<div class="card-body">
4+
<h3 class="card-title">
5+
<a href="{% pageurl entity %}">{{ entity.title }}</a>
6+
</h3>
7+
{% if entity.specific.publication_date %}
8+
<p class="text-sm text-base-content/70">
9+
Published
10+
<time datetime="{{ entity.specific.publication_date|date:'Y-m-d' }}">
11+
{{ entity.specific.publication_date|date:"F Y" }}
12+
</time>
13+
</p>
14+
{% endif %}
15+
</div>
16+
</article>

tags/tests.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
import re
12
from django.test import TestCase
23
from django.urls import reverse
34
from taggit.models import Tag
45
from library.models import LibraryItem
56
from library.factories import LibraryItemFactory
67
from magazine.factories import MagazineArticleFactory
7-
from magazine.models import MagazineArticle
8+
from magazine.models import MagazineArticle, MagazineArticleAuthor
89
from news.factories import NewsItemFactory
910
from news.models import NewsItem
1011
from wf_pages.factories import WfPageFactory
1112
from wf_pages.models import WfPage
13+
from contact.factories import PersonFactory
1214

1315

1416
class TaggedPageListViewQuerysetAndContentOrderTest(TestCase):
@@ -157,3 +159,65 @@ def test_pagination_second_page(self):
157159
paginated_context.page.previous_page_number(),
158160
expected_previous_page,
159161
)
162+
163+
164+
class TaggedPageListViewTemplateRenderingTest(TestCase):
165+
def setUp(self):
166+
self.tag = Tag.objects.create(name="Authors Tag", slug="authors-tag")
167+
self.url = reverse("tags:tagged_page_list", kwargs={"tag": self.tag.slug})
168+
169+
# Create a magazine article and attach authors
170+
self.article = MagazineArticleFactory(title="Article With Authors")
171+
self.article.tags.add(self.tag)
172+
self.article.save()
173+
174+
self.person_live = PersonFactory()
175+
# Publish person_live so template renders it as a link
176+
self.person_live.save_revision().publish()
177+
178+
self.person_not_live = PersonFactory()
179+
# Ensure non-live stays unpublished for template logic
180+
self.person_not_live.live = False
181+
self.person_not_live.save()
182+
183+
MagazineArticleAuthor.objects.create(
184+
article=self.article,
185+
author=self.person_live,
186+
)
187+
MagazineArticleAuthor.objects.create(
188+
article=self.article,
189+
author=self.person_not_live,
190+
)
191+
192+
def test_authors_and_issue_render_with_accessible_markup(self):
193+
response = self.client.get(self.url)
194+
html = response.content.decode()
195+
196+
# Authors label and list present with dynamic id
197+
self.assertIn("Authors", html)
198+
# Extract actual authors-label id from the span
199+
m = re.search(
200+
r"<span[^>]*id=\"(authors-label-[^\"]+)\"[^>]*>\s*Authors\s*:</span>",
201+
html,
202+
)
203+
self.assertIsNotNone(m, "Could not find authors label span with id")
204+
label_id = m.group(1)
205+
# Verify aria-labelledby matches the extracted id
206+
self.assertIn(f'aria-labelledby="{label_id}"', html)
207+
208+
# Live author should be linked
209+
live_title = self.person_live.title
210+
self.assertRegex(html, rf"<a[^>]*>\s*{re.escape(live_title)}\s*</a>")
211+
# Non-live author should be plain text (no anchor)
212+
non_live_title = self.person_not_live.title
213+
self.assertIn(non_live_title, html)
214+
self.assertIsNone(
215+
re.search(rf"<a[^>]*>\s*{re.escape(non_live_title)}\s*</a>", html),
216+
)
217+
218+
# Issue label and machine-readable date present
219+
self.assertIn("Issue", html)
220+
expected_date = self.article.get_parent().specific.publication_date.strftime(
221+
"%Y-%m-%d",
222+
)
223+
self.assertIn(f'<time datetime="{expected_date}"', html)

tags/views.py

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,42 @@ def get_queryset(self):
2525
# Get all the pages tagged with the given tag
2626
filter_condition = Q(tags__slug__in=[tag])
2727

28-
library_items = LibraryItem.objects.filter(filter_condition).order_by("title")
28+
library_items = (
29+
LibraryItem.get_queryset()
30+
.filter(filter_condition)
31+
.live()
32+
.public()
33+
.select_related("content_type")
34+
.prefetch_related("authors__author")
35+
# DB-level ordering unnecessary; combined list is sorted below
36+
)
2937

30-
magazine_articles = MagazineArticle.objects.filter(filter_condition).order_by(
31-
"title",
38+
magazine_articles = (
39+
MagazineArticle.get_queryset()
40+
.filter(filter_condition)
41+
.live()
42+
.public()
43+
.select_related("content_type")
44+
.prefetch_related("authors__author")
45+
# DB-level ordering unnecessary; combined list is sorted below
3246
)
3347

34-
news_items = NewsItem.objects.filter(filter_condition).order_by("title")
48+
news_items = (
49+
NewsItem.objects.filter(filter_condition)
50+
.live()
51+
.public()
52+
.select_related("content_type")
53+
# DB-level ordering unnecessary; combined list is sorted below
54+
)
3555

36-
pages = WfPage.objects.filter(filter_condition).order_by("title")
56+
pages = (
57+
WfPage.get_queryset()
58+
.filter(filter_condition)
59+
.live()
60+
.public()
61+
.select_related("content_type")
62+
# DB-level ordering unnecessary; combined list is sorted below
63+
)
3764

3865
combined_queryset_list = list(
3966
chain(

wf_pages/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class WfPage(DrupalFields, Page):
8787
@classmethod
8888
def get_queryset(cls):
8989
"""Prefetch tags for performance."""
90-
return super().get_queryset().prefetch_related("tags__tag")
90+
return cls.objects.prefetch_related("tags")
9191

9292
content_panels = Page.content_panels + [
9393
FieldPanel("body"),

0 commit comments

Comments
 (0)