Skip to main content

A django planet and feeds (RSS and ATOM) aggregator application for Django.

Project description

django-planet

Python Compatibility Django Compatibility PyPi Version CI badge codecov License

A reusable Django app for building RSS/Atom feed aggregator websites (aka "Planet" sites).

Django-planet makes it easy to create a planet-style feed aggregator. Collect posts from multiple blogs and websites, store them in your database, and display them with built-in views and templatesโ€”or build your own custom front-end.

Post List

๐Ÿ“‘ Table of Contents

โœจ Features

  • RSS and Atom feed parsing - Supports both RSS and Atom feed formats via feedparser
  • Automatic feed updates - Management commands to add feeds and update all feeds
  • Blog, Feed, Post, and Author models - Complete data model with relationships
  • Built-in views and templates - Ready-to-use views for blogs, feeds, posts, and authors
  • Django admin integration - Manage all content through Django's admin interface
  • Search functionality - Built-in search across posts, blogs, feeds, and authors
  • SEO-friendly URLs - Slugified URLs with automatic redirects
  • Custom managers - QuerySet methods for filtering by blog, feed, author
  • Template tags - Custom template tags for common operations
  • Pagination support - Uses django-pagination-py3 for easy pagination

๐Ÿ“ฆ Installation & Configuration

Via pip

pip install django-planet

From source

git clone https://github.com/matagus/django-planet.git
cd django-planet
pip install -e .

Configure your Django project

  1. Add planet and pagination to your INSTALLED_APPS in settings.py:
INSTALLED_APPS = [
    # Django apps
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",

    # Third-party apps
    "planet",
    "pagination",  # Required dependency
]
  1. Add pagination middleware to MIDDLEWARE in settings.py:
MIDDLEWARE = [
    # ... other middleware
    "pagination.middleware.PaginationMiddleware",
]
  1. (Optional) Configure planet settings in settings.py:
PLANET = {
    "USER_AGENT": "MyPlanet/1.0",  # Customize the User-Agent for feed requests
    "RECENT_POSTS_LIMIT": 10,  # Number of recent posts to show
    "RECENT_BLOGS_LIMIT": 10,  # Number of recent blogs to show
    "FETCH_ORIGINAL_CONTENT": False,  # Fetch and archive full post content from the original URL
    "FETCH_CONTENT_DELAY": 0,  # Seconds to wait between content fetches (int or float)
}

Original Content Archiving

When FETCH_ORIGINAL_CONTENT is True, django-planet will fetch the full HTML of each post's original URL using readability-lxml to extract the article body. The result is stored in Post.original_content and shown on the post detail page instead of the feed summary.

PLANET = {
    "FETCH_ORIGINAL_CONTENT": True,
    "FETCH_CONTENT_DELAY": 1,  # 1 second between fetches to be polite to servers
}
  • If fetching fails for a post, a WARNING is logged and original_content remains None (the feed summary is shown as fallback).
  • Use the planet_fetch_post_content management command to backfill existing posts.
  1. Run migrations:
python manage.py migrate
  1. Include planet URLs in your project's urls.py:
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("planet.urls")),  # or path("planet/", include("planet.urls"))
]

๐Ÿ“– Usage

Adding Feeds

You can add feeds using the management command:

python manage.py planet_add_feed https://example.com/feed.xml

This will:

  • Parse the feed and create a Blog entry if it doesn't exist
  • Create the Feed entry
  • Import all posts from the feed
  • Create Author entries and link them to posts

Updating Feeds

Update all active feeds to fetch new posts:

python manage.py planet_update_all_feeds

This command:

  • Iterates through all feeds
  • Fetches new entries
  • Creates new Post and Author entries as needed
  • Updates feed metadata (last_checked, etag)

Set up periodic updates:

For production, schedule this command to run periodically:

Using cron:

# Run every hour
0 * * * * /path/to/venv/bin/python /path/to/project/manage.py planet_update_all_feeds

Built-in Views

Django-planet provides these URL patterns:

  • / - Post list (index)
  • /posts/ - All posts
  • /posts/<id>/<slug>/ - Post detail
  • /blogs/ - All blogs
  • /blogs/<id>/<slug>/ - Blog detail (shows all posts from that blog)
  • /feeds/ - All feeds
  • /feeds/<id>/<slug>/ - Feed detail (shows all posts from that feed)
  • /authors/ - All authors
  • /authors/<id>/<slug>/ - Author detail (shows all posts by that author)
  • /search/ - Search form endpoint

Templates

Planet includes a complete set of templates:

planet/templates/planet/
โ”œโ”€โ”€ base.html                    # Base template
โ”œโ”€โ”€ posts/
โ”‚   โ”œโ”€โ”€ list.html               # Post list view
โ”‚   โ”œโ”€โ”€ detail.html             # Post detail view
โ”‚   โ””โ”€โ”€ blocks/
โ”‚       โ””โ”€โ”€ list.html           # Reusable post list block
โ”œโ”€โ”€ blogs/
โ”‚   โ”œโ”€โ”€ list.html               # Blog list view
โ”‚   โ”œโ”€โ”€ detail.html             # Blog detail view
โ”‚   โ””โ”€โ”€ blocks/
โ”‚       โ””โ”€โ”€ list.html           # Reusable blog list block
โ”œโ”€โ”€ feeds/
โ”‚   โ”œโ”€โ”€ list.html               # Feed list view
โ”‚   โ”œโ”€โ”€ detail.html             # Feed detail view
โ”‚   โ””โ”€โ”€ blocks/
โ”‚       โ””โ”€โ”€ list_for_author.html
โ””โ”€โ”€ authors/
    โ”œโ”€โ”€ list.html               # Author list view
    โ”œโ”€โ”€ detail.html             # Author detail view
    โ””โ”€โ”€ blocks/
        โ”œโ”€โ”€ list.html           # Reusable author list block
        โ””โ”€โ”€ list_for_feed.html

Using Template Tags

Django-planet includes custom template tags for common operations. Load them in your templates:

{% load planet_tags %}

Available Template Tags

Filters

clean_html - Cleans HTML content by removing inline styles, style tags, and script tags

{{ post.content|clean_html }}

This filter:

  • Removes inline style attributes
  • Removes <style> and <script> tags
  • Replaces multiple consecutive <br/> tags (3+) with just two
  • Returns safe HTML that won't be escaped
Simple Tags

get_first_paragraph - Extracts the first paragraph or sentence from post content

{% get_first_paragraph post.content as excerpt %}
{{ excerpt }}

This tag:

  • Strips all HTML tags
  • Normalizes whitespace
  • Returns the first sentence longer than 80 characters
  • Falls back to the first 80 characters if no long sentence is found
  • Useful for creating post excerpts or previews

get_authors_for_blog - Returns all authors who have written posts for a specific blog

{% get_authors_for_blog blog as authors %}
{% for author in authors %}
  <a href="{{ author.get_absolute_url }}">{{ author.name }}</a>
{% endfor %}

blogs_for_author - Returns all blogs that an author has contributed to

{% blogs_for_author author as blogs %}
{% for blog in blogs %}
  <a href="{{ blog.get_absolute_url }}">{{ blog.title }}</a>
{% endfor %}
Inclusion Tags

Inclusion tags render complete HTML blocks with their own templates.

authors_for_feed - Renders a list of all authors who have posts in a feed

{% authors_for_feed feed %}

Uses template: planet/authors/blocks/list_for_feed.html

feeds_for_author - Renders a list of all feeds an author has contributed to

{% feeds_for_author author %}

Uses template: planet/feeds/blocks/list_for_author.html

recent_posts - Renders a list of the most recent posts across all blogs

{% recent_posts %}

Uses template: planet/posts/blocks/list.html Limit controlled by PLANET["RECENT_POSTS_LIMIT"] setting (default: 10)

recent_blogs - Renders a list of the most recently added blogs

{% recent_blogs %}

Uses template: planet/blogs/blocks/list.html Limit controlled by PLANET["RECENT_BLOGS_LIMIT"] setting (default: 10)

Admin Interface

All models are registered in Django admin with sensible defaults:

  • BlogAdmin
  • FeedAdmin
  • PostAdmin
  • AuthorAdmin

All admin interfaces include search and filtering capabilities.

Management Commands

planet_add_feed <feed_url>

  • Adds a new feed to the database
  • Creates Blog if it doesn't exist
  • Imports all existing posts from the feed
  • Creates Author entries for post authors

planet_update_all_feeds

  • Updates all active feeds
  • Fetches new posts from each feed
  • Updates feed metadata (etag, last_checked)
  • Creates new Post and Author entries as needed

planet_fetch_post_content

  • Backfills original_content for posts where it is missing
  • Optional --feed <id> argument to limit to a specific feed
  • Optional --limit <n> argument to cap the number of posts processed
  • Respects FETCH_CONTENT_DELAY between requests

๐Ÿ” Post Filtering

By default, all feed entries are saved. You can configure a post filter backend to accept only relevant posts before they are stored.

Configuration

PLANET = {
    "POST_FILTER_BACKEND": "planet.backends.accept_all.AcceptAllBackend",  # default
    "TOPIC_KEYWORDS": [],
}

Built-in Backends

planet.backends.accept_all.AcceptAllBackend (default) Accepts every entry unchanged. No configuration required.

planet.backends.keyword.KeywordFilterBackend Accepts entries whose title or summary contains at least one of the configured keywords (case-insensitive). Rejected entries are logged at INFO level.

PLANET = {
    "POST_FILTER_BACKEND": "planet.backends.keyword.KeywordFilterBackend",
    "TOPIC_KEYWORDS": ["python", "django", "open source"],
}

When TOPIC_KEYWORDS is empty the backend accepts all entries (fail-open).

Writing a Custom Backend

Subclass BasePostFilterBackend and implement filter_entries:

from planet.backends.base import BasePostFilterBackend

class MyBackend(BasePostFilterBackend):
    def filter_entries(self, entries, feed):
        # entries: list of feedparser entry objects
        # feed: planet.models.Feed instance
        return [e for e in entries if passes_my_check(e)]

Then point to it in your settings:

PLANET = {
    "POST_FILTER_BACKEND": "myapp.backends.MyBackend",
}

๐Ÿ“‹ Logging

django-planet uses Python's standard logging module. All loggers use names under the planet.* namespace (e.g. planet.utils, planet.management.commands.planet_update_all_feeds).

Following Python library best practices, no handlers are attached by default โ€” the host project controls all logging output. Add a LOGGING configuration in your Django settings to see log output:

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
        },
    },
    "loggers": {
        "planet": {
            "handlers": ["console"],
            "level": "INFO",  # Use "DEBUG" for more verbosity
        },
    },
}

At INFO level you'll see feed add/update summaries and 304 skips. At DEBUG level you'll also see individual fetch details, per-entry creation, and to_datetime() edge cases.

๐Ÿ“ธ Screenshots

Post List

Post List

Blog View

Blog View

Full Post View

Full Post View

Live Demo

Check out the live demo: django-planet.matagus.dev

Demo source code is available in the project/ directory.

๐Ÿงช Testing

You can run tests either with Hatch (recommended for testing multiple Python/Django versions) or directly.

With Hatch (recommended)

Django-planet uses Hatch for testing across multiple Python and Django versions.

Run all tests

Test across all Python/Django version combinations:

hatch run test:test

Run tests for specific versions

# Python 3.14 + Django 6.0
hatch run test.py3.14-6.0:test

# Python 3.14 + Django 5.2
hatch run test.py3.14-5.2:test

# Python 3.11 + Django 5.1
hatch run test.py3.11-5.1:test

Run with coverage

hatch run test:cov

This will:

  1. Run tests with coverage tracking
  2. Generate a coverage report
  3. Output results to the terminal

View test matrix

See all available Python/Django test combinations:

hatch env show test

Without Hatch

If you prefer to run tests directly without Hatch:

  1. Install test dependencies:

    pip install coverage factory_boy
    
  2. Run tests using Django's test runner:

    python -m django test --settings tests.settings
    
  3. Run tests with coverage:

    coverage run -m django test --settings tests.settings
    coverage report
    
  4. Generate coverage JSON:

    coverage json
    

๐Ÿค Contributing

Contributions are welcome! โค๏ธ

Development Setup

  1. Fork the repository

  2. Clone your fork:

    git clone https://github.com/YOUR_USERNAME/django-planet.git
    cd django-planet
    
  3. Install development dependencies:

    pip install -e .
    pip install pre-commit
    
  4. Set up pre-commit hooks:

    pre-commit install
    

    This will automatically run code quality checks (ruff, black, codespell, etc.) before each commit.

  5. (Optional) Run pre-commit on all files manually:

    pre-commit run --all-files
    

Quick Contribution Guide

  1. Create a feature branch (git checkout -b feature/new-feature)
  2. Make your changes
  3. Run tests (see above)
  4. Pre-commit hooks will run automatically when you commit
  5. Commit your changes (git commit -m 'Add new feature')
  6. Push to the branch (git push origin feature/new-feature)
  7. Open a Pull Request

๐Ÿ“„ License

django-planet is released under the BSD 3-Clause License - see the LICENSE file for more information.

๐Ÿ™ Acknowledgements

Developed and built using:

Hatch project linting - Ruff code style - black

Inspired by:

๐Ÿ’ฌ Support

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_planet-1.0.0.tar.gz (30.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_planet-1.0.0-py3-none-any.whl (46.4 kB view details)

Uploaded Python 3

File details

Details for the file django_planet-1.0.0.tar.gz.

File metadata

  • Download URL: django_planet-1.0.0.tar.gz
  • Upload date:
  • Size: 30.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for django_planet-1.0.0.tar.gz
Algorithm Hash digest
SHA256 33f6bb6f62372d57fc63f0a17a1a6bff70c3e79eb49ae0c1359606fc4ad9f1ce
MD5 d591673afb43d69a70bdc6e0651e112a
BLAKE2b-256 a3956b1a4f0d8cb3ff556148c6f0da597632fb6a5891935603cf2098529d4a75

See more details on using hashes here.

File details

Details for the file django_planet-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: django_planet-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 46.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for django_planet-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a579267f4dc4e7e81ac214b450fb6818ed7a4fa6b955d97fd4207bbed15b60b5
MD5 8f07b31aafd8fe0fa8139d1a9d130c5a
BLAKE2b-256 112ce5dc39ffa09c37f7087a3d3adf175e8a3b6e8102a6182b91b42fa8dcd381

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page