Skip to content

A Django model field and widget that renders a customizable WYSIWYG/rich text editor

License

Notifications You must be signed in to change notification settings

jaap3/django-richtextfield

Folders and files

NameName
Last commit message
Last commit date

Latest commit

2515419 · Mar 7, 2024
Mar 7, 2024
Mar 7, 2024
Mar 7, 2024
Mar 7, 2024
Apr 14, 2021
Sep 25, 2018
Nov 5, 2018
Sep 15, 2014
Mar 7, 2024
Sep 11, 2014
Apr 14, 2021
Sep 15, 2014
Mar 7, 2024
May 20, 2020
Mar 7, 2024
Mar 7, 2024
Mar 7, 2024

Repository files navigation

Django Rich Text Field

Latest Version https://github.com/jaap3/django-richtextfield/actions/workflows/ci.yml/badge.svg?branch=main

A Django model field and widget that renders a customizable rich text/WYSIWYG widget.

Works in Django's admin interface and "normal" forms.

Supports global editor settings, reusable editor profiles and per field & widget settings. There's built-in support for pluggable server side content sanitizers.

Tested with TinyMCE and CKEditor. Designed to be easily extended to use other editors.

Quickstart

Install django-richtextfield and add it to your Django project's INSTALLED_APPS, django.contrib.admin must also be in INSTALLED_APPS:

INSTALLED_APPS = [
    'django.contrib.admin',
    ...
    'djrichtextfield'
]

Add the urls to the project's urlpatterns:

path('djrichtextfield/', include('djrichtextfield.urls'))

Configure django-richtextfield in settings.py:

DJRICHTEXTFIELD_CONFIG = {
    'js': ['//cdn.tiny.cloud/1/no-api-key/tinymce/5/tinymce.min.js'],
    'init_template': 'djrichtextfield/init/tinymce.js',
    'settings': {
        'menubar': False,
        'plugins': 'link image',
        'toolbar': 'bold italic | link image | removeformat',
        'width': 700
    }
}

Now you're ready to use the field in your models:

from djrichtextfield.models import RichTextField

class Post(models.Model):
    content = RichTextField()

or forms:

from djrichtextfield.widgets import RichTextWidget

class CommentForm(forms.ModelForm):
    content = forms.CharField(widget=RichTextWidget())

When using the editor outside of the admin make sure to include form.media in the <head> of the template:

<head>
  ...
  {{ form.media }}
  ...
</head>

Configuration

Define the DJRICHTEXTFIELD_CONFIG dictionary in your project settings. This dictionary can have the following keys:

Javascript souce(s)

'js'

A list of required javascript files. These can be URLs to a CDN or paths relative to your STATIC_URL e.g.:

'js': ['//cdn.ckeditor.com/4.14.0/standard/ckeditor.js']

or:

'js': ['path/to/editor.js', 'path/to/plugin.js']

CSS souce(s)

'css'

A dictionary of CSS files required. These can be URLs to a CDN or paths relative to your STATIC_URL e.g.:

'css': {
    'all': [
        'https://cdn.example.com/css/editor.css'
    ]
}

or:

'css': {'all': ['path/to/editor.css', 'path/to/plugin.css']}

Editor init template

'init_template'

Path to the init template for your editor. Currently django-richtextfield ships with two templates, either:

'init_template': 'djrichtextfield/init/tinymce.js'

or:

'init_template': 'djrichtextfield/init/ckeditor.js'

Editor settings

'settings'

A Python dictionary with the default configuration data for your editor e.g.:

'settings': {  # TinyMCE
    'menubar': False,
    'plugins': 'link image',
    'toolbar': 'bold italic | link image | removeformat',
    'width': 700
}

or:

'settings': {  # CKEditor
    'toolbar': [
        {'items': ['Format', '-', 'Bold', 'Italic', '-',
                   'RemoveFormat']},
        {'items': ['Link', 'Unlink', 'Image', 'Table']},
        {'items': ['Source']}
    ],
    'format_tags': 'p;h1;h2;h3',
    'width': 700
}

Editor profiles

'profiles'

This is an optional configuration key. Profiles are "named" custom settings used to configure specific type of fields. You can configure profiles like this:

'profiles': {
    'basic': {
        'toolbar': 'bold italic | removeformat'
    },
    'advanced': {
        'plugins': 'link image table code',
        'toolbar': 'formatselect | bold italic | removeformat |'
                   ' link unlink image table | code'
    }
}

Note

A profile is treated the same way as directly defined field & widget settings. This means that profile settings are merged with the defaults!

Content sanitizers

'sanitizer'

This is an optional configuration key. A sanitizer can be used to process submitted values before it is returned by the widget. By default no processing is performed on submitted values. You can configure a sanitizer either by providing a function or an importable path to a function, like so:

'sanitizer': lambda value: '<h1>Title</h1>' + value

or:

'sanitizer': 'bleach.clean'
'sanitizer_profiles'

This is an optional configuration key. It is possible to override the default or configured sanitizer for each of the configured profiles. For example to set a custom sanitizer for the advanced profile:

'sanitizer_profiles': {
    'advanced': lambda value: value + 'This text has been sanitized.'
}

Field & Widget settings

You can override the default settings per field:

class CommentForm(forms.ModelForm):
    content = forms.CharField(widget=RichTextWidget())
    content.widget.field_settings = {'your': 'custom', 'settings': True}

or:

class Post(models.Model):
    content = RichTextField(
        field_settings={'your': 'custom', 'settings': True},
        sanitizer='bleach.linkify'
    )

It's recommended to use profiles, they make it easier to switch configs or even editors on a later date. You use a profile like this:

class CommentForm(forms.ModelForm):
    content = forms.CharField(widget=RichTextWidget(field_settings='basic'))

or:

class Post(models.Model):
    content = RichTextField(field_settings='advanced')

Note

Fields always inherit the default settings, customs settings and profiles are merged with the defaults!

Custom init / Using another editor

It should be fairly easy to use this project with another editor. All that's required is to configure DJRICHTEXTFIELD_CONFIG to load the right Javascript/CSS files and to create a custom init template.

For example, to use jQuery based Summernote (lite) editor:

DJRICHTEXTFIELD_CONFIG = {
    'js': [
        '//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js',
        '//cdnjs.cloudflare.com/ajax/libs/summernote/0.8.9/summernote-lite.js',
    ],
    'css': {
        'all': [
            '//cdnjs.cloudflare.com/ajax/libs/summernote/0.8.9/summernote-lite.css',
        ]
    },
    'init_template': 'path/to/init/summernote.js',
    'settings': {
        'followingToolbar': False,
        'minHeight': 250,
        'width': 700,
        'toolbar': [
            ['style', ['bold', 'italic', 'clear']],
        ],
    }
}

Init template

The init template is a Django template (so it should be in the template and not in the static directory). It contains a tiny bit of Javascript that's called to initialize each editor. For example, the init template for Summernote would like this:

$('#' + id).summernote(settings)

The init template has the following Javascript variables available from the outer scope:

field
DOM node of the textarea to be replaced
id
The id attribute of the textarea
default_settings
DJRICHTEXTFIELD_CONFIG['settings'] as a JS object
custom_settings
The field_settings as a JS object
settings
Merge of default_settings and custom_settings

Handling uploads & other advanced features

django-richtextfield built to be editor agnostic. This means that it's up to you to handle file uploads, show content previews and support other "advanced" features.