Rich Text Plugin for django CMS
Project description
djangocms-text is a flexible and extensible rich text editing solution for Django CMS. This package is designed as a replacement for djangocms-text-ckeditor, introducing a swappable rich text editor interface and supporting enhanced data storage in both HTML and JSON formats.
Features
Swappable editors — switch between TipTap (default), CKEditor 4, and CKEditor 5 frontends.
HTML and JSON storage — store content in either format, depending on your use case.
Inline editing — click a text plugin to edit it directly in edit mode.
Text-enabled plugins — embed CMS plugins inline within rich text.
Drop-in for djangocms-text-ckeditor — automatic data migration on install.
Installation
Install: pip install djangocms-text
Add to INSTALLED_APPS:
INSTALLED_APPS = [..., "djangocms_text", ...]
Run migrations: python manage.py migrate djangocms_text
Start your server and add a Text plugin.
The default editor is TipTap and is included in the base package. To use a different editor, install its frontend package and set TEXT_EDITOR:
INSTALLED_APPS = [..., "djangocms_text.contrib.text_ckeditor4", ...]
TEXT_EDITOR = "djangocms_text.contrib.text_ckeditor4.ckeditor4"
Editors
djangocms-text ships with several editor frontends that can be swapped via the TEXT_EDITOR setting:
TipTap (default) — A modern, modular rich text editor. Supports text-enabled plugins, dynamic links, inline editing, and on-the-fly Markdown conversion. Does not allow direct HTML source editing.
CKEditor 4 — Compatible with djangocms-text-ckeditor and usable as a drop-in replacement. Supports inline editing and text-enabled plugins.
CKEditor 5 — Available as a separate package (djangocms-text-ckeditor5) to keep licenses separated. Fully supports text-enabled CMS plugins and dynamic links.
Configuration
Selecting an editor
Add the editor’s package to INSTALLED_APPS and point TEXT_EDITOR at its RTEConfig path:
INSTALLED_APPS = [
...,
"djangocms_text.contrib.text_ckeditor4",
...,
]
TEXT_EDITOR = "djangocms_text.contrib.text_ckeditor4.ckeditor4"
Global editor configuration
The TEXT_EDITOR setting points to a RTEConfig object. The following attributes are available:
name (str) — The name of the RTE configuration.
config (str) — The configuration string.
js (Iterable[str]) — JavaScript files to include.
css (dict) — CSS files to include.
admin_css (Iterable[str]) — CSS files for the admin interface only.
inline_editing (bool) — Whether to enable inline editing.
child_plugin_support (bool) — Whether to support child plugins.
configuration (dict) — Frontend-specific options.
additional_context (dict) — Additional context to pass to the editor.
The default configuration is:
DEFAULT_EDITOR = RTEConfig(
name="tiptap",
config="TIPTAP",
js=("djangocms_text/bundles/bundle.tiptap.min.js",),
css={"all": ("djangocms_text/css/bundle.tiptap.min.css",)},
admin_css=("djangocms_text/css/tiptap.admin.css",),
inline_editing=True,
child_plugin_support=True,
configuration={}, # see below
)
Use admin_css to include CSS files loaded into the dialog window, e.g. to declare custom colors or other styles.
Frontend-specific configuration
Frontend-specific options live in the configuration property of the RTEConfig. The contents depend on the rich text editor frontend (TipTap, CKEditor 4, etc.).
The preferred way to set them is via TEXT_EDITOR_SETTINGS, which mirrors RTEConfig.configuration. For backwards compatibility with djangocms-text-ckeditor, CKEDITOR_SETTINGS is also forwarded to the frontend (even when the frontend is not CKEditor 4).
Example TipTap configuration (matches the defaults):
DEFAULT_EDITOR.configuration = {
"inlineStyles": [ # Styles menu, by default contains some rarer styles
{ "name": 'Small', "element": 'small' },
{ "name": 'Kbd', "element": 'kbd' },
{ "name": 'Var', "element": 'var' },
{ "name": 'Samp', "element": 'samp' },
],
"blockStyles": [],
# Block styles menu, e.g., for paragraphs; empty by default.
# Example: [{"name": "Lead", "element": "div", "attributes": {"class": "lead"}}]
"textColors": { # Colors offered for the text color menu - the keys are CSS classes
'text-primary': {"name": "Primary"},
'text-secondary': {"name": "Secondary"},
'text-success': {"name": "Success"},
'text-danger': {"name": "Danger"},
'text-warning': {"name": "Warning"},
'text-info': {"name": "Info"},
'text-light': {"name": "Light"},
'text-dark': {"name": "Dark"},
'text-body': {"name": "Body"},
'text-muted': {"name": "Muted"},
},
"tableClasses": "table", # classes added to new tables
}
Three ways to configure the classes added to new tables:
# Option 1: modify the default editor configuration in place
from djangocms_text.editors import DEFAULT_EDITOR
DEFAULT_EDITOR.configuration["tableClasses"] = "table ui"
# Option 2: offer a list of named choices
from djangocms_text.editors import DEFAULT_EDITOR
DEFAULT_EDITOR.configuration["tableClasses"] = [
["table", _("Default")],
["table table-striped", _("Striped")],
]
# Option 3: use TEXT_EDITOR_SETTINGS in settings.py
TEXT_EDITOR_SETTINGS = {
"tableClasses": "table ui",
}
Inline editing
Inline editing lets editors click a text plugin and change its contents directly in django CMS edit mode. The editor appears around the text field, and changes are saved as soon as the field loses focus.
Inline editing wraps the HTML in a <div> in edit mode, which may interact with site CSS that uses direct child selectors.
Inline editing is enabled by default. To disable it:
TEXT_INLINE_EDITING = False
When enabled, a toolbar toggle lets users switch inline editing on and off for the current session. If only text changes, editing continues seamlessly. If a text-enabled plugin was added, changed, or removed, the page refreshes to update the page tree and re-render the affected plugins.
Custom plugin templates
If you override the default cms/plugins/text.html and wrap the plugin output in container elements (e.g. <section>, <article>, <div>), the innermost container must have the class cms-content-start:
<!-- cms/plugins/text.html -->
<section class="my-text-plugin">
<div class="cms-content-start">
{{ body|safe }}
</div>
</section>
This tells the inline editor which element to use as the editable area. Without it, the editor will treat the outermost container as the editable region, which may include non-editable markup.
Text-enabled plugins
djangocms-text supports text-enabled plugins (note: not all editor frontends do — see Editors).
To make a plugin available inside Text plugins, set text_enabled = True on its plugin class:
class MyTextPlugin(TextPlugin):
name = "My text plugin"
model = MyTextModel
text_enabled = True
The plugin then appears in the CMS Plugins dropdown (puzzle icon) in the editor and is previewed inline.
Pro-tip: Provide an icon_alt method on the plugin so that, when many text_enabled plugins are available, users get a useful tooltip — for example, the name of the product whose price the plugin shows.
Text-enabled plugins can also have their own icons. Add a text_icon property containing SVG source code:
class MyTextPlugin(TextPlugin):
name = "My text plugin"
model = MyTextModel
text_enabled = True
text_icon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z"/></svg>'
The icon shows in the CMS plugin pulldown menu and toolbar. To make a text-enabled plugin directly accessible from the editor toolbar, add its name (e.g. "LinkPlugin") to the toolbar configuration.
For more on extending the CMS with plugins, see the django-cms doc.
Default content in placeholders
You can use TextPlugin in default_plugins (see the CMS_PLACEHOLDER_CONF setting docs). TextPlugin requires a single value: body for the default HTML content. To attach default children (e.g. a LinkPlugin), reference them in the body using "%(_tag_child_<order>)s" placeholders:
CMS_PLACEHOLDER_CONF = {
'content': {
'name' : _('Content'),
'plugins': ['TextPlugin', 'LinkPlugin'],
'default_plugins':[
{
'plugin_type':'TextPlugin',
'values':{
'body':'<p>Great websites : %(_tag_child_1)s and %(_tag_child_2)s</p>'
},
'children':[
{
'plugin_type':'LinkPlugin',
'values':{
'name':'django',
'url':'https://www.djangoproject.com/'
},
},
{
'plugin_type':'LinkPlugin',
'values':{
'name':'django-cms',
'url':'https://www.django-cms.org'
},
},
]
},
]
}
}
HTML sanitizer
djangocms-text uses nh3 to sanitize HTML, both for security and to enforce well-formed markup. Sanitization may strip tags useful for some use cases (e.g. iframe). Customize allowed tags and attributes via TEXT_ADDITIONAL_ATTRIBUTES:
TEXT_ADDITIONAL_ATTRIBUTES = {
'iframe': {'scrolling', 'allowfullscreen', 'frameborder'},
}
The dictionary maps tag names to sets of allowed attribute names.
If you have settings in the older djangocms-text-ckeditor style using both TEXT_ADDITIONAL_TAGS and TEXT_ADDITIONAL_ATTRIBUTES, those will be translated automatically — with a warning from the Django checks framework at server startup.
Note: Some frontend editors pre-sanitize content before sending it to the server, which can render the above settings ineffective.
To disable sanitization entirely, set TEXT_HTML_SANITIZE = False.
Markdown support
The TipTap frontend includes (minimal) Markdown support:
Markdown is converted to HTML when pasting. (To prevent XSS attacks, pasted content is not converted if it contains JavaScript.)
When typing, Markdown syntax is converted on the fly.
Supported syntax:
Headings: # Heading 1, ## Heading 2, ### Heading 3, etc.
Bold: **bold text** or __bold text__
Italic: *italic text* or _italic text_
Strikethrough: ~~strikethrough~~
Links: [link text](http://example.com)
Lists: - Item or * Item for unordered, 1. Item for ordered.
Blockquotes: > Quote
Code: `inline code` for inline code, triple backticks for code blocks.
Tables (pasting only): use | to separate columns:
| Header 1 | Header 2 | |----------|----------| | Row 1 | Row 2 |
Horizontal rules: ---
Optional contrib extensions
Three small contrib packages ship with djangocms-text as demos of how to extend the TipTap editor dynamically — at runtime, through the window.CMS_Editor.tiptap registry, without forking djangocms-text or pulling a second copy of TipTap into the page. The full registry contract is documented in tiptap-extensions.md.
Each is opt-in: add the relevant app to INSTALLED_APPS. The READMEs linked below cover what each package adds, the configuration knobs it exposes, and the integration pattern it illustrates.
djangocms_text.contrib.filer_image — insert django-filer images via filer’s existing picker popup.
djangocms_text.contrib.officepaste — strip Word/Outlook detritus on paste.
Migrating from djangocms-text-ckeditor
djangocms-text’s migrations automatically migrate existing text plugins from djangocms-text-ckeditor and clean up old tables. To migrate:
Uninstall djangocms-text-ckeditor.
Remove djangocms_text_ckeditor from INSTALLED_APPS.
Add djangocms_text to INSTALLED_APPS (see Installation).
Run python -m manage migrate djangocms_text.
Attention: The migration also deletes djangocms-text-ckeditor’s tables from the database (to avoid referential integrity issues). Make a backup beforehand to be safe.
Switching from CKEditor 4 to TipTap
When transitioning from CKEditor 4 to TipTap (the new default), keep these points in mind:
No HTML source editing — TipTap does not support direct HTML source editing. This simplifies the editor for most users but may be a drawback for advanced users or developers who manually edit HTML.
Loss of non-standard formatting — TipTap stores content in an abstract JSON format and regenerates HTML on edit. Formatting created via CKEditor 4 plugins or manually added HTML classes may not be preserved. This only happens on first edit after migration.
Keep using CKEditor 4 if needed — If retaining CKEditor 4 behavior is essential, use the CKEditor 4 backend that ships with djangocms-text.
You can continue to use CKEditor 4. Compared to djangocms-text-ckeditor, the CKEditor 4 sources have moved to static/djangocms_text/vendor/ckeditor4. Update any custom CKEditor 4 plugins accordingly.
Usage outside django CMS
django CMS Text can be used without django CMS installed. It provides HTMLField, HTMLFormField, and the TextEditorWidget class for use with any Django model or form.
When django CMS is not installed alongside django CMS Text, add this to your MIGRATION_MODULES setting to skip creation of the CMS plugin model:
MIGRATION_MODULES = {
...,
"djangocms_text": None,
...,
}
Contributing
Contributions to djangocms-text are welcome! See the contributing guidelines to get started.
pre-commit hooks
The repo uses pre-commit git hooks to ensure code quality. Install with:
pip install pre-commit pre-commit install
Building the JavaScript
djangocms-text distributes a JavaScript bundle containing the editor frontends and CMS integration. To rebuild the bundle:
nvm use npm install npm run build
Other build scripts:
npm run build:dev — unminified development build
npm run watch — rebuild on file changes
Acknowledgments
Special thanks to the django CMS community and to all contributors to the djangocms-text-ckeditor project.
License
This project is licensed under the BSD-3-Clause License — see the LICENSE file for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file djangocms_text-0.9.7.tar.gz.
File metadata
- Download URL: djangocms_text-0.9.7.tar.gz
- Upload date:
- Size: 2.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
192d98ee0f4612a4a483c848fb628c49018295cf3679c01943536ddf9dd65160
|
|
| MD5 |
7fca9af9e6d7538468d8f0de3cb6bac8
|
|
| BLAKE2b-256 |
d4ea3897dd097ec660dc79f38a84bd9d8f894f856ebd4f9a2338e51da46b9ca2
|
Provenance
The following attestation bundles were made for djangocms_text-0.9.7.tar.gz:
Publisher:
publish-to-live-pypi.yml on django-cms/djangocms-text
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
djangocms_text-0.9.7.tar.gz -
Subject digest:
192d98ee0f4612a4a483c848fb628c49018295cf3679c01943536ddf9dd65160 - Sigstore transparency entry: 1458626763
- Sigstore integration time:
-
Permalink:
django-cms/djangocms-text@9a5f20f362c60409236a0fc7d0a959d5ee6f30c3 -
Branch / Tag:
refs/tags/0.9.7 - Owner: https://github.com/django-cms
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-live-pypi.yml@9a5f20f362c60409236a0fc7d0a959d5ee6f30c3 -
Trigger Event:
release
-
Statement type:
File details
Details for the file djangocms_text-0.9.7-py3-none-any.whl.
File metadata
- Download URL: djangocms_text-0.9.7-py3-none-any.whl
- Upload date:
- Size: 2.6 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
00a30df612c152049222d8574fe347d3f8de9460f6ee225f3d5b758a4c254291
|
|
| MD5 |
10f98f8349fddb6ea0ceb674796a66e7
|
|
| BLAKE2b-256 |
552b0e8e812be8c73ac3d87c492857bbc8a79fd3505b401e1969cae27b26a139
|
Provenance
The following attestation bundles were made for djangocms_text-0.9.7-py3-none-any.whl:
Publisher:
publish-to-live-pypi.yml on django-cms/djangocms-text
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
djangocms_text-0.9.7-py3-none-any.whl -
Subject digest:
00a30df612c152049222d8574fe347d3f8de9460f6ee225f3d5b758a4c254291 - Sigstore transparency entry: 1458626944
- Sigstore integration time:
-
Permalink:
django-cms/djangocms-text@9a5f20f362c60409236a0fc7d0a959d5ee6f30c3 -
Branch / Tag:
refs/tags/0.9.7 - Owner: https://github.com/django-cms
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-live-pypi.yml@9a5f20f362c60409236a0fc7d0a959d5ee6f30c3 -
Trigger Event:
release
-
Statement type: