Customizing Martor Widgets
Martor provides powerful widget classes that can be customized to fit your specific needs. This guide covers widget customization, configuration, and advanced usage patterns.
Available Widgets
Martor provides two main widget classes:
MartorWidget: For use in regular forms
AdminMartorWidget: For use in Django Admin
Basic Widget Usage
Standard Widget
from django import forms
from martor.widgets import MartorWidget
class MyForm(forms.Form):
content = forms.CharField(widget=MartorWidget())
Admin Widget
from django.contrib import admin
from django.db import models
from martor.widgets import AdminMartorWidget
class MyModelAdmin(admin.ModelAdmin):
formfield_overrides = {
models.TextField: {'widget': AdminMartorWidget},
}
Widget Configuration
Martor widgets can be configured through several methods:
HTML Attributes
Configure the widget using HTML data attributes:
from martor.widgets import MartorWidget
widget = MartorWidget(attrs={
# Upload configuration
'data-upload-url': '/custom-upload-endpoint/',
'data-search-users-url': '/custom-user-search/',
'data-base-emoji-url': 'https://example.com/emojis/',
# Editor behavior
'data-save-timeout': 2000, # milliseconds
'placeholder': 'Start writing...',
'rows': 15,
'class': 'my-custom-editor',
# Feature toggles (override global settings)
'data-enable-configs': {
'emoji': 'true',
'imgur': 'false',
'mention': 'true',
'living': 'false',
'spellcheck': 'true',
'hljs': 'true',
}
})
Available Data Attributes
data-upload-url: Custom image upload endpointdata-search-users-url: Custom user search endpointdata-base-emoji-url: Custom emoji base URLdata-save-timeout: Preview update delay (milliseconds)data-markdownfy-url: Custom markdown conversion endpointdata-enable-configs: Override global feature settings
Advanced Widget Customization
Custom Widget Class
Create your own widget class for reusable customizations:
from martor.widgets import MartorWidget
class BlogMartorWidget(MartorWidget):
def __init__(self, *args, **kwargs):
# Set default attributes
default_attrs = {
'data-upload-url': '/blog/upload-image/',
'data-search-users-url': '/blog/search-users/',
'placeholder': 'Write your blog post...',
'rows': 20,
'class': 'blog-editor',
'data-enable-configs': {
'emoji': 'true',
'imgur': 'false',
'mention': 'true',
'living': 'true',
'spellcheck': 'true',
'hljs': 'true',
}
}
# Merge with any provided attrs
attrs = kwargs.get('attrs', {})
default_attrs.update(attrs)
kwargs['attrs'] = default_attrs
super().__init__(*args, **kwargs)
# Usage
class BlogPostForm(forms.Form):
content = forms.CharField(widget=BlogMartorWidget())
Conditional Widget Configuration
Configure the widget based on context:
class ContextualMartorWidget(MartorWidget):
def __init__(self, user=None, *args, **kwargs):
attrs = kwargs.get('attrs', {})
# Configure based on user permissions
if user and user.has_perm('myapp.can_upload_images'):
attrs['data-upload-url'] = '/secure-upload/'
else:
attrs['data-upload-url'] = '' # Disable uploads
# Configure mentions based on user
if user and user.is_staff:
attrs['data-search-users-url'] = '/admin/search-users/'
attrs['data-enable-configs'] = {
'mention': 'true'
}
else:
attrs['data-enable-configs'] = {
'mention': 'false'
}
kwargs['attrs'] = attrs
super().__init__(*args, **kwargs)
# Usage in form
class PostForm(forms.Form):
def __init__(self, user=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['content'] = forms.CharField(
widget=ContextualMartorWidget(user=user)
)
Theme-Specific Widgets
Bootstrap Widget
class BootstrapMartorWidget(MartorWidget):
def __init__(self, *args, **kwargs):
attrs = kwargs.get('attrs', {})
attrs.update({
'class': 'form-control martor-bootstrap',
'data-theme': 'bootstrap'
})
kwargs['attrs'] = attrs
super().__init__(*args, **kwargs)
Semantic UI Widget
class SemanticMartorWidget(MartorWidget):
def __init__(self, *args, **kwargs):
attrs = kwargs.get('attrs', {})
attrs.update({
'class': 'ui input martor-semantic',
'data-theme': 'semantic'
})
kwargs['attrs'] = attrs
super().__init__(*args, **kwargs)
Widget Media Configuration
The widget automatically includes necessary CSS and JavaScript files. You can customize this:
class CustomMartorWidget(MartorWidget):
class Media:
css = {
'all': (
'plugins/css/ace.min.css',
'plugins/css/highlight.min.css',
'martor/css/martor.bootstrap.min.css',
'custom/my-martor-theme.css', # Your custom CSS
)
}
js = (
'plugins/js/ace.js',
'plugins/js/mode-markdown.js',
'plugins/js/ext-language_tools.js',
'plugins/js/theme-github.js',
'plugins/js/highlight.min.js',
'plugins/js/emojis.min.js',
'martor/js/martor.bootstrap.min.js',
'custom/my-martor-extensions.js', # Your custom JS
)
Widget Templates
Martor uses templates for rendering. You can override these:
Template Structure:
martor/
├── bootstrap/
│ ├── editor.html
│ ├── emoji.html
│ ├── guide.html
│ └── toolbar.html
└── semantic/
├── editor.html
├── emoji.html
├── guide.html
└── toolbar.html
Custom Template Example:
<!-- templates/martor/bootstrap/editor.html -->
<div class="martor-field" id="martor-{{ field_name }}">
<div class="martor-toolbar">
<!-- Custom toolbar -->
{% include "martor/bootstrap/toolbar.html" %}
</div>
<div class="martor-editor-wrapper">
{{ martor|safe }}
</div>
{% if emoji_enabled %}
<div class="martor-emoji-picker">
{% include "martor/bootstrap/emoji.html" %}
</div>
{% endif %}
<div class="martor-preview" style="display: none;">
<div class="martor-preview-content"></div>
</div>
</div>
JavaScript API Integration
Access the Martor editor instance via JavaScript:
// Get editor instance
var editor = ace.edit('martor-editor-id');
// Configure editor
editor.setTheme('ace/theme/github');
editor.getSession().setMode('ace/mode/markdown');
// Custom event handlers
editor.on('change', function() {
console.log('Content changed');
// Custom logic here
});
// Custom commands
editor.commands.addCommand({
name: 'customCommand',
bindKey: {win: 'Ctrl-K', mac: 'Command-K'},
exec: function(editor) {
// Custom command logic
}
});
Multiple Editors
When using multiple Martor editors on the same page:
class MultiEditorForm(forms.Form):
summary = forms.CharField(
widget=MartorWidget(attrs={
'placeholder': 'Brief summary...',
'rows': 8,
'data-editor-id': 'summary-editor'
})
)
content = forms.CharField(
widget=MartorWidget(attrs={
'placeholder': 'Full content...',
'rows': 20,
'data-editor-id': 'content-editor'
})
)
notes = forms.CharField(
widget=MartorWidget(attrs={
'placeholder': 'Additional notes...',
'rows': 6,
'data-editor-id': 'notes-editor',
'data-enable-configs': {
'toolbar': 'false' # Minimal toolbar
}
}),
required=False
)
Performance Optimization
Lazy Loading Widget
For pages with many editors, implement lazy loading:
class LazyMartorWidget(MartorWidget):
def __init__(self, *args, **kwargs):
attrs = kwargs.get('attrs', {})
attrs.update({
'data-lazy-load': 'true',
'class': 'martor-lazy'
})
kwargs['attrs'] = attrs
super().__init__(*args, **kwargs)
Corresponding JavaScript:
// Initialize editors only when they come into view
document.addEventListener('DOMContentLoaded', function() {
const lazyEditors = document.querySelectorAll('.martor-lazy');
const observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
initializeMartorEditor(entry.target);
observer.unobserve(entry.target);
}
});
});
lazyEditors.forEach(function(editor) {
observer.observe(editor);
});
});
Widget with Custom Upload Handler
Implement custom upload functionality:
class CustomUploadMartorWidget(MartorWidget):
def __init__(self, upload_handler=None, *args, **kwargs):
attrs = kwargs.get('attrs', {})
if upload_handler:
attrs['data-upload-url'] = upload_handler
# Custom upload configuration
attrs.update({
'data-upload-max-size': '5MB',
'data-upload-allowed-types': 'image/jpeg,image/png,image/gif',
'data-upload-progress': 'true'
})
kwargs['attrs'] = attrs
super().__init__(*args, **kwargs)
Usage Examples
Simple Blog Editor
blog_widget = MartorWidget(attrs={
'placeholder': 'Share your thoughts...',
'rows': 15,
'data-enable-configs': {
'emoji': 'true',
'imgur': 'true',
'mention': 'false',
'living': 'true',
}
})
Documentation Editor
docs_widget = MartorWidget(attrs={
'placeholder': 'Write documentation...',
'rows': 25,
'data-enable-configs': {
'emoji': 'false',
'imgur': 'false',
'mention': 'true',
'living': 'true',
'spellcheck': 'true',
'hljs': 'true',
}
})
Troubleshooting Widgets
- Editor not rendering?
Check that all required static files are included and
MARTOR_THEMEis set correctly.- Custom attributes not working?
Ensure attribute names start with
data-and use proper JSON format for complex values.- Multiple editors conflicting?
Use unique
data-editor-idattributes for each editor.- Upload not working?
Verify the upload URL is correct and the endpoint returns proper JSON responses.
Best Practices
Use data attributes for configuration:
widget = MartorWidget(attrs={
'data-upload-url': '/upload/',
'data-save-timeout': 1000,
})
Create reusable widget classes:
class BlogMartorWidget(MartorWidget):
# Predefined configuration for blog posts
Consider performance for multiple editors:
# Use lazy loading or minimal configurations
Provide appropriate placeholders:
widget = MartorWidget(attrs={
'placeholder': 'Clear instructions for users...'
})
Next Steps
Using Martor in Django Admin - Using widgets in Django Admin
../customization - Advanced customization
Basic Examples - Complete widget examples
../themes - Theming and styling
Comment Editor