========== Quickstart ========== Example: articles with rich text plugins ========================================= First comes a models file which defines a simple article model with support for adding rich text and download content blocks. ``app/models.py``: .. code-block:: python from django.db import models from django_prose_editor.fields import ProseEditorField from content_editor.models import Region, create_plugin_base class Article(models.Model): title = models.CharField(max_length=200) pub_date = models.DateField(blank=True, null=True) # The ContentEditor requires a "regions" attribute or property # on the model. Our example hardcodes regions; if you need # different regions depending on other factors have a look at # feincms3's TemplateMixin. regions = [ Region(key="main", title="main region"), Region(key="sidebar", title="sidebar region"), ] def __str__(self): return self.title # create_plugin_base does nothing outlandish, it only defines an # abstract base model with the following attributes: # - a parent ForeignKey with a related_name that is guaranteed to # not clash # - a region CharField containing the region key defined above # - an ordering IntegerField for ordering plugin items # - a get_queryset() classmethod returning a queryset for the # Contents class and its helpers (a good place to add # select_related and # prefetch_related calls or anything # similar) # That's all. Really! ArticlePlugin = create_plugin_base(Article) class RichText(ArticlePlugin): text = ProseEditorField( extensions={ "Bold": True, "Italic": True, "BulletList": True, "OrderedList": True, "Link": True, }, sanitize=True, ) class Meta: verbose_name = "rich text" verbose_name_plural = "rich texts" class Download(ArticlePlugin): file = models.FileField(upload_to="downloads/%Y/%m/") class Meta: verbose_name = "download" verbose_name_plural = "downloads" Next, the admin integration. Plugins are integrated as ``ContentEditorInline`` inlines, a subclass of ``StackedInline`` that does not do all that much except serve as a marker that those inlines should be treated a bit differently, that is, the content blocks should be added to the content editor where inlines of different types can be edited and ordered. ``app/admin.py``: .. code-block:: python from django.contrib import admin from content_editor.admin import ContentEditor, ContentEditorInline from app import models @admin.register(models.Article) class ArticleAdmin(ContentEditor): inlines = [ ContentEditorInline.create(model=models.RichText), ContentEditorInline.create(model=models.Download), ] Rich text editor integration ============================ The example above uses `django-prose-editor`_ for rich text editing. This editor integrates seamlessly with the content editor without requiring any additional JavaScript configuration, as it uses Django's built-in ``formset:added`` event. The content editor also emits two events: ``content-editor:activate`` and ``content-editor:deactivate``. These are useful if you need to integrate JavaScript widgets that don't work well with Django's standard formset events. Since content blocks can be dynamically added and reordered using drag-and-drop, some widgets may need special handling when moved. These are native ``CustomEvent`` instances dispatched on ``document``. The affected inline is available as ``event.detail.inline`` (a DOM element) and the plugin's formset prefix as ``event.detail.prefix`` (look up the full plugin configuration via ``ContentEditor.pluginsByPrefix[prefix]``): .. code-block:: javascript document.addEventListener("content-editor:activate", (event) => { const { inline, prefix } = event.detail // ... initialize widgets inside `inline` ... }) .. note:: Older versions of django-content-editor triggered these as jQuery events and passed the inline as a jQuery-wrapped second handler argument. Because jQuery's ``.on()`` also catches native events, a widget that must support both the old and the new behavior can register with jQuery and branch on ``event.detail``:: function handleActivate(inline) { // `inline` is a DOM element } django.jQuery(document).on("content-editor:activate", (event, $row) => { if (event.detail && event.detail.inline) { handleActivate(event.detail.inline) // new: native event } else { handleActivate($row.get(0)) // old: jQuery event } }) .. note:: django-prose-editor works out of the box with the content editor since it uses Django's standard formset events. No additional JavaScript is needed. Here's a possible view implementation: ``app/views.py``: .. code-block:: python from django.shortcuts import get_object_or_404, render from django.utils.html import format_html, mark_safe from content_editor.contents import contents_for_item from app.models import Article, RichText, Download def render_items(items): for item in items: if isinstance(item, RichText): # ProseEditorField returns HTML that's already safe yield item.text elif isinstance(item, Download): yield format_html( '{}', item.file.url, item.file.name, ) def article_detail(request, id): article = get_object_or_404(Article, id=id) contents = contents_for_item(article, [RichText, Download]) return render(request, "app/article_detail.html", { "article": article, "content": { region.key: mark_safe("".join(render_items(contents[region.key]))) for region in article.regions }, }) .. note:: The ``RegionRenderer`` from `feincms3 `__ offers a more flexible and capable method of rendering plugins. After the ``render_regions`` call all that's left to do is add the content to the template. ``app/templates/app/article_detail.html``: .. code-block:: html+django

{{ article }}

{{ article.pub_date }} {{ content.main }}
Finally, ensure that ``content_editor``, ``django_prose_editor`` and ``app`` are added to your ``INSTALLED_APPS`` setting: .. code-block:: python INSTALLED_APPS = [ # ... other apps 'content_editor', 'django_prose_editor', 'app', # your app ] You'll also need to install django-prose-editor:: pip install django-prose-editor[sanitize] And you're good to go! Custom buttons to add content blocks ===================================== You can add nice icons to the plugin buttons using Google's Material Icons (which are bundled with the content editor): ``app/admin.py``: .. code-block:: python from content_editor.admin import ContentEditor, ContentEditorInline @admin.register(Article) class ArticleAdmin(ContentEditor): inlines = [ ContentEditorInline.create(model=models.RichText, icon="article"), ContentEditorInline.create(model=models.Download, icon="download"), ] The content editor bundles Google's Material Icons font. You can browse available icons at https://fonts.google.com/icons. Additional options include ``button`` (where you can set custom HTML for the icon if you need more control) and ``color`` to set a CSS color for the icon. .. _django-prose-editor: https://django-prose-editor.readthedocs.io/en/latest/