Creating RSS Feeds with Django

See Django: Tips and Tricks for similar articles.

Django’s syndication framework makes it pretty simple to create RSS feeds. It is a little trickier if you want to create separate feeds based on a category or a tag. For example, we have a feed at /articles/django/feed/ that returns articles about Django and a feed at /articles/python/feed/ that returns articles about Python.

In addition, we have a general feed for all articles at /articles/feed/. In this article, I’ll explain how to do this.

But first, a quick thanks to Filip Němeček, who happened to be tackling the same issue just a couple of days before I did and wrote up his solution here. The official documentation shows a similar example, but I missed the importance of get_object() until I read it in Filip’s post.


models.py

Here are the relevant models (pared down to just show the fields that matter):

class Article(models.Model):
    created = models.DateTimeField(default=timezone.now)
    date_published = models.DateTimeField(null=True, blank=True)
    description = models.TextField(blank=True)
    name = models.CharField(max_length=150)
    slug = models.SlugField(max_length=50, unique=True, null=False, blank=True)
    updated = models.DateTimeField(auto_now=True)

    def get_absolute_url(self):
        return reverse("article", args=[self.slug])


class Tag(models.Model):
    name = models.CharField(max_length=100)
    slug = models.SlugField(max_length=50, unique=True, null=False, blank=True)


class ArticleTag(models.Model):
    article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name="tags")
    tag = models.ForeignKey(Tag, on_delete=models.CASCADE, related_name="articletags")

Notice that articles and tags have a many-to-many relationship through the ArticleTag model.


urls.py

Here are the relevant URL patterns. Notice there are two patterns for feeds:

  1. A pattern matching a specific tag (e.g., /articles/django/feed/)
  2. A non-tag-specific pattern (e.g., /articles/feed/)
from django.urls import path

from .feeds import ArticlesFeed,…

urlpatterns = [
    path("articles/<tag_slug>/feed/", ArticlesFeed(), name="article-feed"),
    path("articles/feed/", ArticlesFeed(), name="article-feed-all"),
    …
    path("article/<slug>/", ArticleView.as_view(), name="article"),
    path("articles/", ArticlesView.as_view(), name="articles"),
    path("articles/<slug>/", ArticlesView.as_view(), name="articles-for-tag"),
    …
]

Notice there are two patterns for feeds:

  1. A pattern matching a specific tag (e.g., /articles/django/feed/)
  2. A non-tag-specific pattern (e.g., /articles/feed/)

feeds.py

The code below shows the methods required to create tag-specific feeds and one non-tag-specific feed:

from django.contrib.syndication.views import Feed
      
class ArticlesFeed(Feed):
    def get_object(self, request, tag_slug=None):
        """Return the tag matching tag_slug if it is passed in."""
        if tag_slug:
            return Tag.objects.get(slug=tag_slug)
        return None

    def items(self, tag=None):
        """Return the items to include in the feed."""
        items = Article.objects.filter(date_published__isnull=False).order_by("-date_published")

        if tag:
            return items.filter(tags__tag=tag)

        return items

    def description(self, tag=None):
        """Return a description for the passed-in tag or a generic description."""
        if tag:
            return f"Webucator {tag.name} Articles"

        return "Webucator Articles"

    def link(self, tag=None):
        """Return the link to articles for the passed-in tag or a link to all articles."""
        if tag:
            return reverse("articles-for-tag", kwargs={"slug": tag.slug})

        return reverse("articles")

    def title(self, tag=None):
        """Return a title for the passed-in tag or a generic title."""
        if tag:
            return f"Webucator {tag.name} Articles"

        return "Webucator Articles"

The key here is the get_object() method, which we use to get the tag based on the slug passed in the URL. This object is passed in as the second argument to the other methods. Because the object is a tag, we’ve called that parameter tag:

def items(self, tag=None):
    …

In each method, if tag is not None then we return a value specific to that tag. If it is None, we return something non-tag specific.

Additional Methods

You may also want to add the following methods to provide additional data on each article:

class ArticlesFeed(Feed):
    …

    def item_author_name(self, article):
        if article.author:
            return article.author.full_name
        return None

    def item_categories(self, article):
        return [tag.tag.name for tag in article.tags.all()]

    def item_description(self, article):
        return article.description

    def item_pubdate(self, article):
        return article.date_published

    def item_title(self, article):
        return article.name

    def item_updateddate(self, article):
        return article.updated

Note that our Article model includes a get_absolute_url() method. If it did not, we would need to include an item_link() method in the ArticlesFeed class:

class ArticlesFeed(Feed):
    …

    def item_link(self, article):
        return reverse("article", args=[article.slug])

Written by Nat Dunn. Follow Nat on Twitter.


Related Articles

  1. Creating RSS Feeds with Django (this article)
  2. How to Run the Shell for a Django Project within AWS Elastic Beanstalk AMI