| Version: | 0.3.0 |
|---|---|
| Source: | https://github.com/maykinmedia/django_prosemirror |
| Keywords: | Django, Prosemirror, rich-text, editor, document, JSON, WYSIWYG, content editor, text editor, markdown, html |
| PythonVersion: | 3.11+ |
Rich-text fields for Django using Prosemirror - a powerful, schema-driven rich text editor.
Contents
- Rich-text editing: Full-featured Prosemirror editor integration with Django (admin) forms
- Bidirectional conversion: Seamless conversion between HTML and ProseMirror document format
- Configurable schemas: Fine-grained control over allowed content (headings, links, images, tables, etc.)
- Native ProseMirror storage: Documents are stored in their native Prosemirror format, preserving structure and enabling programmatic manipulation without HTML parsing
- Python 3.11 or above
- Django 4.2 or newer
pip install maykin-django-prosemirrorAdd to your Django settings:
INSTALLED_APPS = [
# ... your other apps
'django_prosemirror',
]Use ProseMirrorModelField in your Django models:
from django.db import models
from django_prosemirror.fields import ProseMirrorModelField
from django_prosemirror.schema import NodeType, MarkType
class BlogPost(models.Model):
title = models.CharField(max_length=200)
# Full-featured rich text content (uses default configuration allowing all node
# and mark types)
content = ProseMirrorModelField()
# Limited schema - only headings and paragraphs with bold text
summary = ProseMirrorModelField(
allowed_node_types=[NodeType.PARAGRAPH, NodeType.HEADING],
allowed_mark_types=[MarkType.STRONG],
null=True,
blank=True
)
# Default document
content_with_prompt = ProseMirrorModelField(
default=lambda: {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [{"type": "text", "text": "Start writing..."}]
}
]
}
)The field provides both document and HTML representations:
post = BlogPost.objects.get(pk=1)
# Access as ProseMirror document (dict)
doc_content = post.content.doc
# Output: {
# "type": "doc",
# "content": [
# {
# "type": "heading",
# "attrs": {"level": 1},
# "content": [{"type": "text", "text": "Heading"}]
# },
# {
# "type": "paragraph",
# "content": [
# {"type": "text", "text": "Paragraph content..."}
# ]
# }
# ]
# }
# Access as HTML
html_content = post.content.html
# Output: "<h1>Heading</h1><p>Paragraph content...</p>"
# Modify content from HTML, which will be converted to a Prosemirror document internally
post.content.html = "<h2>New heading</h2><p>Updated content</p>"
post.save()
# After modification, the document structure is updated
updated_doc = post.content.doc
# Output: {
# "type": "doc",
# "content": [
# {
# "type": "heading",
# "attrs": {"level": 2},
# "content": [{"type": "text", "text": "New heading"}]
# },
# {
# "type": "paragraph",
# "content": [{"type": "text", "text": "Updated content"}]
# }
# ]
# }Use ProsemirrorFormField in Django forms:
from django import forms
from django_prosemirror.fields import ProsemirrorFormField
from django_prosemirror.schema import NodeType, MarkType
class BlogPostForm(forms.Form):
title = forms.CharField(max_length=200)
# Full-featured editor (uses default configuration)
content = ProsemirrorFormField()
# Limited to headings and paragraphs with basic formatting
summary = ProsemirrorFormField(
allowed_node_types=[NodeType.PARAGRAPH, NodeType.HEADING],
allowed_mark_types=[MarkType.STRONG, MarkType.ITALIC],
required=False
)Control exactly what content types are allowed using node and mark types:
Important
You must always include NodeType.PARAGRAPH in your allowed_node_types list.
The field will raise a ValueError if omitted.
from django_prosemirror.schema import NodeType, MarkType
# Available node types
NodeType.PARAGRAPH # Paragraphs (required)
NodeType.HEADING # Headings (h1-h6)
NodeType.BLOCKQUOTE # Quote blocks
NodeType.HORIZONTAL_RULE # Horizontal rules
NodeType.CODE_BLOCK # Code blocks
NodeType.FILER_IMAGE # Images (requires django-filer)
NodeType.HARD_BREAK # Line breaks
NodeType.BULLET_LIST # Bullet lists
NodeType.ORDERED_LIST # Numbered lists
NodeType.LIST_ITEM # List items
NodeType.TABLE # Tables
NodeType.TABLE_ROW # Table rows
NodeType.TABLE_CELL # Table data cells
NodeType.TABLE_HEADER # Table header cells
# Available mark types
MarkType.STRONG # Bold text
MarkType.ITALIC # Italic text (em)
MarkType.UNDERLINE # Underlined text
MarkType.STRIKETHROUGH # Strikethrough text
MarkType.CODE # Inline code
MarkType.LINK # Links
# Custom configurations
BASIC_FORMATTING = {
'allowed_node_types': [NodeType.PARAGRAPH, NodeType.HEADING],
'allowed_mark_types': [MarkType.STRONG, MarkType.ITALIC, MarkType.LINK],
}
BLOG_EDITOR = {
'allowed_node_types': [
NodeType.PARAGRAPH,
NodeType.HEADING,
NodeType.BLOCKQUOTE,
NodeType.IMAGE,
NodeType.BULLET_LIST,
NodeType.ORDERED_LIST,
NodeType.LIST_ITEM,
],
'allowed_mark_types': [
MarkType.STRONG,
MarkType.ITALIC,
MarkType.LINK,
MarkType.CODE,
],
}
TABLE_EDITOR = {
'allowed_node_types': [
NodeType.PARAGRAPH,
NodeType.HEADING,
NodeType.TABLE,
NodeType.TABLE_ROW,
NodeType.TABLE_CELL,
NodeType.TABLE_HEADER,
],
'allowed_mark_types': [MarkType.STRONG, MarkType.ITALIC],
}
# Use in fields
class DocumentModel(models.Model):
blog_content = ProseMirrorModelField(**BLOG_EDITOR)
table_content = ProseMirrorModelField(**TABLE_EDITOR)Always use callables for default values returning valid ProseMirror documents:
class Article(models.Model):
# ✅ Correct: Using a callable
content = ProseMirrorModelField(
default=lambda: {"type": "doc", "content": []}
)
# ❌ Wrong: Static dict (validation error)
# content = ProseMirrorModelField(
# default={"type": "doc", "content": []}
# )The field works automatically with Django admin:
from django.contrib import admin
from .models import BlogPost
@admin.register(BlogPost)
class BlogPostAdmin(admin.ModelAdmin):
fields = ['title', 'content', 'summary']
readonly_fields = ['summary'] # Read-only fields render as HTML
# Editable fields: Render the full ProseMirror rich-text editor
# Read-only fields: Render as formatted HTML outputRequired Assets: The ProseMirror form fields require both CSS and JavaScript assets to function. These assets are mandatory for any template that renders ProseMirror form fields - without them, the rich text editor will not work.
{% load django_prosemirror %}
<!DOCTYPE html>
<html>
<head>
{% include_django_prosemirror_css %}
{% include_django_prosemirror_js_defer %}
</head>
<body>
{{ form.as_p }}
</body>
</html>Note: These assets are only required for form rendering (editing). Displaying saved content using {{ post.content.html }} in templates does not require these assets.
Create ProseMirror content programmatically:
# Create a document with heading and paragraph
content = {
"type": "doc",
"content": [
{
"type": "heading",
"attrs": {"level": 1},
"content": [{"type": "text", "text": "My Heading"}]
},
{
"type": "paragraph",
"content": [
{"type": "text", "text": "Some "},
{
"type": "text",
"marks": [{"type": "strong"}],
"text": "bold"
},
{"type": "text", "text": " text."}
]
}
]
}
article = Article.objects.create(content=content)Requirements for development:
- Node.js (for building frontend assets)
- All runtime requirements listed above
Setup for development:
python -mvirtualenv .venv
source .venv/bin/activate
# Install Python package in development mode
pip install -e .[tests,coverage,docs,release]
# Install Node.js dependencies
npm install
# Build frontend assets (when making changes to JavaScript)
./build.shWhen running management commands via django-admin, make sure to add the root
directory to the python path (or use python -m django <command>):
export PYTHONPATH=. DJANGO_SETTINGS_MODULE=testapp.settings
django-admin migrate
django-admin createsuperuser # optional
django-admin runserver