Feeds¶
Django Cast provides comprehensive feed support for both blogs and podcasts, with RSS and Atom formats, iTunes metadata, and performance optimizations.
Feed Detail Page¶
Each blog and podcast has a dedicated feed detail page at <slug>/feed/
(URL name cast:feed_detail) that lists all available feeds in one place:
Blog RSS and Atom feed links (for all blogs)
Platform links — Apple Podcasts, Spotify, YouTube (for podcasts, when the corresponding key is set in
CAST_FOLLOW_LINKS)Podcast feeds table with all four audio formats (MP3, M4A, OGA, OPUS) in both RSS and Atom (for podcasts only)
The navbar RSS icon links to this page instead of the raw XML feed. Custom
themes without a feed_detail.html template automatically fall back to the
plain theme.
Feed XML endpoints use shared response caches, and the feed detail page is also public. These endpoints are available only for live blogs and podcasts without Wagtail login, password, or group restrictions on the page or any ancestor. Restricted pages return 404; django-cast does not expose authenticated private feeds.
The template receives the following context variables:
blog— the Blog or Podcast instanceis_podcast— boolean,Truefor podcastsblog_feed_url— URL to the blog RSS XML feedblog_atom_feed_url— URL to the blog Atom XML feedtemplate_base_dir— the active theme namepodcast_feeds— list of dicts withformat,format_label,rss_url,atom_url(podcasts only)apple_podcasts_url— Apple Podcasts URL from settings, orNone(podcasts only)spotify_url— Spotify URL from settings, orNone(podcasts only)youtube_url— YouTube URL from settings, orNone(podcasts only)
Feed Types¶
Blog Feeds¶
Blog feeds are available in RSS and Atom formats, automatically generated from your blog content:
RSS 2.0 feed at
<slug>/feed/rss.xmlAtom 1.0 feed at
<slug>/feed/atom.xmlFeed fields populated from Blog model: title, description, author
Automatic inclusion of post content (overview and detail sections)
Blog RSS item GUIDs are based on the post UUID with isPermaLink="false", so
they are stable across slug and URL changes. Feed readers that subscribed before
UUID-based GUIDs were introduced see each existing post once as new; from then on
the GUIDs remain constant.
Podcast Feeds¶
Podcast feeds extend blog feeds with additional podcast-specific features:
iTunes podcast metadata (artwork, categories, explicit content marking)
Optional episode publishing metadata for iTunes and Podcasting 2.0: episode number, episode type, and season
Audio file enclosures for episode distribution
Multiple audio format support with separate feeds per format
RSS at
<slug>/feed/podcast/<audio_format>/rss.xmlAtom at
<slug>/feed/podcast/<audio_format>/atom.xmlChapter marks support for enhanced navigation
Transcript URLs included in feeds (WebVTT and DOTE formats)
Feed Fields¶
Standard Fields¶
These fields are populated from the Blog/Podcast model:
Title: From the blog’s title field
Description: From the blog’s description field
Author: Populates both iTunes and Atom feed author tags
Link: Canonical URL to the blog/podcast homepage
Language: Configurable per blog instance
Podcast-Specific Fields¶
Additional metadata for podcast feeds:
iTunes Artwork: High-resolution podcast cover image
iTunes Subtitle: From the blog’s subtitle field
iTunes Categories: Podcast directory categorization
Explicit Content: Content rating flag
Podcast Type: Optional
episodicorserialchannel ordering value emitted asitunes:typeonly when explicitly configured.Episode Enclosures: Audio files with proper MIME types
Episode Duration: Calculated from audio files
Episode Number: Optional positive integer emitted as
itunes:episodeandpodcast:episodeEpisode Type: Optional
full,trailer, orbonusvalue emitted asitunes:episodeTypeonly when explicitly set; a blank value omits the tag and is equivalent tofullSeason: Optional reusable season object scoped to the podcast. Positive season numbers are emitted as
itunes:seasonandpodcast:season; a season name is emitted as the Podcasting 2.0nameattribute.Chapter Marks: Time-indexed navigation points
Transcripts: Links to VTT and DOTE transcript files
Podlove Simple Chapters¶
Podcast RSS and Atom feeds include inline Podlove Simple Chapters for episodes
that have chapter marks. The psc namespace is declared on each emitted
psc:chapters element, not on the feed root, so feeds and episodes without
chapter marks remain unchanged.
The emitted shape is:
<psc:chapters version="1.2" xmlns:psc="http://podlove.org/simple-chapters">
<psc:chapter start="00:01:23" title="Intro"/>
<psc:chapter start="00:04:56.789" title="Topic"/>
</psc:chapters>
(The feed serializer emits self-closing empty elements and sorts element attributes alphabetically.)
Chapter start values use HH:MM:SS or HH:MM:SS.mmm when fractional
seconds are present. Podlove Simple Chapters v1 output currently includes the
start and title attributes only.
Chaptered episodes also include a Podcasting 2.0 external chapters reference:
<podcast:chapters type="application/json+chapters" url="https://example.com/chapters/<audio pk>/?episode_id=<episode pk>"/>
The stable endpoint path is chapters/<audio pk>/?episode_id=<episode pk>.
application/json+chapters is the Podcasting 2.0 specification’s literal media-type
string, not application/chapters+json.
It returns application/json+chapters with this body shape:
{
"version": "1.2.0",
"chapters": [
{"startTime": 83, "title": "Intro"}
]
}
startTime values are integer seconds. Access to the endpoint uses the same
audio-access checks as public audio and transcript endpoints: the supplied
episode_id must reference the audio and be viewable by the requester.
Denied requests raise Http404 so object existence is not leaked. Authorized
requests for audio without chapter marks return a valid empty chapters document.
RSS item GUIDs remain based on the episode UUID with
isPermaLink="false". Episode numbers, episode types, and seasons are
publishing metadata only; changing them does not change feed identity.
Backfilling Podcast Metadata¶
Existing podcasts do not need a data migration. The new episode number, episode type, and season fields are optional and feeds omit the corresponding tags until values are set.
When backfilling from imported source metadata, copy only values that are valid
for the django-cast fields: positive integer episode numbers, positive integer
season numbers, and one of full, trailer, or bonus for episode
type. Leave legacy values such as 0, blank numbers, decimal episode numbers,
or host-specific display labels unset until a project-specific mapping is
chosen. Keep imported GUIDs or django-cast UUIDs as feed identity; do not derive
identity from episode or season numbers.
Feed Generation¶
Repository Pattern¶
Feeds use the FeedContext pattern for optimized generation:
# Efficient feed generation with minimal queries
repository = FeedContext(blog)
# All posts and related data prefetched
Performance Features¶
Feed Caching: Generated XML cached to reduce server load
Prefetch Optimization: Single query retrieves all feed data
Lazy Loading: Large content fields loaded on-demand
Conditional GET: Support for If-Modified-Since headers
Host and Site Configuration¶
Feed self URLs and relative channel links use the request host validated by
Django’s ALLOWED_HOSTS, which supports sites served from more than one
configured hostname. Cached feed responses are scoped to that host. Item URLs
continue to follow Wagtail site routing. Feed rendering does not replace
Django’s process-wide current Site with request data. When
django.contrib.sites is installed, SITE_ID must identify an existing
django_site row (or, without SITE_ID, the request host must match one);
otherwise feed generation raises an explicit configuration error. Without the
Sites app, Django’s request-local RequestSite fallback is preserved.
API Access¶
Feeds are also available via the REST API:
/api/posts/- JSON feed of blog posts/api/episodes/- JSON feed of podcast episodesSupports filtering, pagination, and field selection
Machine-readable alternative to XML feeds
Configuration¶
Post date index¶
Migration 0083_post_feed_boundary_index adds a composite post date/ID index.
Run the normal migrate command after upgrading. Creating the index can lock
the post table; schedule the migration appropriately for large installations.
Feed Limits¶
Feeds include all eligible live, publicly accessible entries by default.
Podcast feeds additionally require podcast audio. CAST_FEED_ITEM_LIMIT is
not implemented and setting it has no effect.
Set CAST_BLOG_FEED_ITEM_LIMIT to keep only the newest entries in the blog
feeds (latest_entries_feed and latest_entries_atom_feed):
CAST_BLOG_FEED_ITEM_LIMIT = 50
Feed readers keep entries they have already fetched, so a limited blog feed does not remove older posts for existing subscribers. Podcast feeds always include every episode: most podcast apps show only the episodes that are currently in the feed, so a limit would hide the back catalog.
Follow Links¶
Configure platform links shown on the feed detail page for podcasts:
CAST_FOLLOW_LINKS = {
"apple_podcasts": "https://podcasts.apple.com/...",
"spotify": "https://open.spotify.com/show/...",
"youtube": "https://www.youtube.com/@...",
}
Cache Duration¶
The built-in XML feed routes cache responses for five minutes.
CAST_FEED_CACHE_TIMEOUT is not implemented and setting it has no effect.
The feed root’s public accessibility is rechecked before serving cached output.
When a Wagtail view restriction (login, password, or group) is added to or
removed from any page and the change commits, every cached feed response is
retired: the feed cache key carries a generation that the change rotates. A
feed render already in flight during the change stores its response under the
old generation, so no later request is served the stale entry. The generation
lives in the cache named by CACHE_MIDDLEWARE_ALIAS; with a per-process
cache such as LocMemCache only the process that committed the change sees
the rotation, so use a shared cache backend in multi-process deployments.
Other entry changes (edits, unpublishing, deletion) can remain in a warm response until it expires. Downstream HTTP caches and proxies are not purged; a cached feed is not an immediate-revocation mechanism outside this application.
Conditional Requests¶
Feed responses carry a weak ETag computed from the XML body. A client that
sends a matching If-None-Match header gets 304 Not Modified without a
body, including when the response comes from the five-minute cache. This saves
most of the bandwidth for podcast clients that poll often, such as Apple
Podcasts and Spotify.
If-Modified-Since alone never produces a 304. The Last-Modified
header reflects only the newest entry’s date, so it does not change when an
older entry is edited or removed.
Best Practices¶
Use Descriptive Titles: Feed titles should clearly identify your content
Set Appropriate Descriptions: Descriptions appear in feed readers
Configure Author Information: Improves attribution and discoverability
Optimize Images: Use appropriate resolutions for podcast artwork
Enable Caching: Reduces server load for popular feeds
Monitor Feed Validation: Ensure feeds validate against standards
Feed Validation¶
Validate your feeds with these tools:
W3C Feed Validator for RSS/Atom
Cast Feed Validator for podcasts
Troubleshooting¶
Common Issues¶
Missing Enclosures: Ensure episodes have
podcast_audiosetInvalid Characters: Check for special characters in titles/descriptions
Large Feed Size: Feeds include the complete eligible archive unless
CAST_BLOG_FEED_ITEM_LIMITlimits the blog feeds; podcast feeds have no item limit. Serve feeds compressed (for example gzip or brotli at the reverse proxy), rely on theETag/304support, and considerCAST_FEED_ITUNES_SUMMARY = Falseto drop the duplicated episode descriptions. Measure response size and generation cost before choosing deployment-specific caching or customization.Cache Issues: Clear cache after major content updates