Skip to main content

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 comment
  • GET /comments/list/<content_type>/<path:content_id> - Get comments for content
  • GET /comments/count/<content_type>/<path:content_id> - Get comment count

Moderation Routes (admin/editor)

  • GET /comments/moderation/queue - View pending comments
  • POST /comments/moderation/approve/<id> - Approve comment
  • POST /comments/moderation/reject/<id> - Reject comment
  • GET /comments/moderation/activity - View all comments with filters

Admin Routes (admin only)

  • GET/POST /comments/admin/blocked-words - Edit blocked_words.yaml
  • GET/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

  1. XSS Prevention

    • Text mode: HTML escaping
    • HTML mode: Whitelist sanitization
    • Markdown mode: Safe rendering with HTML escape
  2. SQL Injection Protection

    • Uses SQLAlchemy ORM (parameterized queries)
  3. Content Filtering

    • blocked_words detection
    • Automatic moderation queue
  4. Authentication & Authorization

    • Requires login for commenting (configurable)
    • Role-based moderation access (admin/editor)
  5. Input Validation

    • Content length limits
    • Thread depth limits
    • YAML validation for blocked_words

Workflow Examples

New User Comments:

  1. User (comment_style='t', moderation_level='1') submits comment
  2. Content is HTML-escaped
  3. Checked against blocked_words
  4. If clean: status='m', status_reason='a' (held for moderation)
  5. If blocked words found: status='m', status_reason='d'
  6. Admin/editor reviews in moderation queue
  7. Approve: status='p', status_reason='m', moderated_at/moderated_by_id set
  8. Comment now visible to public

Admin Comments:

  1. Admin (comment_style='m', moderation_level='9') submits comment
  2. Content processed as markdown
  3. Checked against blocked_words
  4. If clean: status='p', status_reason='a' (immediately posted)
  5. If blocked words found: status='m', status_reason='d' (held for review)

Architecture Notes

  • Shares database with qdflask: Uses db_instance parameter 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:

  1. Install qdcomments
  2. Add init_comments() to trellis/__init__.py
  3. Run qdcomments-migrate-users
  4. Update article route to load comments
  5. Add {% include 'qdcomments/comment_form.html' %} to article template

Integration with CommerceNode

Works identically to Trellis, but use:

  • content_type='product' for product pages
  • content_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


Download files

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

Source Distribution

qdcomments-0.1.0.tar.gz (25.5 kB view details)

Uploaded Source

Built Distribution

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

qdcomments-0.1.0-py3-none-any.whl (27.0 kB view details)

Uploaded Python 3

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

Hashes for qdcomments-0.1.0.tar.gz
Algorithm Hash digest
SHA256 3ac9296be4d355c2f402e1babbf6becc682e55e0c249168e1c18ac9aa63fbbe2
MD5 4eac086fe6261019074fe28c75eb8c18
BLAKE2b-256 e30f149c9de25e019e0db4fbef1d5f816ac0f4791d7ee3f53655a6fa247c0d51

See more details on using hashes here.

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

Hashes for qdcomments-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8cd606b12e1f04656201fcb3fbc610d47a9c0f18e27170d5142e70224a7a630e
MD5 5a8ebe955c5ca28c49a37891bf28206c
BLAKE2b-256 bc23525ec67b364388b2fd1e17fe4af001e26088d03b279081d8333b8d9c5052

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