Template Integration
This guide covers how to properly integrate Martor in your Django templates, both for editing (forms) and displaying (rendered markdown) content.
Template Structure for Forms
Basic Form Template
<!-- post_form.html -->
{% extends "base.html" %}
{% load static %}
{% block title %}Create Post{% endblock %}
{% block css %}
<!-- Required CSS files -->
<link href="{% static 'plugins/css/ace.min.css' %}" rel="stylesheet">
<link href="{% static 'plugins/css/highlight.min.css' %}" rel="stylesheet">
<link href="{% static 'martor/css/martor.bootstrap.min.css' %}" rel="stylesheet">
<!-- Optional: Bootstrap for styling -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h3>Create New Post</h3>
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
<!-- Title field -->
<div class="mb-3">
<label for="{{ form.title.id_for_label }}" class="form-label">
{{ form.title.label }}
</label>
{{ form.title }}
{% if form.title.errors %}
<div class="text-danger">
{% for error in form.title.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
<!-- Martor content field -->
<div class="mb-3">
<label for="{{ form.content.id_for_label }}" class="form-label">
{{ form.content.label }}
</label>
{{ form.content }}
{% if form.content.help_text %}
<div class="form-text">{{ form.content.help_text }}</div>
{% endif %}
{% if form.content.errors %}
<div class="text-danger">
{% for error in form.content.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Save Post
</button>
<a href="{% url 'post_list' %}" class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<!-- Required JavaScript files -->
<script src="{% static 'plugins/js/ace.js' %}"></script>
<script src="{% static 'plugins/js/mode-markdown.js' %}"></script>
<script src="{% static 'plugins/js/ext-language_tools.js' %}"></script>
<script src="{% static 'plugins/js/theme-github.js' %}"></script>
<script src="{% static 'plugins/js/highlight.min.js' %}"></script>
<script src="{% static 'plugins/js/emojis.min.js' %}"></script>
<script src="{% static 'martor/js/martor.bootstrap.min.js' %}"></script>
<!-- Optional: Bootstrap JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
{% endblock %}
Semantic UI Template
<!-- semantic_form.html -->
{% extends "base.html" %}
{% load static %}
{% block css %}
<!-- Semantic UI CSS -->
<link href="{% static 'plugins/css/semantic.min.css' %}" rel="stylesheet">
<link href="{% static 'plugins/css/ace.min.css' %}" rel="stylesheet">
<link href="{% static 'martor/css/martor.semantic.min.css' %}" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="ui container">
<div class="ui segment">
<h2 class="ui header">Create Post</h2>
<form class="ui form" method="post">
{% csrf_token %}
<div class="field">
<label>{{ form.title.label }}</label>
{{ form.title }}
</div>
<div class="field">
<label>{{ form.content.label }}</label>
{{ form.content }}
</div>
<button type="submit" class="ui primary button">
<i class="save icon"></i> Save
</button>
</form>
</div>
</div>
{% endblock %}
{% block js %}
<script src="{% static 'plugins/js/semantic.min.js' %}"></script>
<script src="{% static 'plugins/js/ace.js' %}"></script>
<script src="{% static 'plugins/js/mode-markdown.js' %}"></script>
<script src="{% static 'plugins/js/ext-language_tools.js' %}"></script>
<script src="{% static 'plugins/js/theme-github.js' %}"></script>
<script src="{% static 'plugins/js/highlight.min.js' %}"></script>
<script src="{% static 'plugins/js/emojis.min.js' %}"></script>
<script src="{% static 'martor/js/martor.semantic.min.js' %}"></script>
{% endblock %}
Template Structure for Display
Basic Display Template
<!-- post_detail.html -->
{% extends "base.html" %}
{% load static %}
{% load martortags %}
{% block title %}{{ post.title }}{% endblock %}
{% block css %}
<!-- CSS for rendered markdown -->
<link href="{% static 'plugins/css/highlight.min.css' %}" rel="stylesheet">
<link href="{% static 'martor/css/martor.bootstrap.min.css' %}" rel="stylesheet">
<!-- Custom styles for content -->
<style>
.post-content {
max-width: 800px;
margin: 0 auto;
line-height: 1.6;
}
.post-content h1, .post-content h2, .post-content h3 {
margin-top: 2rem;
margin-bottom: 1rem;
}
.post-content img {
max-width: 100%;
height: auto;
border-radius: 4px;
margin: 1rem 0;
}
.post-content blockquote {
border-left: 4px solid #007bff;
padding-left: 1rem;
margin: 1rem 0;
font-style: italic;
}
</style>
{% endblock %}
{% block content %}
<div class="container mt-4">
<article class="post-content">
<header class="mb-4">
<h1>{{ post.title }}</h1>
<p class="text-muted">
Published on {{ post.created_at|date:"F d, Y" }}
{% if post.author %}by {{ post.author.get_full_name|default:post.author.username }}{% endif %}
</p>
</header>
<div class="martor-preview">
{{ post.content|safe_markdown }}
</div>
<footer class="mt-4 pt-3 border-top">
<p class="text-muted">
Last updated: {{ post.updated_at|date:"F d, Y" }}
</p>
</footer>
</article>
</div>
{% endblock %}
{% block js %}
<!-- JavaScript for syntax highlighting -->
<script src="{% static 'plugins/js/highlight.min.js' %}"></script>
<script>
// Apply syntax highlighting to code blocks
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.martor-preview pre code').forEach(function(block) {
hljs.highlightBlock(block);
});
});
</script>
{% endblock %}
Advanced Display Templates
Template with Table of Contents:
<!-- advanced_post.html -->
{% extends "base.html" %}
{% load static %}
{% load martortags %}
{% block css %}
{{ block.super }}
<style>
.toc {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
padding: 1rem;
margin-bottom: 2rem;
}
.toc ul {
list-style: none;
padding-left: 1rem;
}
.toc a {
text-decoration: none;
color: #495057;
}
.toc a:hover {
color: #007bff;
}
</style>
{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-lg-9">
<article class="post-content">
<h1>{{ post.title }}</h1>
<div class="martor-preview">
{{ post.content|safe_markdown }}
</div>
</article>
</div>
<div class="col-lg-3">
<div class="toc sticky-top" style="top: 2rem;">
<h5>Table of Contents</h5>
<div id="toc-content">
<!-- Generated by JavaScript -->
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
{{ block.super }}
<script>
// Generate table of contents
document.addEventListener('DOMContentLoaded', function() {
const tocContainer = document.getElementById('toc-content');
const headings = document.querySelectorAll('.martor-preview h1, .martor-preview h2, .martor-preview h3');
if (headings.length > 0) {
const tocList = document.createElement('ul');
headings.forEach(function(heading, index) {
const id = 'heading-' + index;
heading.id = id;
const listItem = document.createElement('li');
const link = document.createElement('a');
link.href = '#' + id;
link.textContent = heading.textContent;
link.className = 'toc-' + heading.tagName.toLowerCase();
listItem.appendChild(link);
tocList.appendChild(listItem);
});
tocContainer.appendChild(tocList);
} else {
tocContainer.innerHTML = '<p class="text-muted">No headings found</p>';
}
});
</script>
{% endblock %}
Multiple Content Display
For models with multiple markdown fields:
<!-- product_detail.html -->
{% extends "base.html" %}
{% load static %}
{% load martortags %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-8">
<h1>{{ product.name }}</h1>
<!-- Product description -->
<section class="mb-5">
<h2>Description</h2>
<div class="martor-preview">
{{ product.description|safe_markdown }}
</div>
</section>
<!-- Technical specifications -->
{% if product.specifications %}
<section class="mb-5">
<h2>Technical Specifications</h2>
<div class="martor-preview">
{{ product.specifications|safe_markdown }}
</div>
</section>
{% endif %}
<!-- Usage instructions -->
{% if product.usage_instructions %}
<section class="mb-5">
<h2>Usage Instructions</h2>
<div class="martor-preview">
{{ product.usage_instructions|safe_markdown }}
</div>
</section>
{% endif %}
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Product Info</h5>
<p class="card-text">
<strong>Price:</strong> ${{ product.price }}<br>
<strong>SKU:</strong> {{ product.sku }}
</p>
<button class="btn btn-primary">Add to Cart</button>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
AJAX Templates
For dynamic content loading:
<!-- ajax_preview.html -->
<div class="modal fade" id="previewModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Preview</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="preview-content" class="martor-preview">
<!-- Content loaded via AJAX -->
</div>
</div>
</div>
</div>
</div>
<script>
function loadPreview(contentId) {
fetch(`/preview/${contentId}/`)
.then(response => response.json())
.then(data => {
document.getElementById('preview-content').innerHTML = data.html;
// Apply syntax highlighting
document.querySelectorAll('#preview-content pre code').forEach(function(block) {
hljs.highlightBlock(block);
});
// Show modal
new bootstrap.Modal(document.getElementById('previewModal')).show();
});
}
</script>
Template Blocks and Includes
Reusable Template Blocks
<!-- _martor_css.html -->
{% load static %}
<link href="{% static 'plugins/css/ace.min.css' %}" rel="stylesheet">
<link href="{% static 'plugins/css/highlight.min.css' %}" rel="stylesheet">
<link href="{% static 'martor/css/martor.bootstrap.min.css' %}" rel="stylesheet">
<!-- _martor_js.html -->
{% load static %}
<script src="{% static 'plugins/js/ace.js' %}"></script>
<script src="{% static 'plugins/js/mode-markdown.js' %}"></script>
<script src="{% static 'plugins/js/ext-language_tools.js' %}"></script>
<script src="{% static 'plugins/js/theme-github.js' %}"></script>
<script src="{% static 'plugins/js/highlight.min.js' %}"></script>
<script src="{% static 'plugins/js/emojis.min.js' %}"></script>
<script src="{% static 'martor/js/martor.bootstrap.min.js' %}"></script>
<!-- Usage in templates -->
{% block css %}
{% include "_martor_css.html" %}
{% endblock %}
{% block js %}
{% include "_martor_js.html" %}
{% endblock %}
Form Field Include
<!-- _martor_field.html -->
<div class="form-group">
<label for="{{ field.id_for_label }}" class="form-label">
{{ field.label }}
{% if field.field.required %}<span class="text-danger">*</span>{% endif %}
</label>
{{ field }}
{% if field.help_text %}
<div class="form-text">{{ field.help_text }}</div>
{% endif %}
{% if field.errors %}
<div class="text-danger">
{% for error in field.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
<!-- Usage -->
{% with field=form.content %}
{% include "_martor_field.html" %}
{% endwith %}
Template Performance
Optimization Techniques
<!-- Optimized template -->
{% extends "base.html" %}
{% load static %}
{% load martortags %}
{% block css %}
{% if form %}
<!-- Load editor CSS only for forms -->
{% include "_martor_css.html" %}
{% else %}
<!-- Load minimal CSS for display -->
<link href="{% static 'plugins/css/highlight.min.css' %}" rel="stylesheet">
{% endif %}
{% endblock %}
{% block js %}
{% if form %}
<!-- Load editor JS only for forms -->
{% include "_martor_js.html" %}
{% else %}
<!-- Load minimal JS for display -->
<script src="{% static 'plugins/js/highlight.min.js' %}"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('pre code').forEach(function(block) {
hljs.highlightBlock(block);
});
});
</script>
{% endif %}
{% endblock %}
Caching Rendered Content
{% load cache %}
{% load martortags %}
{% cache 3600 post_content post.id post.updated_at %}
<div class="martor-preview">
{{ post.content|safe_markdown }}
</div>
{% endcache %}
Best Practices
Separate CSS/JS for editing vs. display:
<!-- For editing -->
{% if form %}
{% include "_martor_editor_assets.html" %}
{% endif %}
<!-- For display -->
{% if content %}
{% include "_martor_display_assets.html" %}
{% endif %}
Use proper CSRF tokens:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
</form>
Handle errors gracefully:
{% if form.errors %}
<div class="alert alert-danger">
Please correct the errors below.
</div>
{% endif %}
Provide loading states:
<div id="editor-loading" class="text-center">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading editor...</span>
</div>
</div>
Next Steps
../customization - Advanced customization
Basic Examples - Complete template examples
../themes - Theming and styling
../security - Security considerations