Reusable Flask commenting system with moderation and content filtering
Project description
qdcomments - QuickDev Commenting System
A reusable Flask commenting system with sophisticated moderation, user permissions, and content filtering.
Features
-
User-level comment permissions
- Text-only ('t'): Plain text with HTML escaping
- Limited HTML ('h'): Safe HTML tags only (bold, italic, links)
- Markdown ('m'): Full markdown support
-
Three-tier moderation system
- '0': Blocked (all comments auto-rejected)
- '1': Requires approval (comments held for moderation)
- '9': Auto-approved (comments posted immediately)
-
Content filtering
- Configurable blocked_words list via YAML file
- Automatic flagging of comments with blocked words
- Site-editable word list through admin interface
-
Content-agnostic design
- Works with articles, products, listings, or any content type
- Flexible content_type + content_id pattern
-
Comment threading
- Nested replies with configurable depth limit
- Parent-child comment relationships
-
Global moderation dashboard
- View pending comments
- Approve/reject workflow
- Filter by status, content type, user
- Track moderation history
-
Admin interface
- Edit blocked_words.yaml
- Toggle global settings
- Monitor all comment activity
Installation
From local directory (development):
cd /path/to/QuickDev/qdcomments
pip install -e .
Requirements:
- Python 3.7+
- Flask 2.0+
- Flask-SQLAlchemy
- Flask-Login
- PyYAML
- Markdown
- qdflask (required for User model)
Quick Start
1. Install qdcomments
pip install -e /path/to/QuickDev/qdcomments
2. Initialize your Flask app
from flask import Flask
from qdflask import init_auth
from qdflask.models import db
from qdcomments import init_comments
app = Flask(__name__)
# Configure
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
app.config['DATA_DIR'] = '/path/to/data'
# Initialize authentication (qdflask)
init_auth(app)
# Initialize commenting
init_comments(app, config={
'COMMENTS_ENABLED': True,
'BLOCKED_WORDS_PATH': '/path/to/data/blocked_words.yaml'
}, db_instance=db)
if __name__ == '__main__':
app.run(debug=True)
3. Migrate user table
Add comment fields to existing users:
qdcomments-migrate-users --app myapp:create_app
4. Initialize blocked words
Create default blocked_words.yaml:
qdcomments-init-blocked-words --app myapp:create_app
5. Add to templates
In your content template (e.g., article.html):
{% if comments_enabled %}
<div id="comments">
<h2>Comments</h2>
{# Comment submission form #}
{% include 'qdcomments/comment_form.html' %}
{# Comment list #}
{% include 'qdcomments/comment_list.html' %}
</div>
{% endif %}
6. Update your route
Pass comments to the template:
from qdcomments.models import Comment
@app.route('/article/<slug>')
def article(slug):
# ... load article ...
# Load comments
comments = Comment.get_for_content(
content_type='article',
content_id=slug,
status='p'
).all()
return render_template('article.html',
article=article,
comments_enabled=True,
comments=comments,
content_type='article',
content_id=slug
)
Configuration Options
Pass to init_comments() via the config dict:
| Option | Default | Description |
|---|---|---|
COMMENTS_ENABLED |
True | Enable/disable comments globally |
COMMENTS_REQUIRE_LOGIN |
True | Require authentication to comment |
BLOCKED_WORDS_PATH |
DATA_DIR/blocked_words.yaml | Path to blocked words file |
COMMENTS_PER_PAGE |
50 | Pagination limit |
COMMENT_MAX_LENGTH |
5000 | Maximum comment length (characters) |
ALLOW_THREADING |
True | Enable comment replies |
MAX_THREAD_DEPTH |
3 | Maximum nesting level for replies |
Database Schema
Extended User Model (qdflask)
class User:
# ... existing fields ...
comment_style = Column(String(1), default='t') # 't', 'h', 'm'
moderation_level = Column(String(1), default='1') # '0', '1', '9'
Comment Model
class Comment:
id = Integer (PK)
user_id = Integer (FK to users)
content_type = String # 'article', 'product', etc.
content_id = String # slug, product ID, etc.
content = Text # Raw comment content
user_comment_style = String # Snapshot of user's style at comment time
user_moderation_level = String # Snapshot of user's level at comment time
status = String # 'p' (posted), 'm' (moderation), 'b' (blocked)
status_reason = String # 'a' (automatic), 'm' (moderator), 'd' (blocked_words)
parent_id = Integer (FK to comments, nullable)
created_at = DateTime
updated_at = DateTime
moderated_at = DateTime (nullable)
moderated_by_id = Integer (FK to users, nullable)
Routes
Public Routes
POST /comments/post- Submit a commentGET /comments/list/<content_type>/<path:content_id>- Get comments for contentGET /comments/count/<content_type>/<path:content_id>- Get comment count
Moderation Routes (admin/editor)
GET /comments/moderation/queue- View pending commentsPOST /comments/moderation/approve/<id>- Approve commentPOST /comments/moderation/reject/<id>- Reject commentGET /comments/moderation/activity- View all comments with filters
Admin Routes (admin only)
GET/POST /comments/admin/blocked-words- Edit blocked_words.yamlGET/POST /comments/admin/config- Toggle global settings
CLI Tools
Migrate Users Table
Add comment fields to existing User table:
qdcomments-migrate-users --app myapp:create_app
Initialize Blocked Words
Create default blocked_words.yaml:
qdcomments-init-blocked-words --app myapp:create_app [--path /custom/path.yaml]
List Pending Comments
qdcomments-pending --app myapp:create_app
Approve Comment
qdcomments-approve --app myapp:create_app --id 123 [--moderator-id 1]
Reject Comment
qdcomments-reject --app myapp:create_app --id 123 [--moderator-id 1]
Content-Agnostic Usage
qdcomments works with any content type by using the content_type + content_id pattern:
Articles (Trellis):
content_type = 'article'
content_id = 'python/my-article-slug'
Products (e-commerce):
content_type = 'product'
content_id = 'SKU-12345'
Listings:
content_type = 'listing'
content_id = 'ebay-293847562938'
User Permission Levels
comment_style
| Value | Name | Behavior |
|---|---|---|
| 't' | Text | Plain text, HTML escaped, newlines → <br> |
| 'h' | Limited HTML | Safe tags only: <b>, <i>, <strong>, <em>, <a>, <br> |
| 'm' | Markdown | Full markdown support, raw HTML escaped |
Defaults:
- New users: 't'
- Admin/editor: 'm' (after migration)
moderation_level
| Value | Name | Behavior |
|---|---|---|
| '0' | Blocked | All comments automatically blocked (status='b') |
| '1' | Requires Approval | Comments held for moderation (status='m') |
| '9' | Auto-Approved | Comments posted immediately (status='p') |
Defaults:
- New users: '1'
- Admin/editor: '9' (after migration)
blocked_words.yaml Format
# Blocked words for comment filtering
words:
- spam
- viagra
- badword
# Configuration
case_sensitive: false # Treat "Spam" and "spam" the same
whole_word_only: true # Only match complete words (not substrings)
Edit via:
- Admin interface:
/comments/admin/blocked-words - Direct file edit:
$DATA_DIR/blocked_words.yaml
Security Features
-
XSS Prevention
- Text mode: HTML escaping
- HTML mode: Whitelist sanitization
- Markdown mode: Safe rendering with HTML escape
-
SQL Injection Protection
- Uses SQLAlchemy ORM (parameterized queries)
-
Content Filtering
- blocked_words detection
- Automatic moderation queue
-
Authentication & Authorization
- Requires login for commenting (configurable)
- Role-based moderation access (admin/editor)
-
Input Validation
- Content length limits
- Thread depth limits
- YAML validation for blocked_words
Workflow Examples
New User Comments:
- User (comment_style='t', moderation_level='1') submits comment
- Content is HTML-escaped
- Checked against blocked_words
- If clean: status='m', status_reason='a' (held for moderation)
- If blocked words found: status='m', status_reason='d'
- Admin/editor reviews in moderation queue
- Approve: status='p', status_reason='m', moderated_at/moderated_by_id set
- Comment now visible to public
Admin Comments:
- Admin (comment_style='m', moderation_level='9') submits comment
- Content processed as markdown
- Checked against blocked_words
- If clean: status='p', status_reason='a' (immediately posted)
- If blocked words found: status='m', status_reason='d' (held for review)
Architecture Notes
- Shares database with qdflask: Uses
db_instanceparameter to ensure Comment and User models use the same database - Content-agnostic: Works with any content by using flexible content_type/content_id pattern
- User snapshot: Stores user's comment_style and moderation_level at comment time (prevents retroactive permission changes)
- Status tracking: Comprehensive status and status_reason codes for audit trail
Integration with Trellis
See INTEGRATION.md for full Trellis integration guide (if available).
Quick summary:
- Install qdcomments
- Add
init_comments()totrellis/__init__.py - Run
qdcomments-migrate-users - Update article route to load comments
- Add
{% include 'qdcomments/comment_form.html' %}to article template
Integration with CommerceNode
Works identically to Trellis, but use:
content_type='product'for product pagescontent_type='listing'for marketplace listings
License
MIT License - see LICENSE file for details
Author
QuickDev / Allan Margolis
Contributing
This is part of the QuickDev suite of reusable Flask components. Contributions welcome!
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 qdcomments-0.1.0.tar.gz.
File metadata
- Download URL: qdcomments-0.1.0.tar.gz
- Upload date:
- Size: 25.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3ac9296be4d355c2f402e1babbf6becc682e55e0c249168e1c18ac9aa63fbbe2
|
|
| MD5 |
4eac086fe6261019074fe28c75eb8c18
|
|
| BLAKE2b-256 |
e30f149c9de25e019e0db4fbef1d5f816ac0f4791d7ee3f53655a6fa247c0d51
|
File details
Details for the file qdcomments-0.1.0-py3-none-any.whl.
File metadata
- Download URL: qdcomments-0.1.0-py3-none-any.whl
- Upload date:
- Size: 27.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8cd606b12e1f04656201fcb3fbc610d47a9c0f18e27170d5142e70224a7a630e
|
|
| MD5 |
5a8ebe955c5ca28c49a37891bf28206c
|
|
| BLAKE2b-256 |
bc23525ec67b364388b2fd1e17fe4af001e26088d03b279081d8333b8d9c5052
|