django-prose-editor¶
Prose editor for the Django admin based on ProseMirror. Announcement blog post.
About rich text editors¶
Copied from the django-content-editor documentation.
We have been struggling with rich text editors for a long time. To be honest, I do not think it was a good idea to add that many features to the rich text editor. Resizing images uploaded into a rich text editor is a real pain, and what if you’d like to reuse these images or display them using a lightbox script or something similar? You have to resort to writing loads of JavaScript code which will only work on one browser. You cannot really filter the HTML code generated by the user to kick out ugly HTML code generated by copy-pasting from word. The user will upload 10mb JPEGs and resize them to 50x50 pixels in the rich text editor.
All of this convinced me that offering the user a rich text editor with too much capabilities is a really bad idea. The rich text editor in FeinCMS only has bold, italic, bullets, link and headlines activated (and the HTML code button, because that’s sort of inevitable – sometimes the rich text editor messes up and you cannot fix it other than going directly into the HTML code. Plus, if someone really knows what they are doing, I’d still like to give them the power to shot their own foot).
If this does not seem convincing you can always add your own rich text plugin with a different configuration (or just override the rich text editor initialization template in your own project). We do not want to force our world view on you, it’s just that we think that in this case, more choice has the bigger potential to hurt than to help.
Installation¶
The first step is to ensure that you have an activated virtualenv for your
current project, using something like . .venv/bin/activate
.
Install the package into your environment:
pip install django-prose-editor
To include nh3 as optional dependency for sanitized HTML, install the extra “sanitize”:
pip install django-prose-editor[sanitize]
Add django_prose_editor
to INSTALLED_APPS
:
INSTALLED_APPS = [
# ...
"django_prose_editor",
]
Replace models.TextField
with ProseEditorField
where appropriate:
from django_prose_editor.fields import ProseEditorField
class Project(models.Model):
description = ProseEditorField()
Note! No migrations will be generated when switching from and to
models.TextField
. That’s by design. Those migrations are mostly annoying.
Security¶
ProseMirror does a really good job of only allowing content which confirms to a
particular scheme. Of course users can submit what they want, they are not
constrainted by the HTML widgets you’re using. You should still always sanitize
the HTML submitted on the server side. A good way to do this is by using the
sanitize
argument to the ProseEditorField
. You can use the following
snippet to always pass HTML through nh3:
from django_prose_editor.sanitized import SanitizedProseEditorField
description = SanitizedProseEditorField()
Install django-prose-editor with the extra “sanitize” to use
SanitizedProseEditorField
.
Convenience¶
Sometimes it may be useful to show an excerpt of the HTML field; the
ProseEditorField
automatically adds a get_*_excerpt
method to models
which returns the truncated and stripped beginning of your HTML field’s
content. The name would be Project.get_description_excerpt
in the example
above.
Customization¶
It’s possible to slightly customize the field or widget by passing an optional
config
dictionary. The default configuration is:
config = {
"types": None, # Allow all nodes and marks
"history": True, # Enable undo and redo
"html": True, # Add a button which allows editing the raw HTML
"typographic": True, # Highlight typographic characters
"headingLevels": [1, 2, 3, 4, 5], # Available heading levels
}
If you only want to support paragraphs, strong, emphasis, sub- and superset and no history or HTML editing you could add the following field:
text = SanitizedProseEditorField(
config={"types": ["strong", "em", "sub", "sup"]},
)
Paragraphs cannot be removed at the moment. Note that the backend doesn’t sanitize the content to ensure that the HTML doesn’t contain only the provided tags, that’s out of scope for now.
doc
, paragraph
and text
are always in the allowlist.
The supported node types are heading
, blockquote
, horizontal_rule
and hard_break
. List nodes are ordered_list
, bullet_list
and
list_item
.
The supported mark types are link
, strong
, em
, underline
,
strikethrough
, sub
and sup
Usage outside the Django admin¶
The prose editor can easily be used outside the Django admin. The form field respectively the widget includes the necessary CSS and JavaScript:
from django_prose_editor.fields import ProseEditorFormField
class Form(forms.Form):
text = ProseEditorFormField()
Or maybe you want to use django_prose_editor.widgets.ProseEditorWidget
, but
why make it more complicated than necessary.
If you’re rendering the form in a template you have to include the form media:
<form method="post">
{{ form.errors }} {# Always makes sense #}
{{ form.media }} {# This is the important line! #}
{{ form.as_div }}
<button type="submit">send</button>
</form>
Note that the form media isn’t django-prose-editor specific, that’s a Django feature.
The django-prose-editor CSS uses the following CSS custom properties.
--prose-editor-background
--prose-editor-foreground
--prose-editor-border-color
--prose-editor-active-color
--prose-editor-disabled-color
If you do not set them, they get their value from the following properties that are defined in the Django admin’s CSS:
--border-color
--body-fg
--body-bg
--primary
You should set these properties with appropriate values to use django-prose-editor outside the admin in your site.
In addition, you may optionally set a --prose-editor-typographic
property
to control the color of typographic characters when shown.
Change log¶
Next version¶
Updated the ProseMirror dependencies.
0.8 (2024-08-26)¶
Made the link button only active when the cursor is inside a link.
Added docs on read the docs.
Updated the ProseMirror dependencies.
Added extremely hacky german translations for the dialogs.
Added Django 5.1 to the CI matrix.
Allowed specifying the heading levels for the menu. The schema itself supports all heading levels (1-6) as before.
0.7 (2024-08-02)¶
Added the
django-prose-editor[sanitize]
which automatically installs thenh3
dependency. Thanks @plenaerts!Properly restored the textarea element when destroying the editor.
Added more unittesting.
Supported using the
ProseEditorFormField
with widget instances, not just with widget classes.Documented the CSS custom properties. Thanks @carltongibson!
Converted the block type dropdown back to a button group.
Changed the CSS so that block type buttons look active instead of disabled when in a block of the respective type.
Stopped showing the ‘remove link’ button as active when inside a link – it’s not active, just enabled.
Improved the styles of the dialog a bit.
0.6 (2024-07-26)¶
Added support for highlighting soft hyphens.
Updated all dependencies.
Moved the Django administration interface CSS overrides into their own file, and only load them if necessary so that using the editor outside the admin requires using less
!important
overrides.
0.5 (2024-07-08)¶
Updated all dependencies.
Stopped putting anything into the global scope in
init.js
.Added support for showing typographic characters.
Changed the editor initialization to make the initial
textarea
a child of the.prose-editor
div, and changed the CSS todisplay: none !important;
so that thetextarea
is only shown in exceptional circumstances, when people really really want it.
0.4 (2024-05-26)¶
Allowed installing the package in Python 3.10 environments too.
Tweaked the cleaning methods of
ProseEditorField
andSanitizedProseEditorField
to produce empty strings when no content is entered. Previously they would produce an empty paragraph (<p></p>
) since our ProseMirror schema says that there exists always one or more block nodes.Stopped setting a black color on the
.ProseMirror
class by default.Dropped the dependency on
admin/js/jquery.init.js
. We’re using our own DOM-ready handler and therefore can still accessdjango.jQuery
to hook up the inline events handler if running inside the Django admin.Moved the paragraph formats into a popover.
0.3 (2024-04-09)¶
Made the editor usable in dark mode.
Changed the cancel buttons in dialogs to not validate the form.
Switched the
SanitizedProseEditorField
from html-sanitizer (which at the moment uses the problematic lxml HTML cleaner under the hood) with nh3. html-sanitizer is still a good choice but since we build on ProseMirror we only require a sanitizer, we don’t have to clean up strange HTML.Added customization options to the fields and widgets.
0.2 (2024-03-12)¶
Extended the README.
Fixed the initialization in Django admin inlines.
Added a server-side sanitization callback to the
ProseEditorField
, and addeddjango_prose_editor.sanitized.SanitizedProseEditorField
which automatically does the right thing.Automatically added a
get_*_excerpt
model method to models using theProseEditorField
as a convenience.Cleaned up the styles.
Added a maximum width to the editor.
Started hiding labels for prose editor fields in the Django admin if the label is an empty string. This looks better to me.
Added a shortcut for adding links.
Added a button for editing the raw HTML. This is sometimes useful.
Stopped generating source maps unless in dev mode. I like source maps a lot in general, but the files are really big in this case.
Added a button to the menu to insert horizontal rules.
Added material icons for the format bar.
Added client side validation to dialogs.
Upgraded esbuild.
0.1 (2024-03-11)¶
Initial public release.