Plugins

DJ Press includes a plugin system that allows you to extend its functionality. Plugins can modify content before and after rendering, send notifications when content is published, and more. The plugin system is designed to be easy to use while still being powerful enough for complex extensions.

Plugin Lifecycle

The DJ Press plugin system follows this lifecycle:

  1. Discovery - Plugins are loaded from the PLUGINS setting during application startup

  2. Registration - Plugins register callbacks for specific hooks

  3. Execution - Callbacks are executed when hooks are triggered during normal application operation

  4. Cleanup - When Django shuts down, plugins can perform cleanup (if needed)

Creating a Plugin

To create a plugin, create a new Python package with the following structure:

djpress_my_plugin/
    __init__.py
    plugin.py
    tests/
        __init__.py
        test_plugin.py

In plugin.py, create a class called Plugin that inherits from DJPressPlugin:

from djpress.plugins import DJPressPlugin
from djpress.plugin.hook_registry import PRE_RENDER_CONTENT, POST_RENDER_CONTENT, POST_SAVE_POST

class Plugin(DJPressPlugin):
    name = "djpress_my_plugin"  # Required - use same name as package
    hooks = [
        (PRE_RENDER_CONTENT, "modify_content"),
        (POST_RENDER_CONTENT, "modify_html"),
        (POST_SAVE_POST, "notify_on_publish"),
    ]


    def modify_content(self, content: str) -> str:
        """Modify the markdown content before rendering.

        This callback implements the PreRenderHook signature.

        Args:
            content: The raw markdown content

        Returns:
            Modified markdown content
        """
        try:
            # Always wrap plugin code in try/except to prevent site breakage
            if not content or not isinstance(content, str):
                return content

            # Example: Add a header to all content
            prefix = self.config.get("prefix_text", "")
            if prefix:
                content = f"{prefix}\n\n{content}"

            return content
        except Exception as e:
            # Log the error but don't break the site
            import logging
            logging.error(f"Error in modify_content: {e}")
            return content

    def modify_html(self, html_content: str) -> str:
        """Modify the HTML after markdown rendering.

        Args:
            html_content: The rendered HTML content

        Returns:
            Modified HTML content
        """
        try:
            # Example: Add a footer to all content
            suffix = self.config.get("suffix_text", "")
            if suffix and html_content:
                html_content = f"{html_content}<div class='plugin-footer'>{suffix}</div>"
            return html_content
        except Exception as e:
            import logging
            logging.error(f"Error in modify_html: {e}")
            return html_content

    def notify_on_publish(self, post):
        """Send notification when a post is published.

        Args:
            post: The Post object that was published

        Returns:
            None (return value is ignored for this hook)
        """
        try:
            # Example: Track published posts in plugin storage
            data = self.get_data() or {"published_posts": []}

            # Add the post ID to our tracking list if not already there
            if post.id not in data.get("published_posts", []):
                data.setdefault("published_posts", []).append(post.id)
                self.save_data(data)

            # Example: Could send an email, ping a webhook, etc.
            if self.config.get("send_notifications"):
                self._send_notification(post)
        except Exception as e:
            import logging
            logging.error(f"Error in notify_on_publish: {e}")

    def _send_notification(self, post):
        """Private helper method to send notifications."""
        # Implementation would depend on notification method
        pass

Saving Plugin Data

Plugins can store data in the database through the PluginStorage model. This is useful for maintaining state between requests or for caching data.

# Get the current data (returns None if no data exists)
data = self.get_data()

# Initialise if needed
if data is None:
    data = {"counter": 0, "settings": {}}

# Update the data
data["counter"] += 1
data["settings"]["last_updated"] = str(timezone.now())

# Save the data back to the database
self.save_data(data)

The data must be JSON-serialisable (dictionaries, lists, strings, numbers, booleans, or None).

Available Hooks

DJ Press provides these hooks for plugins:

Hook Name

Description

Arguments

Expected Return

PRE_RENDER_CONTENT

Called before markdown content is rendered to HTML

content: str (markdown)

Modified markdown content

POST_RENDER_CONTENT

Called after markdown content is rendered to HTML

content: str (HTML)

Modified HTML content

POST_SAVE_POST

Called after saving a published post

post: Post (object)

None (return ignored)

DJ_HEADER

Used to insert HTML into the template’s <head> tag.

None

HTML content (str)

DJ_FOOTER

Called after saving a published post

post: Post (object)

None (return ignored)

Hooks are imported from the hook_registry module, and then assigned to a list called hooks in the Plugin class. The hook is added to the list as a tuple with the first element being the hook, and the second element being the string name of the callable.

from djpress.plugin.hook_registry import PRE_RENDER_CONTENT

# Define a hooks list of tuples:
    hooks = [(PRE_RENDER_CONTENT, "modify_content")]

Installing Plugins

To install a plugin, follow these steps:

  1. Install the plugin package:

pip install djpress-my-plugin
  1. Add the plugin to your settings:

DJPRESS_SETTINGS = {
    "PLUGINS": [
        "djpress_my_plugin"  # Package name
    ],
}
  1. Run migrations and restart your server:

python manage.py migrate

If your plugin is structured differently, specify the full path:

DJPRESS_SETTINGS = {
    "PLUGINS": [
        "djpress_my_plugin.custom.MyPlugin"  # Full path to class
    ],
}

Plugin Configuration

Configure plugins using the PLUGIN_SETTINGS dictionary:

DJPRESS_SETTINGS = {
    "PLUGINS": ["djpress_example_plugin"],
    "PLUGIN_SETTINGS": {
        "djpress_example_plugin": {  # Must match plugin's 'name'
            "prefix_text": "### Featured Content",
            "suffix_text": "<em>Thanks for reading!</em>",
            "send_notifications": True,
            "notification_email": "admin@example.com",
        },
    },
}

Access settings in your plugin using self.config:

# Get with default value if setting doesn't exist
prefix = self.config.get("prefix_text", "Default prefix")

# Check if a setting exists
if "send_notifications" in self.config:
    # Do something

Error Handling

Proper error handling is essential for plugins. Always wrap your code in try/except blocks:

def my_hook_handler(self, content):
    try:
        # Your code here
        return modified_content
    except Exception as e:
        import logging
        logging.error(f"Plugin error in my_hook_handler: {e}")
        # Return original content to avoid breaking the site
        return content

Plugin Development Guidelines

  1. Unique Name: Define a unique name property that matches your package name.

  2. Error Handling: Always use try/except to prevent crashing the site.

  3. Type Hints: Use type hints for better code maintainability.

  4. Documentation: Document your plugin’s purpose, hooks, and configuration options.

  5. Testing: Include comprehensive tests for your plugin’s functionality.

  6. Performance: Be mindful of performance in hooks that run frequently.

  7. Security: Validate and sanitise any user-provided data.

System Checks

DJ Press includes system checks that will warn about:

  • Unknown hooks (might indicate deprecated hooks or version mismatches)

  • Plugins that fail to load or initialise properly

Run Django’s check framework to see any warnings:

python manage.py check

Complete Example Plugin

Check out the example plugin included with DJ Press for a working reference implementation.