Standalone Django app for threaded discussions with HTMX support
Project description
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:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - 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
- Documentation: [Coming soon]
- GitHub: https://github.com/directory-platform/django-threaded-discussions
- Issues: https://github.com/directory-platform/django-threaded-discussions/issues
- PyPI: https://pypi.org/project/django-threaded-discussions/ (after first release)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_threaded_discussions-0.1.0.tar.gz.
File metadata
- Download URL: django_threaded_discussions-0.1.0.tar.gz
- Upload date:
- Size: 21.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1a4ffbdb41d7384369d6ebe4877e842effbae8410090e70e7825a7662ff8c31e
|
|
| MD5 |
ea7873b3334e6e4dc7c350a71c3ffee8
|
|
| BLAKE2b-256 |
7334b8f1d5b08c13541d0b57004993beac499bdb069e2f6f38d590225805dfa1
|
Provenance
The following attestation bundles were made for django_threaded_discussions-0.1.0.tar.gz:
Publisher:
publish.yml on heysamtexas/django-threaded-discussions
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_threaded_discussions-0.1.0.tar.gz -
Subject digest:
1a4ffbdb41d7384369d6ebe4877e842effbae8410090e70e7825a7662ff8c31e - Sigstore transparency entry: 612111067
- Sigstore integration time:
-
Permalink:
heysamtexas/django-threaded-discussions@96209f82d46ee247e46dad7e53283d3898d6eaed -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/heysamtexas
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@96209f82d46ee247e46dad7e53283d3898d6eaed -
Trigger Event:
release
-
Statement type:
File details
Details for the file django_threaded_discussions-0.1.0-py3-none-any.whl.
File metadata
- Download URL: django_threaded_discussions-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
15e36a8c1ea7e79117199f00543e41aa2a551c5457ac349028cba4f9203028f0
|
|
| MD5 |
6ef81fe4cf0c394cfadc073a1fecb98c
|
|
| BLAKE2b-256 |
27146b67b59c842cd424701b5d1971dbce601f892f59d3d42b69f777fbc890fa
|
Provenance
The following attestation bundles were made for django_threaded_discussions-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on heysamtexas/django-threaded-discussions
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_threaded_discussions-0.1.0-py3-none-any.whl -
Subject digest:
15e36a8c1ea7e79117199f00543e41aa2a551c5457ac349028cba4f9203028f0 - Sigstore transparency entry: 612111068
- Sigstore integration time:
-
Permalink:
heysamtexas/django-threaded-discussions@96209f82d46ee247e46dad7e53283d3898d6eaed -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/heysamtexas
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@96209f82d46ee247e46dad7e53283d3898d6eaed -
Trigger Event:
release
-
Statement type: