Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 53 additions & 19 deletions tbx/blog/feeds.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import datetime, time
from typing import TYPE_CHECKING, Any, Optional

from django.contrib.syndication.views import Feed

Expand All @@ -7,41 +8,74 @@
from .models import BlogPage


if TYPE_CHECKING:
from django.http import HttpRequest, HttpResponse

from wagtail.query import PageQuerySet

# Main blog feed


class BlogFeed(Feed):
title = "The Torchbox Blog"
link = "/blog/"
link = "/news/"
description = "The latest news and views from Torchbox on the work we do, the web and the wider world"

def items(self):
return BlogPage.objects.live().public().order_by("-date")[:10]

def item_title(self, item):
request: Optional["HttpRequest"] = None

def __call__(
self, request: "HttpRequest", *args: Any, **kwargs: Any
) -> "HttpResponse":
self.request = request

return super().__call__(request, *args, **kwargs)

def items(self) -> "PageQuerySet[BlogPage]":
return (
BlogPage.objects.live()
.public()
.defer_streamfields()
.select_related("feed_image")
.prefetch_related("authors__author")
.order_by("-date")[:10]
)

def item_title(self, item: BlogPage) -> str:
return item.title

def item_description(self, item):
return item.listing_summary
def item_description(self, item: BlogPage) -> str:
return item.listing_summary or item.search_description

def item_link(self, item):
return item.get_full_url()
def item_link(self, item: BlogPage) -> str:
return item.get_full_url(request=self.request)
Copy link
Copy Markdown
Member

@helenb helenb Mar 26, 2026

Choose a reason for hiding this comment

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

Can you explain this change a bit more? I get the exact same result for the urls before and after the change.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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


def item_author_name(self, item):
pass
def item_author_name(self, item: BlogPage) -> str | None:
return (
", ".join([author_item.author.name for author_item in item.authors.all()])
or None
)

def item_pubdate(self, item):
def item_pubdate(self, item: BlogPage) -> datetime:
return datetime.combine(item.date, time())

def item_enclosure_url(self, item):
def item_enclosure_url(self, item: BlogPage) -> str | None:
if item.feed_image:
return item.feed_image.file.url

def item_enclosure_mime_type(self, item):
def item_enclosure_mime_type(self, item: BlogPage) -> str | None:
if item.feed_image:
image_format = filetype.guess_extension(item.feed_image.file)
return f"image/{image_format}"
try:
if image_format := filetype.guess_extension(item.feed_image.file):
return f"image/{image_format}"
except (AttributeError, OSError, TypeError):
pass

return None

def item_enclosure_length(self, item):
def item_enclosure_length(self, item: BlogPage) -> str | None:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Another code review issue that claude raised - will leave you to judge if it is valid (I don't know if it's correct about what file.size returns but seems plausible that it is an int):

Issue: The return type is str | None, but file.size returns an int. This creates a type mismatch.

Suggested change
def item_enclosure_length(self, item: BlogPage) -> str | None:
def item_enclosure_length(self, item: BlogPage) -> str | None:
if item.feed_image:
return str(item.feed_image.file.size)
return None

Impact:

  • RSS spec requires enclosure length to be a string
  • Current code might work due to Python's implicit conversion, but explicit is better

if item.feed_image:
return item.feed_image.file.size
try:
return str(item.feed_image.file.size)
except (AttributeError, OSError, TypeError):
pass

return None
3 changes: 1 addition & 2 deletions tbx/blog/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,7 @@ def blog_index(self):
@cached_property
def first_author(self):
"""Safely return the first author if one exists."""
author = self.authors.first()
if author:
if author := self.authors.first():
return author.author
return None

Expand Down
Loading