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:
Discovery - Plugins are loaded from the
PLUGINS
setting during application startupRegistration - Plugins register callbacks for specific hooks
Execution - Callbacks are executed when hooks are triggered during normal application operation
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 |
---|---|---|---|
|
Called before markdown content is rendered to HTML |
|
Modified markdown content |
|
Called after markdown content is rendered to HTML |
|
Modified HTML content |
|
Called after saving a published post |
|
None (return ignored) |
|
Used to insert HTML into the template’s |
None |
HTML content ( |
|
Called after saving a published post |
|
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:
Install the plugin package:
pip install djpress-my-plugin
Add the plugin to your settings:
DJPRESS_SETTINGS = {
"PLUGINS": [
"djpress_my_plugin" # Package name
],
}
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
Unique Name: Define a unique
name
property that matches your package name.Error Handling: Always use try/except to prevent crashing the site.
Type Hints: Use type hints for better code maintainability.
Documentation: Document your plugin’s purpose, hooks, and configuration options.
Testing: Include comprehensive tests for your plugin’s functionality.
Performance: Be mindful of performance in hooks that run frequently.
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.