django-rmeditor
A lightweight, self-hosted rich text editor for Django. It outputs plain HTML,
so it is a drop-in replacement for a TextField / tinymce HTMLField with no data
migration. No CDN, no API key, no branding, no word/usage limits.
- Pure vanilla JS (no jQuery, no build step).
- Auto-enhances any
<textarea class="rmeditor">. - Existing content is loaded as-is (HTML in / HTML out).
- Configurable toolbar per field.
- Paste cleaning (strips Word/Office junk) + basic XSS sanitize.
- Works with Django forms, the admin, or hand-written templates.
Install
pip install django-rmeditor
# settings.py
INSTALLED_APPS = [
...,
"rmeditor",
]
Make sure django.contrib.staticfiles is set up (it is by default) and run
collectstatic in production.
Usage
1. Hand-written templates (no form/widget)
{% load rmeditor %}
{% rmeditor_media %} {# include once per page, e.g. in your base template #}
<form method="post">
{% csrf_token %}
<textarea name="body" class="rmeditor"
data-tools="format fontsize | bold italic underline | bullist numlist | link image | removeformat">
{{ object.body }}
</textarea>
<button type="submit">Save</button>
</form>
The textarea stays in the form (hidden) and always holds the current HTML, so your
existing view code that reads request.POST["body"] keeps working unchanged.
Autochange — enhance every textarea (no class needed)
To convert all textareas on a page into editors without touching each one
(like tinymce's selector:'textarea'), enable autochange:
{% load rmeditor %}
{% rmeditor_media auto="textarea" %} {# every <textarea> becomes an editor #}
or from JS:
<script src="/static/rmeditor/js/rmeditor.js"></script>
<script>RMEditor.auto("textarea");</script> <!-- or any CSS selector -->
or globally before the script loads:
<script>window.RMEDITOR_AUTO = "textarea.rich";</script>
Opt a textarea out of autochange with class="no-rmeditor" or data-rmeditor="off".
This makes replacing tinymce a one-line change: swap the tinymce script/init for
{% rmeditor_media auto="textarea" %}.
2. Model field
from django.db import models
from rmeditor.fields import RichTextField
class Article(models.Model):
body = RichTextField(blank=True, default="")
RichTextField is a TextField subclass — same column, no data difference. Switching
an existing TextField/HTMLField to it needs only a no-op migration.
3. Form widget
from django import forms
from rmeditor.widgets import RichTextWidget
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ["body"]
widgets = {"body": RichTextWidget(tools="bold italic link")}
Toolbar tokens
format (paragraph/H1/H2/H3 dropdown), fontsize (px-based dropdown), bold, italic,
underline, strike, forecolor, bullist, numlist, indent, outdent, alignleft,
aligncenter, alignright, justify, link, unlink, image, table, rowadd,
rowdel, coladd, coldel, removeformat, source (view HTML), undo, redo.
Table tokens: table inserts a table (prompts for rows,columns); rowadd/rowdel
add/delete a row and coladd/coldel add/delete a column relative to the cell the
caret is in.
Use | for a separator. Set per field with data-tools="..." or on the widget with
RichTextWidget(tools="...").
JavaScript API
RMEditor.get(el) // instance for a textarea node or its id ("" if none)
RMEditor.getHTML(el) // current HTML
RMEditor.setHTML(el, html) // replace content
RMEditor.getText(el) // plain text, trimmed
RMEditor.syncAll() // flush every editor into its textarea
el may be the textarea DOM node or its id string.
Migrating from tinymce
- Replace the tinymce script/
tinymce.init(...)with{% rmeditor_media %}and addclass="rmeditor"to the textareas. tinymce.get("x").setContent(html)→RMEditor.setHTML("x", html)tinymce.get("x").getContent({format:"text"})→RMEditor.getText("x")
Storage stays HTML, so rendered pages ({{ field|safe }}), PDFs, and API/mobile
consumers are unaffected.
Notes / limits (v0.1)
- Uses
document.execCommand(deprecated but supported in all current browsers). - Tables support insert plus add/remove row and column. Cell merging/splitting and media (video/embed) editing are out of scope; existing such content is preserved and still renders.
- Client-side sanitize is a basic guard. For untrusted authors, also sanitize on the
server (e.g.
bleach) before rendering with|safe.
License
MIT
Release files for django-rmeditor 0.1.8
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_rmeditor-0.1.8.tar.gz | 17.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_rmeditor-0.1.8-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 34.7 kB
Release files / django_rmeditor-0.1.8.tar.gz
| Download URL | django_rmeditor-0.1.8.tar.gz |
|---|---|
| Size | 17.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
89d4967553e07c66304f449663aeac9a0632fdcfef6ea4b876b76d8147a3876a
|
|
BLAKE2b-256 checksum How to use checksums |
f626d66f58d7263eb797337cdf26ed566c7f1770242a546d4ad54c69d3e7ab3a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 25, 2026.
Transparency logRelease files / django_rmeditor-0.1.8-py3-none-any.whl
| Download URL | django_rmeditor-0.1.8-py3-none-any.whl |
|---|---|
| Size | 17.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
88b98f853c762a2f6c0391fd4dff5a0dd89bc1ceaf0f5a178fd8df322e986d3e
|
|
BLAKE2b-256 checksum How to use checksums |
858793cbee7bd0951fba3e24d528249967c970fbd2d116d440b6c31f942644b5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 25, 2026.
Transparency log