Skip to main content

Django Threaded Discussions

A standalone Django app for threaded discussions with HTMX support. Attach discussions to any model using a simple UUID identifier pattern.

Features

  • Zero Coupling: Attach discussions to any model without modifying it
  • UUID-Based: Use any unique identifier (UUID, slug, ShortUUID, etc.)
  • HTMX Ready: Built-in HTMX templates for dynamic interactions
  • SEO Friendly: Server-side rendering support with structured data helpers
  • Threaded Comments: Nested comment replies with parent/child relationships
  • User Permissions: Built-in authentication and ownership checks
  • Spam Protection: Flags for spam detection and moderation
  • Lightweight: Minimal dependencies, maximum flexibility

Installation

From PyPI (when published)

pip install django-threaded-discussions

From Source (Development)

# In your workspace directory
git clone https://github.com/directory-platform/django-threaded-discussions.git
cd django-threaded-discussions
pip install -e .

UV Workspace (Recommended for development)

Add to your workspace pyproject.toml:

[tool.uv.workspace]
members = [
    "your-project",
    "django-threaded-discussions",
]

Then run:

uv sync

Quick Start

1. Add to Django Settings

# settings.py
INSTALLED_APPS = [
    # ...
    "django_discussions",
]

2. Run Migrations

python manage.py migrate django_discussions

3. Include URLs

# urls.py
urlpatterns = [
    # ...
    path("discussions/", include("django_discussions.urls")),
]

4. Add to Your Templates

<!-- blog_post_detail.html -->
<div class="discussion-section">
    <!-- HTMX lazy-load discussions -->
    <div hx-get="/discussions/{{ blog_post.uuid }}/"
         hx-trigger="load">
        Loading discussions...
    </div>
</div>

<!-- Or server-render for SEO -->
{% load discussion_tags %}
{% render_discussions_seo discussion_id=blog_post.uuid %}

That's it! No changes to your models required.

How It Works

Django Threaded Discussions uses a simple discussion_id string field to link threads to your content. This means:

  • ✅ No ForeignKeys to your models
  • ✅ No GenericForeignKey complexity
  • ✅ No migrations when adding new model types
  • ✅ Works with any unique identifier you already have

Usage Examples

With UUID Field

class BlogPost(models.Model):
    uuid = models.UUIDField(default=uuid.uuid4, unique=True)
    # ... other fields
<div hx-get="/discussions/{{ blog_post.uuid }}/">...</div>

With Slug Field

class Article(models.Model):
    slug = models.SlugField(unique=True)
    # ... other fields
<div hx-get="/discussions/{{ article.slug }}/">...</div>

With ShortUUID

from shortuuid.django_fields import ShortUUIDField

class Product(models.Model):
    short_id = ShortUUIDField(unique=True)
    # ... other fields
<div hx-get="/discussions/{{ product.short_id }}/">...</div>

With Custom Identifier

class Entity(models.Model):
    id = models.AutoField(primary_key=True)

    @property
    def discussion_id(self):
        return f"entity-{self.id}"
<div hx-get="/discussions/{{ entity.discussion_id }}/">...</div>

Authentication & Permissions

Discussions integrate seamlessly with Django's auth system:

Public Views (No Login Required):

  • View discussion threads
  • View comments
  • See thread counts

Authenticated Views (@login_required):

  • Create new threads
  • Post comments
  • Reply to comments

Ownership Checks:

  • Delete own threads/comments
  • Superusers can delete any content
# Automatically enforced
@login_required
def create_thread(request, discussion_id):
    thread = Thread.objects.create(
        discussion_id=discussion_id,
        author=request.user,  # ← Django's auth
        # ...
    )

SEO Optimization

Server-Side Rendering

Render discussions on first page load for SEO:

# views.py
from django_discussions.models import Thread

def blog_post_detail(request, slug):
    post = get_object_or_404(BlogPost, slug=slug)

    # Get SEO-worthy threads
    seo_threads = Thread.get_seo_threads(
        discussion_id=str(post.uuid),
        max_count=5
    )

    context = {
        'post': post,
        'seo_threads': seo_threads,
    }
    return render(request, 'blog_detail.html', context)
<!-- blog_detail.html -->
<div id="discussions">
    <!-- Server-rendered for SEO -->
    {% for thread in seo_threads %}
        {% include "django_discussions/_thread.html" %}
    {% endfor %}

    <!-- HTMX load more -->
    <div hx-get="/discussions/{{ post.uuid }}/?offset=5">
        Load more...
    </div>
</div>

Structured Data

Add schema.org markup for rich snippets:

{% load discussion_tags %}
{% discussion_structured_data discussion_id=post.uuid %}

Generates:

{
  "@context": "https://schema.org",
  "@type": "DiscussionForumPosting",
  "headline": "Thread title",
  "commentCount": 42,
  // ... etc
}

Template Tags

{% load discussion_tags %}

<!-- Render SEO-optimized discussions -->
{% render_discussions_seo discussion_id="abc-123" max_threads=10 %}

<!-- Get thread count -->
{% thread_count discussion_id="abc-123" %}

<!-- Structured data for SEO -->
{% discussion_structured_data discussion_id="abc-123" %}

Models

Thread

class Thread(models.Model):
    discussion_id = CharField(max_length=255)  # Your unique identifier
    title = CharField(max_length=255)
    content = TextField()
    author = ForeignKey(User)
    short_uuid = ShortUUIDField(unique=True)

    # Moderation flags
    is_active = BooleanField(default=True)
    is_spam = BooleanField(default=False)
    is_deleted = BooleanField(default=False)
    is_locked = BooleanField(default=False)

    # Timestamps
    created_at = DateTimeField(auto_now_add=True)
    updated_at = DateTimeField(auto_now=True)

Comment

class Comment(models.Model):
    thread = ForeignKey(Thread)
    content = TextField()
    author = ForeignKey(User)
    parent = ForeignKey('self', null=True)  # Threaded replies
    short_uuid = ShortUUIDField(unique=True)

    # Moderation flags
    is_active = BooleanField(default=True)
    is_pinned = BooleanField(default=False)
    is_spam = BooleanField(default=False)

    # Timestamps
    created_at = DateTimeField(auto_now_add=True)
    updated_at = DateTimeField(auto_now=True)

Customization

Settings

# settings.py
DISCUSSIONS_SETTINGS = {
    'MAX_THREADS_PER_PAGE': 20,
    'MAX_COMMENTS_PER_THREAD': 100,
    'ALLOW_ANONYMOUS_VIEWING': True,
    'MIN_TITLE_LENGTH': 3,
    'MAX_TITLE_LENGTH': 100,
}

Custom Templates

Override any template by creating templates/django_discussions/ in your project:

your_project/
  templates/
    django_discussions/
      index.html              # Override thread list
      _thread_header.html     # Override thread display
      _comment.html           # Override comment display

Admin Interface

Discussions are registered in Django admin with search and filtering:

# Automatic admin registration
# Access at /admin/django_discussions/thread/
# Access at /admin/django_discussions/comment/

Development

Setup

git clone https://github.com/directory-platform/django-threaded-discussions.git
cd django-threaded-discussions
uv sync --extra dev

Running Tests

make test
# or
PYTHONPATH=. uv run python tests/manage.py test

Code Quality

make lint          # Ruff linting
make format        # Auto-format
make typecheck     # Mypy type checking
make check         # All checks + tests

Requirements

  • Python 3.12+
  • Django 4.2+
  • shortuuid 1.0+

Contributing

Contributions welcome! Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT License - see LICENSE file for details.

Credits

Built by the Directory Platform team as part of the directory ecosystem.

Links

Metadata

Release files for django-threaded-discussions 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-threaded-discussions 0.1.0
File Size Uploaded
django_threaded_discussions-0.1.0.tar.gz 21.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-threaded-discussions 0.1.0
File Interpreter ABI Platform
django_threaded_discussions-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 41.4 kB

Release files / django_threaded_discussions-0.1.0.tar.gz

Download URL django_threaded_discussions-0.1.0.tar.gz
Size 21.2 kB
Tags Source
SHA-256 checksum
How to use checksums
1a4ffbdb41d7384369d6ebe4877e842effbae8410090e70e7825a7662ff8c31e
BLAKE2b-256 checksum
How to use checksums
7334b8f1d5b08c13541d0b57004993beac499bdb069e2f6f38d590225805dfa1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2025.

Transparency log

Release files / django_threaded_discussions-0.1.0-py3-none-any.whl

Download URL django_threaded_discussions-0.1.0-py3-none-any.whl
Size 20.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
15e36a8c1ea7e79117199f00543e41aa2a551c5457ac349028cba4f9203028f0
BLAKE2b-256 checksum
How to use checksums
27146b67b59c842cd424701b5d1971dbce601f892f59d3d42b69f777fbc890fa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2025.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page