Skip to main content

A GitHub-style markdown editor widget for Django forms

Project description

django-markdown-widget

A GitHub-style markdown editor widget for Django forms and admin.

PyPI version Python versions Django versions License: MIT codecov Docs

Documentation | PyPI | GitHub

Screenshots

Dark (default) Light
Write Light
Split View Preview
Split Preview

Features

  • Three view modes -- Write, Split (side-by-side live preview), Preview
  • Syntax highlighting for code blocks via highlight.js (GitHub themes)
  • Rich toolbar -- text formatting, headings, lists, code, tables, media, embeds
  • Image, video, and document uploads with drag & drop support
  • Embed support -- paste YouTube, Vimeo, CodePen URLs or raw <iframe> code
  • Autosave -- drafts saved to browser localStorage, survives page reloads
  • Draggable split divider -- resize editor/preview panels
  • Temp upload system -- files upload to temp storage, finalize on form submit
  • Light, dark, and auto theme support
  • Django admin integration via one-line mixin
  • Template tag and filter for server-side markdown rendering
  • Pluggable renderer and upload handler architecture
  • Media cleanup -- automatic orphaned file deletion + management command
  • Custom undo/redo history stack (100 states)
  • HTML sanitization on both client and server side
  • Keyboard shortcuts -- Ctrl+B, Ctrl+I, Ctrl+K, Ctrl+Z, etc.
  • Zero hard dependencies beyond Django

Installation

pip install django-markdown-widget

Add to your INSTALLED_APPS and include the URLs:

# settings.py
INSTALLED_APPS = [
    ...
    "django_markdown_widget",
]

# urls.py
from django.urls import include, path

urlpatterns = [
    ...
    path("md-editor/", include("django_markdown_widget.urls")),
]

For server-side rendering (template tag/filter), install a markdown library:

pip install markdown

Quick Start

Form Widget

from django.forms import ModelForm
from django_markdown_widget import MarkdownEditorWidget

class PostForm(ModelForm):
    class Meta:
        model = Post
        fields = ["title", "content"]
        widgets = {
            "content": MarkdownEditorWidget(),
        }

Django Admin

from django.contrib import admin
from django_markdown_widget import MarkdownEditorAdminMixin

@admin.register(Post)
class PostAdmin(MarkdownEditorAdminMixin, admin.ModelAdmin):
    list_display = ["title", "created_at"]
    markdown_fields = ["content"]  # omit to apply to all TextFields

Template Rendering

{% load markdown_widget %}

{# As a filter (recommended) #}
{{ post.content|markdown }}

{# As a tag #}
{% markdown post.content %}

Media Cleanup

from django_markdown_widget import MarkdownCleanupMixin

class Post(MarkdownCleanupMixin, models.Model):
    content = models.TextField()
    # markdown_cleanup_fields = ["content"]  # optional: limit to specific fields

Enable in settings:

MD_EDITOR = {
    "CLEANUP_MEDIA": True,
}

Bulk cleanup via management command:

python manage.py cleanup_markdown_media --dry-run
python manage.py cleanup_markdown_media

Toolbar

Default toolbar layout, grouped by purpose:

Group Buttons
Text formatting Bold, Italic, Strikethrough, Highlight
Structure Heading, Quote, Horizontal rule
Code Inline code, Code block
Lists Bullet, Numbered, Task list
Links & media Link, Image, Video, Document
Rich blocks Table, Collapsible section, Embed
Extras Superscript, Subscript
Actions Undo, Redo

Fullscreen toggle and autosave switch are in the top-right tabs bar.

Configuration

All settings are optional and go under MD_EDITOR in your Django settings:

MD_EDITOR = {
    # Pluggable backend classes
    "RENDERER_CLASS": "django_markdown_widget.renderers.DefaultRenderer",
    "UPLOAD_HANDLER_CLASS": "django_markdown_widget.uploads.DefaultUploadHandler",

    # Toolbar buttons (grouped by purpose)
    "TOOLBAR": [
        "bold", "italic", "strikethrough", "highlight", "separator",
        "heading", "quote", "horizontal-rule", "separator",
        "code", "code-block", "separator",
        "unordered-list", "ordered-list", "task-list", "separator",
        "link", "image", "video", "document", "separator",
        "table", "details", "embed", "separator",
        "superscript", "subscript", "separator",
        "undo", "redo",
    ],

    # Upload settings
    "ALLOWED_UPLOAD_TYPES": [
        "image/png", "image/jpeg", "image/gif", "image/webp",
        "video/mp4", "video/webm", "video/ogg",
        "application/pdf", "application/zip", "text/plain", "text/csv",
        "application/json",
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        "application/vnd.openxmlformats-officedocument.presentationml.presentation",
    ],
    "MAX_UPLOAD_SIZE": 50 * 1024 * 1024,  # 50 MB
    "UPLOAD_PATH": "md-editor/uploads/%Y/%m/",
    "TEMP_UPLOAD_PATH": "md-editor/tmp/",
    "TEMP_MAX_AGE": 86400,  # 24 hours

    # Editor defaults
    "DEFAULT_HEIGHT": "300px",
    "PLACEHOLDER": "Add your comment here...",
    "THEME": "auto",  # "light", "dark", or "auto"

    # Security
    "REQUIRE_AUTH": True,

    # Media cleanup
    "CLEANUP_MEDIA": False,
}

Custom Renderer

from django_markdown_widget import BaseRenderer

class MyRenderer(BaseRenderer):
    def render(self, markdown_text: str) -> str:
        import markdown_it
        md = markdown_it.MarkdownIt()
        return md.render(markdown_text)
MD_EDITOR = {
    "RENDERER_CLASS": "myapp.renderers.MyRenderer",
}

Custom Upload Handler

from django_markdown_widget import BaseUploadHandler

class S3UploadHandler(BaseUploadHandler):
    def validate(self, file):
        # your validation logic
        pass

    def save(self, file) -> str:
        # save to S3 and return the URL
        return url
MD_EDITOR = {
    "UPLOAD_HANDLER_CLASS": "myapp.uploads.S3UploadHandler",
}

Development

git clone https://github.com/ganiyevuz/django-md-editor.git
cd django-md-editor
uv sync --group dev
make test
make lint

Run the example app:

cd example
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver

License

MIT License. See LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_markdown_widget-2.0.0.tar.gz (1.2 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_markdown_widget-2.0.0-py3-none-any.whl (126.8 kB view details)

Uploaded Python 3

File details

Details for the file django_markdown_widget-2.0.0.tar.gz.

File metadata

  • Download URL: django_markdown_widget-2.0.0.tar.gz
  • Upload date:
  • Size: 1.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for django_markdown_widget-2.0.0.tar.gz
Algorithm Hash digest
SHA256 d38aab4fcfb2e402afd0e03e39b602a80d921792874c5fbe988c74ebf55852ab
MD5 6c7dff9498af807f21508ad9a871a82f
BLAKE2b-256 79769b7e95e9382670b37ba25b172b9c1dbcb58abfbaf918839bb8255cdbe1e6

See more details on using hashes here.

File details

Details for the file django_markdown_widget-2.0.0-py3-none-any.whl.

File metadata

  • Download URL: django_markdown_widget-2.0.0-py3-none-any.whl
  • Upload date:
  • Size: 126.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for django_markdown_widget-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b51065ba3cbfeab416b9728c9fd964cd83346b1db2769c4fa524d51753e2e004
MD5 9401f2926d75e01cf01a62ceea37705c
BLAKE2b-256 acb9f545fdf733697c7918d42f68c4c85f9cd08a66a5fa74248deaa8a39f3c43

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page