Skip to content

Conversation

@brylie
Copy link
Member

@brylie brylie commented Aug 31, 2025

Improve article templates for better author display and optimize database queries by using select_related and prefetch_related for efficiency.

Summary by Sourcery

Streamline article templates for cleaner author display, improve efficiency of tagged page queries with optimized ORM fetching, and introduce new search result templates for magazine issues and articles

New Features:

  • Add dedicated search result templates for magazine issues and articles

Enhancements:

  • Refactor article summary template to consolidate author listing using a with block
  • Optimize tag-based page queries by adding select_related on content_type and prefetch_related on related authors and issues

Summary by CodeRabbit

  • New Features

    • Search results include magazine issues as linked cards with optional published month/year (machine-readable date included).
    • Article summaries show a localized "Authors" label; authors render inline with live authors as links and non-live authors as plain text.
    • Search results delegate article rendering to the article summary partial.
  • Refactor

    • Query/prefetch and related-data loading optimized across articles, issues, library items, workflow pages and tag listings.
  • Tests

    • Added template tests verifying authors and issue metadata rendering.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Aug 31, 2025

Reviewer's Guide

This PR refactors the article summary template for cleaner author handling, enhances tagged pages view performance using select_related and prefetch_related, and adds dedicated search result templates for magazine issues and articles.

Class diagram for optimized queryset usage in tagged pages

classDiagram
    LibraryItem <|-- ContentType
    LibraryItem o-- Author
    MagazineArticle <|-- ContentType
    MagazineArticle o-- Author
    MagazineArticle o-- Issue
    NewsItem <|-- ContentType
    WfPage <|-- ContentType
    class LibraryItem {
        +title
        +tags
        +authors
        +content_type
    }
    class MagazineArticle {
        +title
        +tags
        +authors
        +content_type
        +issue
    }
    class NewsItem {
        +title
        +tags
        +content_type
    }
    class WfPage {
        +title
        +tags
        +content_type
    }
    class Author {
        +author
        +live
    }
    class Issue {
        +publication_date
    }
    class ContentType {
    }
Loading

File-Level Changes

Change Details Files
Simplify author display logic in article summary template
  • Introduce a {% with author_links=article.authors.all %} block
  • Replace direct authors.count check with if author_links
  • Loop over author_links instead of querying authors twice
magazine/templates/magazine/magazine_article_summary.html
Optimize tagged pages view with ORM prefetching
  • Add select_related('content_type') to all tagged queries
  • Add prefetch_related('authors__author') on LibraryItem and NewsItem
  • Add prefetch_related('authors__author', 'issue') on MagazineArticle
  • Apply select_related and prefetch_related on WfPage
tags/views.py
Add search result templates for magazine issues and articles
  • Create magazine_issue.html with card layout and publication date
  • Create magazine_article.html that includes the summary template
magazine/templates/search/magazine_issue.html
magazine/templates/search/magazine_article.html

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link

coderabbitai bot commented Aug 31, 2025

Warning

Rate limit exceeded

@brylie has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 3 minutes and 35 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 6d7b4a6 and f588b10.

📒 Files selected for processing (2)
  • magazine/templates/magazine/magazine_article_summary.html (2 hunks)
  • tags/tests.py (2 hunks)

Walkthrough

Adds i18n and safer author and issue rendering to the magazine article summary template, introduces/updates per-type search templates for magazine articles and issues, and refactors several model and view get_queryset implementations to use manager-based live()/select_related()/prefetch_related() with StreamField deferral and a final Python-level sort for combined tag results.

Changes

Cohort / File(s) Summary of changes
Article summary template
magazine/templates/magazine/magazine_article_summary.html
Loads i18n; replaces article.authors.count guard with with author_links=article.authors.all; renders an inline Authors list (live authors as {% pageurl %} links, non‑live as text) separated by commas and uses translated "Authors" label; adds with issue=article.get_parent and renders Issue: [link] plus optional publication date in a <time> (datetime=Y-m-d, display=F Y).
Search templates
magazine/templates/search/magazine_article.html, magazine/templates/search/magazine_issue.html
magazine_article.html now includes the summary partial (magazine/magazine_article_summary.html) passing article=entity. Adds magazine_issue.html to render a linked issue title with {% pageurl entity %} and an optional "Published" month-year line when entity.specific.publication_date exists.
Tagged/list view querysets
tags/views.py
Rewrites TaggedPageListView.get_queryset() to build each type's queryset via chained .filter(...).live().public().select_related("content_type") with appropriate prefetch_related (e.g., authors__author); removes DB-level ordering from querysets and returns a combined list sorted in Python by title.lower().
LibraryItem queryset
library/models.py
get_queryset switched to cls.objects.live().defer_streamfields().select_related("item_audience","item_genre","item_medium","item_time_period").prefetch_related(*related_prefetch) with related_prefetch = ["authors__author","topics__topic","tags"] (moved several FKs to select_related, changed tags prefetch target).
Magazine models / querysets
magazine/models.py
Removed some issue prefetches from index page context builders; MagazineArticle.get_queryset now returns cls.objects.defer_streamfields().select_related("department").prefetch_related("authors__author","tags"), replacing prior super()-based prefetch pattern and tags__tag.
WfPage queryset
wf_pages/models.py
WfPage.get_queryset now uses cls.objects.prefetch_related("tags") instead of super().get_queryset().prefetch_related("tags__tag").
Tests
tags/tests.py
Adds tests verifying accessible rendering of Authors and Issue metadata in the tagged page list template (checks Authors section presence, live author rendered as link, Issue label and machine-readable date).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant SearchView as Search Results Page
  participant ArticleTmpl as search/magazine_article.html
  participant Summary as magazine_article_summary.html
  participant Wagtail as Wagtail (pageurl)

  User->>SearchView: request results
  SearchView->>ArticleTmpl: render entity
  ArticleTmpl->>Summary: include (article=entity)
  Summary->>Summary: with author_links = article.authors.all\nwith issue = article.get_parent
  alt authors present
    Summary->>Wagtail: pageurl(author) for live authors
    Wagtail-->>Summary: author URLs
    Summary-->>ArticleTmpl: render inline author list (links/text + commas)
  else no authors
    Summary-->>ArticleTmpl: omit authors block
  end
  alt issue present
    Summary->>Wagtail: pageurl(issue)
    Wagtail-->>Summary: issue URL
    Summary-->>ArticleTmpl: render "Issue: [link] (Month Year)"
  end
  ArticleTmpl-->>User: rendered result item
Loading
sequenceDiagram
  autonumber
  participant TaggedView as TaggedPageListView
  participant ORM as Django ORM
  participant DB as Database

  TaggedView->>ORM: build LibraryItem qs\n.filter(...).live().public()\n.select_related("content_type")\n.prefetch_related("authors__author")
  ORM->>DB: query + prefetch
  DB-->>ORM: rows + related

  TaggedView->>ORM: build MagazineArticle qs\n.filter(...).live().public()\n.select_related("content_type")\n.prefetch_related("authors__author")
  ORM->>DB: query + prefetch
  DB-->>ORM: rows + related

  TaggedView->>ORM: build NewsItem qs\n.filter(...).live().public()\n.select_related("content_type")
  ORM->>DB: query
  DB-->>ORM: rows

  TaggedView->>ORM: build WfPage qs\n.filter(...).live().public()\n.select_related("content_type")
  ORM->>DB: query
  DB-->>ORM: rows

  TaggedView->>TaggedView: chain all results and sort by title.lower()
  TaggedView-->>Caller: combined sorted list
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A rabbit hops through templates bright,
I stitch the authors, set issues right.
Links that bloom and dates that chime,
Prefetch paths saved one at a time.
Happy hops—rendered just in time! 🐇

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch tags-n1-query

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (7)
magazine/templates/search/magazine_issue.html (2)

8-8: Use a semantic time element for dates

Make the published date machine-readable and accessible.

-      <p class="text-sm text-base-content/70">Published {{ entity.specific.publication_date|date:"F Y" }}</p>
+      <p class="text-sm text-base-content/70">
+        Published
+        <time datetime="{{ entity.specific.publication_date|date:'Y-m-d' }}">
+          {{ entity.specific.publication_date|date:"F Y" }}
+        </time>
+      </p>

7-8: Potential N+1: consider passing specific objects from the view

Accessing entity.specific in templates can trigger extra queries per row. If feasible, have the search view return specific() results and pass entity directly.

tags/views.py (2)

28-33: Avoid redundant DB ordering; rely on the combined Python sort

Since the combined list is sorted by title afterward, the per-query .order_by("title") adds unnecessary DB work.

-            .order_by("title")
+            # DB-level ordering is unnecessary; the combined list is sorted below

Apply to all four query blocks.

Also applies to: 35-40, 42-46, 48-52


28-33: Leverage model-level optimized querysets

LibraryItem, MagazineArticle, and WfPage expose class-level get_queryset() with prefetches. Use them to centralize optimizations and avoid drift.

-        library_items = (
-            LibraryItem.objects.filter(filter_condition).live().public()
+        library_items = (
+            LibraryItem.get_queryset().filter(filter_condition).live().public()
             .select_related("content_type")
             .prefetch_related("authors__author")
         )

-        magazine_articles = (
-            MagazineArticle.objects.filter(filter_condition).live().public()
+        magazine_articles = (
+            MagazineArticle.get_queryset().filter(filter_condition).live().public()
             .select_related("content_type")
-            .prefetch_related("authors__author", "issue")
+            .prefetch_related("authors__author")  # keep only concrete relations here
         )

-        pages = (
-            WfPage.objects.filter(filter_condition).live().public()
+        pages = (
+            WfPage.get_queryset().filter(filter_condition).live().public()
             .select_related("content_type")
         )

Also applies to: 35-40, 48-52

magazine/templates/search/magazine_article.html (1)

1-1: Confirm specific() is provided upstream to avoid N+1

Including with article=entity.specific is fine, but it may trigger per-row queries. If the search view can return specific() results, pass article=entity.

magazine/templates/magazine/magazine_article_summary.html (2)

17-19: Add a space after commas in author list

Improves readability and matches common UI conventions.

-                            <a href="{% pageurl author.author %}">{{ author.author }}</a>{% if not forloop.last %},{% endif %}
+                            <a href="{% pageurl author.author %}">{{ author.author }}</a>{% if not forloop.last %}, {% endif %}
...
-                            {{ author.author }}{% if not forloop.last %},{% endif %}
+                            {{ author.author }}{% if not forloop.last %}, {% endif %}

30-35: Avoid duplicate get_parent() calls and use a semantic time element

Cache the parent lookup and render a machine-readable date. Reduces potential extra queries and improves accessibility.

-        <div class="card-actions">
-            <span>
-                Issue: <a href="{% pageurl article.get_parent %}" class="link">
-                    {{ article.get_parent }} ({{ article.get_parent.specific.publication_date|date:"F Y" }})
-                </a>
-            </span>
-        </div>
+        {% with issue=article.get_parent %}
+        <div class="card-actions">
+            <span>
+                Issue: <a href="{% pageurl issue %}" class="link">
+                    {{ issue }}
+                    {% if issue.specific.publication_date %}
+                        (<time datetime="{{ issue.specific.publication_date|date:'Y-m-d' }}">
+                            {{ issue.specific.publication_date|date:"F Y" }}
+                        </time>)
+                    {% endif %}
+                </a>
+            </span>
+        </div>
+        {% endwith %}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8b06c2e and 792aa26.

📒 Files selected for processing (4)
  • magazine/templates/magazine/magazine_article_summary.html (1 hunks)
  • magazine/templates/search/magazine_article.html (1 hunks)
  • magazine/templates/search/magazine_issue.html (1 hunks)
  • tags/views.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.html

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.html: Follow WCAG 2.1 and ARIA 1.1; use semantic HTML, appropriate ARIA roles/attributes, and ensure all interactive elements are keyboard accessible
Use semantic HTML tags (header, nav, main, footer, section, article, aside, figure/figcaption, time, address) and proper heading structure
Wrap content rendered via Wagtail’s richtext filter in a Tailwind prose class
Use Bootstrap Icons via bi bi-<icon-name> classes and keep icon usage consistent across the UI

Files:

  • magazine/templates/search/magazine_issue.html
  • magazine/templates/search/magazine_article.html
  • magazine/templates/magazine/magazine_article_summary.html
**/*.{html,css}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer Tailwind CSS and daisyUI for new styles; avoid introducing new Bootstrap styles

Files:

  • magazine/templates/search/magazine_issue.html
  • magazine/templates/search/magazine_article.html
  • magazine/templates/magazine/magazine_article_summary.html
🧬 Code graph analysis (2)
magazine/templates/search/magazine_issue.html (1)
magazine/models.py (6)
  • MagazineIssue (115-169)
  • MagazineIndexPage (36-112)
  • MagazineArticle (230-364)
  • DeepArchiveIndexPage (492-554)
  • ArchiveIssue (455-489)
  • get_context (540-554)
tags/views.py (4)
library/models.py (1)
  • LibraryItem (32-167)
magazine/models.py (3)
  • MagazineArticle (329-470)
  • MagazineArticleTag (172-175)
  • MagazineArticle (230-364)
news/models.py (1)
  • NewsItem (103-156)
wf_pages/models.py (1)
  • WfPage (60-110)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: runner-job
🔇 Additional comments (1)
magazine/templates/magazine/magazine_article_summary.html (1)

11-24: Authors block looks good and avoids extra count() query

Using a with to cache article.authors.all and conditionally rendering is clean and efficient.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tags/views.py (1)

91-95: Avoid recomputing get_queryset() in get_context_data (double work).

ListView.get() already set self.object_list via get_queryset(). Use that to feed pagination.

-        context["paginated_items"] = get_paginated_items(
-            items=self.get_queryset(),
+        items = context.get("object_list", getattr(self, "object_list", []))
+        context["paginated_items"] = get_paginated_items(
+            items=items,
             items_per_page=self.paginate_by,
             page_number=page_number,
         )
♻️ Duplicate comments (1)
tags/views.py (1)

48-54: LGTM: visibility filters and content_type select_related added.

This addresses the prior concern about exposing drafts/private content.

🧹 Nitpick comments (6)
magazine/templates/magazine/magazine_article_summary.html (2)

11-24: Authors block: good consolidation; consider list semantics and i18n (minor).

  • Presenting multiple authors as a list is more accessible than comma-separated spans.
  • Literal strings (“Author(s):”) should be wrapped for translation if i18n is enabled.

Example tweak:

-                <div class="mb-2">
-                    <span class="font-medium">Author(s):</span>
-                    {% for author in author_links %}
-                        {% if author.author.live %}
-                            <a href="{% pageurl author.author %}">{{ author.author }}</a>{% if not forloop.last %}, {% endif %}
-                        {% else %}
-                            {{ author.author }}{% if not forloop.last %}, {% endif %}
-                        {% endif %}
-                    {% endfor %}
-                </div>
+                <div class="mb-2">
+                    <span class="font-medium">{% trans "Authors" %}:</span>
+                    <ul class="inline list-disc list-inside" role="list">
+                        {% for author in author_links %}
+                            <li class="inline">
+                                {% if author.author.live %}
+                                    <a href="{% pageurl author.author %}">{{ author.author }}</a>
+                                {% else %}
+                                    {{ author.author }}
+                                {% endif %}
+                                {% if not forloop.last %}, {% endif %}
+                            </li>
+                        {% endfor %}
+                    </ul>
+                </div>

30-43: Heads-up: article.get_parent in a loop can cause N+1 queries.

Rendering the parent issue per article will likely query once per row. If this page lists many articles, consider adding an explicit foreign key (e.g., MagazineArticle.issue) so you can prefetch it, or cache parent pages upstream.

If adding a relation isn’t feasible now, monitor query counts on this template.

tags/views.py (4)

28-36: Avoid redundant filters/prefetch; rely on model get_queryset where possible.

  • LibraryItem.get_queryset() already applies live=True and prefetches authors__author; chaining .live() and .prefetch_related("authors__author") repeats work.
-        library_items = (
-            LibraryItem.get_queryset()
-            .filter(filter_condition)
-            .live()
-            .public()
-            .select_related("content_type")
-            .prefetch_related("authors__author")
-        )
+        library_items = (
+            LibraryItem.get_queryset()
+            .filter(filter_condition)
+            .public()
+            .select_related("content_type")
+        )

38-46: Same redundancy for MagazineArticle queryset.

MagazineArticle.get_queryset() already prefetches authors__author. Keep .public(), but drop duplicate prefetch to reduce planner/ORM overhead.

-        magazine_articles = (
-            MagazineArticle.get_queryset()
-            .filter(filter_condition)
-            .live()
-            .public()
-            .select_related("content_type")
-            .prefetch_related("authors__author")
-        )
+        magazine_articles = (
+            MagazineArticle.get_queryset()
+            .filter(filter_condition)
+            .live()
+            .public()
+            .select_related("content_type")
+        )

28-36: Reconsider dropping DB-level ordering; Python sort can scale poorly.

Global Python sorting forces full materialization of all four lists before pagination. For large tags, this is memory- and CPU-heavy. Options:

  • Use Page.objects (base Wagtail Page) to query all tagged pages across types and order in DB with Lower("title"), then .specific() for polymorphism.
  • Or pre-order each queryset in DB and perform a k-way merge iterator before pagination.

Also applies to: 38-46, 48-54, 56-63


74-77: Sorting key: prefer casefold() over lower() for robust case-insensitive ordering.

casefold handles more Unicode cases.

-            key=lambda instance: instance.title.lower(),
+            key=lambda instance: instance.title.casefold(),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 792aa26 and 29d3ea2.

📒 Files selected for processing (4)
  • magazine/templates/magazine/magazine_article_summary.html (1 hunks)
  • magazine/templates/search/magazine_article.html (1 hunks)
  • magazine/templates/search/magazine_issue.html (1 hunks)
  • tags/views.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • magazine/templates/search/magazine_article.html
  • magazine/templates/search/magazine_issue.html
🧰 Additional context used
📓 Path-based instructions (2)
**/*.html

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.html: Follow WCAG 2.1 and ARIA 1.1; use semantic HTML, appropriate ARIA roles/attributes, and ensure all interactive elements are keyboard accessible
Use semantic HTML tags (header, nav, main, footer, section, article, aside, figure/figcaption, time, address) and proper heading structure
Wrap content rendered via Wagtail’s richtext filter in a Tailwind prose class
Use Bootstrap Icons via bi bi-<icon-name> classes and keep icon usage consistent across the UI

Files:

  • magazine/templates/magazine/magazine_article_summary.html
**/*.{html,css}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer Tailwind CSS and daisyUI for new styles; avoid introducing new Bootstrap styles

Files:

  • magazine/templates/magazine/magazine_article_summary.html
🧬 Code graph analysis (1)
tags/views.py (3)
library/models.py (1)
  • LibraryItem (32-167)
magazine/models.py (3)
  • MagazineArticle (329-470)
  • MagazineArticle (230-364)
  • MagazineArticleTag (172-175)
wf_pages/models.py (1)
  • WfPage (60-110)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: runner-job
🔇 Additional comments (1)
tags/views.py (1)

56-63: LGTM: WfPage queryset is aligned with the others.

Consistent live/public filtering and content_type preload.

@codecov
Copy link

codecov bot commented Aug 31, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.69%. Comparing base (8b06c2e) to head (f588b10).
⚠️ Report is 22 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1170      +/-   ##
==========================================
+ Coverage   93.32%   93.69%   +0.36%     
==========================================
  Files         130      131       +1     
  Lines        4332     4348      +16     
==========================================
+ Hits         4043     4074      +31     
+ Misses        289      274      -15     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
magazine/models.py (1)

302-325: Invalid prefetch target and duplicate queryset assignment will crash and waste work.

  • prefetch_related("issue") is invalid on MagazineArticle (no FK named "issue") and will raise a FieldError when evaluated.
  • The queryset for articles is built twice with identical logic.

Remove the invalid prefetch and the duplicate block.

-        context["articles"] = (
-            MagazineArticle.objects.filter(
-                department__title=self.title,
-            )
-            .live()
-            .prefetch_related(
-                "authors__author",
-                "issue",
-            )
-        )
-
-        articles = (
+        articles = (
             MagazineArticle.objects.filter(
                 department__title=self.title,
             )
             .live()
             .prefetch_related(
                 "authors__author",
-                "issue",
             )
         )
 
-        context["articles"] = articles
+        context["articles"] = articles
🧹 Nitpick comments (5)
magazine/models.py (1)

368-371: Prefer select_related for FK and keep prefetch for M2M/Orderables.

department is a FK; select_related("department") will be more efficient than prefetch_related("department"). Keep prefetch for authors/tags.

-        """Prefetch authors, tags, and department for performance."""
-        related_fields = ["authors__author", "tags", "department"]
-        return cls.objects.prefetch_related(*related_fields)
+        """Optimize related loads: select FK, prefetch M2M/Orderables."""
+        related_prefetch = ["authors__author", "tags"]
+        return (
+            cls.objects
+            .select_related("department")
+            .prefetch_related(*related_prefetch)
+        )
library/models.py (1)

82-85: Good: switch to "tags" and filter via live(); tighten relation loading further.

Approved as-is. For fewer queries, use select_related for single FKs and keep prefetch for M2M/Orderables.

-        related_fields = [
-            "authors__author",
-            "topics__topic",
-            "item_audience",
-            "item_genre",
-            "item_medium",
-            "item_time_period",
-            "tags",
-        ]
-        return cls.objects.live().prefetch_related(*related_fields)
+        related_prefetch = [
+            "authors__author",
+            "topics__topic",
+            "tags",
+        ]
+        return (
+            cls.objects.live()
+            .select_related(
+                "item_audience",
+                "item_genre",
+                "item_medium",
+                "item_time_period",
+            )
+            .prefetch_related(*related_prefetch)
+        )
magazine/templates/magazine/magazine_article_summary.html (3)

11-29: Tighten list semantics: avoid bullets + commas, and drop redundant role.

Using both list-disc and manual commas yields mixed semantics and extra verbosity for AT. Keep comma-separated inline items and remove bullet styling and role="list".

-                    <ul class="inline list-disc list-inside" role="list">
+                    <ul class="inline">

38-46: Localize “Issue” label.

Match the i18n pattern used for “Authors”.

-                    Issue: <a href="{% pageurl issue %}" class="link">
+                    <span class="font-medium">{% translate "Issue" %}:</span>
+                    <a href="{% pageurl issue %}" class="link">

35-48: Potential N+1: article.get_parent in lists.

In search/tagged listings this may issue 1 query per article. If query counts are high, consider passing issue from the view (e.g., annotate or batch-resolve parents) and reusing it here.

I can help profile the view and sketch a parent-batch helper if needed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 29d3ea2 and f980f4e.

📒 Files selected for processing (4)
  • library/models.py (1 hunks)
  • magazine/models.py (1 hunks)
  • magazine/templates/magazine/magazine_article_summary.html (2 hunks)
  • wf_pages/models.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.html

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.html: Follow WCAG 2.1 and ARIA 1.1; use semantic HTML, appropriate ARIA roles/attributes, and ensure all interactive elements are keyboard accessible
Use semantic HTML tags (header, nav, main, footer, section, article, aside, figure/figcaption, time, address) and proper heading structure
Wrap content rendered via Wagtail’s richtext filter in a Tailwind prose class
Use Bootstrap Icons via bi bi-<icon-name> classes and keep icon usage consistent across the UI

Files:

  • magazine/templates/magazine/magazine_article_summary.html
**/*.{html,css}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer Tailwind CSS and daisyUI for new styles; avoid introducing new Bootstrap styles

Files:

  • magazine/templates/magazine/magazine_article_summary.html
🧬 Code graph analysis (1)
magazine/models.py (1)
content_migration/management/import_magazine_articles_handler.py (1)
  • handle_import_magazine_articles (79-139)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: runner-job
🔇 Additional comments (1)
wf_pages/models.py (1)

90-90: No lingering tags__tag prefetches found; approve switch to prefetch_related('tags'). Confirm callers don’t rely on draft items; consider adding .live() if this queryset powers public views.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
magazine/models.py (1)

356-358: Keep parent queryset semantics; use select_related for FK (department).

Switching from super().get_queryset() to cls.objects may drop parent-level queryset tweaks (ordering/defer settings). Also, department is a single-valued FK, so select_related is more efficient than prefetch_related. Keep prefetching for authors__author and tags. Optionally add .defer_streamfields() if this queryset backs listings where body isn't needed.

 @classmethod
 def get_queryset(cls):
-        """Prefetch authors, tags, and department for performance."""
-        related_fields = ["authors__author", "tags", "department"]
-        return cls.objects.prefetch_related(*related_fields)
+        """Optimize related fetches for listings."""
+        return (
+            super()
+            .get_queryset()
+            .defer_streamfields()  # optional: drop if detail pages rely on eager body
+            .select_related("department")
+            .prefetch_related("authors__author", "tags")
+        )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f980f4e and eb76955.

📒 Files selected for processing (1)
  • magazine/models.py (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: runner-job

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
magazine/templates/magazine/magazine_article_summary.html (2)

11-27: Minor a11y polish for the author list.

Associate the “Authors” label with the list and normalize list styling.

Apply:

-                    <span class="font-medium">{% translate "Authors" %}:</span>
-                    <ul class="inline">
+                    <span id="authors-label" class="font-medium">{% translate "Authors" %}:</span>
+                    <ul class="inline list-none p-0 m-0" aria-labelledby="authors-label">

Also consider inline-flex flex-wrap gap-1 on the <ul> if spacing needs improvement.


35-49: Keep link text succinct (date outside the anchor).

Move the publication date outside the link to improve screen-reader verbosity and hit target clarity.

-                    <a href="{% pageurl issue %}" class="link">
-                        {{ issue }}
-                        {% if issue.specific.publication_date %}
-                            (<time datetime="{{ issue.specific.publication_date|date:'Y-m-d' }}">
-                                {{ issue.specific.publication_date|date:"F Y" }}
-                            </time>)
-                        {% endif %}
-                    </a>
+                    <a href="{% pageurl issue %}" class="link">{{ issue }}</a>
+                    {% if issue.specific.publication_date %}
+                        (<time datetime="{{ issue.specific.publication_date|date:'Y-m-d' }}">
+                            {{ issue.specific.publication_date|date:"F Y" }}
+                        </time>)
+                    {% endif %}

Note: Using article.get_parent() in a list can trigger N+1 queries. If lists are large, consider measuring with Django Debug Toolbar.

library/models.py (1)

75-89: Optional: Defer the StreamField payload in get_queryset
Since the project is pinned to Wagtail 7.0.2 and you’re already using .defer_streamfields() in other models (e.g. magazine/models.py), you can optionally add it here to slim down the listing payload:

     return (
-        cls.objects.live()
+        cls.objects.live().defer_streamfields()
         .select_related(
             "item_audience",
             "item_genre",
             "item_medium",
             "item_time_period",
         )
         .prefetch_related(*related_prefetch)
     )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between eb76955 and 3c7a72a.

📒 Files selected for processing (3)
  • library/models.py (1 hunks)
  • magazine/models.py (1 hunks)
  • magazine/templates/magazine/magazine_article_summary.html (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.html

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.html: Follow WCAG 2.1 and ARIA 1.1; use semantic HTML, appropriate ARIA roles/attributes, and ensure all interactive elements are keyboard accessible
Use semantic HTML tags (header, nav, main, footer, section, article, aside, figure/figcaption, time, address) and proper heading structure
Wrap content rendered via Wagtail’s richtext filter in a Tailwind prose class
Use Bootstrap Icons via bi bi-<icon-name> classes and keep icon usage consistent across the UI

Files:

  • magazine/templates/magazine/magazine_article_summary.html
**/*.{html,css}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer Tailwind CSS and daisyUI for new styles; avoid introducing new Bootstrap styles

Files:

  • magazine/templates/magazine/magazine_article_summary.html
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: runner-job
🔇 Additional comments (2)
magazine/templates/magazine/magazine_article_summary.html (1)

1-1: Good i18n inclusion.

Loading i18n up front is correct; new strings (“Authors”, “Issue”) are wrapped with translate.

magazine/models.py (1)

356-365: Approve: optimized queryset is correct
defer_streamfields(), select_related("department"), and prefetch_related("authors__author", "tags") are fully supported in our pinned Wagtail 7.0.2 (method present since at least v2.13.4 and documented in v5.2.1) (docs.wagtail.org)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
magazine/templates/magazine/magazine_article_summary.html (1)

35-47: Minor: reduce repeated .specific access and tighten variable scope.

Bind the specific parent once; it’s a tiny readability win and avoids repeated attribute resolution.

Apply this diff:

-{% with issue=article.get_parent %}
+{% with issue=article.get_parent.specific %}
 ...
-    <a href="{% pageurl issue %}" class="link">{{ issue }}</a>
-    {% if issue.specific.publication_date %}
-        (<time datetime="{{ issue.specific.publication_date|date:'Y-m-d' }}">
-            {{ issue.specific.publication_date|date:"F Y" }}
+    <a href="{% pageurl issue %}" class="link">{{ issue }}</a>
+    {% if issue.publication_date %}
+        (<time datetime="{{ issue.publication_date|date:'Y-m-d' }}">
+            {{ issue.publication_date|date:"F Y" }}
         </time>)
     {% endif %}
tags/tests.py (3)

173-178: Store authors on self for clearer assertions.

Promote local vars to instance attrs to reference them unambiguously in assertions.

Apply this diff:

-        person_live = PersonFactory()
-        # Publish person_live so template renders it as a link
-        person_live.save_revision().publish()
-
-        person_not_live = PersonFactory()
+        self.person_live = PersonFactory()
+        # Publish person_live so template renders it as a link
+        self.person_live.save_revision().publish()
+
+        self.person_not_live = PersonFactory()

Additionally, add at file top:

import re

179-183: Associate the correct Person objects to the article.

Use the instance attributes to match the refactor above.

Apply this diff:

-        MagazineArticleAuthor.objects.create(article=self.article, author=person_live)
+        MagazineArticleAuthor.objects.create(article=self.article, author=self.person_live)
         MagazineArticleAuthor.objects.create(
             article=self.article,
-            author=person_not_live,
+            author=self.person_not_live,
         )

196-198: Assert the exact machine-readable date.

Check the precise datetime attribute value matches the issue’s publication_date.

Apply this diff:

-        # Issue label and machine-readable date present
-        self.assertIn("Issue", html)
-        self.assertIn('<time datetime="', html)
+        # Issue label and machine-readable date present
+        self.assertIn("Issue", html)
+        expected_date = (
+            self.article.get_parent().specific.publication_date.strftime("%Y-%m-%d")
+        )
+        self.assertIn(f'<time datetime="{expected_date}"', html)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3c7a72a and 6d7b4a6.

📒 Files selected for processing (3)
  • library/models.py (1 hunks)
  • magazine/templates/magazine/magazine_article_summary.html (2 hunks)
  • tags/tests.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • library/models.py
🧰 Additional context used
📓 Path-based instructions (3)
{**/tests.py,**/tests/*.py,**/tests/**/*.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Write tests using Django’s testing framework (e.g., django.test.TestCase) rather than unittest or pytest

Files:

  • tags/tests.py
**/*.html

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.html: Follow WCAG 2.1 and ARIA 1.1; use semantic HTML, appropriate ARIA roles/attributes, and ensure all interactive elements are keyboard accessible
Use semantic HTML tags (header, nav, main, footer, section, article, aside, figure/figcaption, time, address) and proper heading structure
Wrap content rendered via Wagtail’s richtext filter in a Tailwind prose class
Use Bootstrap Icons via bi bi-<icon-name> classes and keep icon usage consistent across the UI

Files:

  • magazine/templates/magazine/magazine_article_summary.html
**/*.{html,css}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer Tailwind CSS and daisyUI for new styles; avoid introducing new Bootstrap styles

Files:

  • magazine/templates/magazine/magazine_article_summary.html
🧬 Code graph analysis (1)
tags/tests.py (2)
magazine/models.py (5)
  • MagazineArticle (317-465)
  • MagazineArticleAuthor (468-489)
  • MagazineArticle (230-364)
  • MagazineArticleAuthor (367-383)
  • MagazineArticleTag (172-175)
contact/factories.py (2)
  • PersonFactory (44-74)
  • slug (56-58)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: runner-job
🔇 Additional comments (4)
magazine/templates/magazine/magazine_article_summary.html (2)

1-1: LGTM: i18n loaded correctly.

Importing i18n alongside wagtailcore_tags is appropriate here.


11-29: Author rendering approach looks good and aligns with prefetching.

Conditional section, inline list semantics, and linking only live authors are correct.

tags/tests.py (2)

7-7: LGTM: import used.

MagazineArticleAuthor is used to attach authors in tests.


12-12: LGTM: import used.

PersonFactory is used to create author pages.

Comment on lines 14 to 16
<span id="authors-label" class="font-medium">{% translate "Authors" %}:</span>
<ul class="inline list-none p-0 m-0" aria-labelledby="authors-label">
{% for author in author_links %}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix duplicate IDs for accessibility and validity.

Using id="authors-label" on multiple cards will create duplicate IDs on listing/search pages. Make the ID unique per article and update aria-labelledby accordingly.

Apply this diff:

-                    <span id="authors-label" class="font-medium">{% translate "Authors" %}:</span>
-                    <ul class="inline list-none p-0 m-0" aria-labelledby="authors-label">
+                    <span id="authors-label-{{ article.id }}" class="font-medium">{% translate "Authors" %}:</span>
+                    <ul class="inline list-none p-0 m-0" aria-labelledby="authors-label-{{ article.id }}">

I can update the related tests in tags/tests.py to assert the new dynamic ID.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<span id="authors-label" class="font-medium">{% translate "Authors" %}:</span>
<ul class="inline list-none p-0 m-0" aria-labelledby="authors-label">
{% for author in author_links %}
<span id="authors-label-{{ article.id }}" class="font-medium">{% translate "Authors" %}:</span>
<ul class="inline list-none p-0 m-0" aria-labelledby="authors-label-{{ article.id }}">
{% for author in author_links %}
🤖 Prompt for AI Agents
magazine/templates/magazine/magazine_article_summary.html around lines 14-16:
the id "authors-label" is static and will be duplicated across multiple article
cards; change it to a unique id per article (e.g., append the article.pk or
forloop.counter: "authors-label-{{ article.pk }}" or similar) and update the
ul's aria-labelledby to reference that same dynamic id so they match; also
update tags/tests.py to assert the new dynamic id is rendered (use the same
identifier logic in the test).

…with dynamic ID extraction and non-live author checks
@brylie brylie merged commit 69029dc into main Aug 31, 2025
8 checks passed
@brylie brylie deleted the tags-n1-query branch August 31, 2025 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants