django-content-editor icon indicating copy to clipboard operation
django-content-editor copied to clipboard

Plugin tree - possible?

Open benzkji opened this issue 1 year ago • 12 comments

I work with django-cms, and like the ability to have nested plugins. Simple use case, have a gallery plugin, place picture plugins in it. Or even picture AND video plugins, mixed, if the frontend allows.

Is this somehow possible, with django-content-editor? Or is it not, by design, and will never be? I guess the later, but hope not.

benzkji avatar Aug 06 '24 08:08 benzkji

Hi @benzkji

Right now it's not possible. What we do instead is use subregions to automatically group related plugins (e.g. a list of images is automatically grouped into a gallery) https://feincms3.readthedocs.io/en/latest/guides/rendering.html#grouping-plugins-into-subregions

You could add a GalleryPlugin, (for example having a title field or something) and register it with the renderer as follows:

renderer.register(models.GalleryPlugin, "", subregion="gallery")
renderer.register(models.Image, template_renderer("plugins/image.html"), marks={"default", "gallery"})
renderer.register(models.Video, template_renderer("plugins/video.html"), marks={"default", "gallery"})

In the renderer implementation, you'd have something like this:

def handle_gallery(self, plugins, context):
    gallery = plugins.popleft()
    yield render_in_context(
        context,
        "subregions/gallery.html",
       {"gallery": gallery, "slides": [self.render_plugin(plugin, context) for plugin in self.takewhile_mark(plugins, "gallery")]},
    )

So, the content model is powerful enough to do what you want, even though the editor interface doesn't support nesting at this time.

Or what we're doing more recently is adding configurable sections, something like this:

CLOSE_SECTION = "close"


class Section(JSONPluginBase, PagePlugin):
   class Meta:
       verbose_name = _("section")
       verbose_name_plural = _("sections")

   def __str__(self):
       return self.type


class SectionInline(JSONPluginInline):
   color = "#1aa4c5"


DidYouKnowBox = Section.proxy(
   "did_you_know",
   verbose_name=_("did you know box"),
   schema={
       "type": "object",
       "properties": {
           "title": {"type": "string", "title": _("title")},
       },
   },
)
KeepInMindStrategiesBox = Section.proxy(
   "keep_in_mind",
   verbose_name=_("keep in mind / strategies box"),
   schema={
       "type": "object",
       "properties": {
           "title": {"type": "string", "title": _("title")},
       },
   },
)

And then customize the renderer to automatically add plugins to those sections.

I want to clean up the code and add section nesting to the CMS for those more advanced use cases.

matthiask avatar Aug 06 '24 08:08 matthiask

Thank you @matthiask !

With the first solution, I could add one gallery per region, as all images and videos would be rendered in there, is that correct? And the visual appearance would not be correct, in the admin, as images and videos would be on the same level as the gallery plugin?

I don't really understand the second example. But if you say "I want to clean up the code and add section nesting to the CMS for those more advanced use cases", does it mean a real tree (with django-tree-inline ;), or something different?

IMHO, hierarchical plugins would be a benefit for this library. It' way more simple (from my developer view), quite a few workarounds would be obsolete, and you just know that you can have hierarchical plugins if you need, or not, if you don't. But yea, it would add complexity.

benzkji avatar Aug 06 '24 08:08 benzkji

ah, just now realized the gallery = plugins.popleft(). Thanks again for the insights.

benzkji avatar Aug 06 '24 12:08 benzkji

I don't really understand the second example. But if you say "I want to clean up the code and add section nesting to the CMS for those more advanced use cases", does it mean a real tree (with django-tree-inline ;), or something different?

It wouldn't be a "real" tree in the sense of django-tree-queries or something, but would still be a tree with nodes and leaves and everything.

IMHO, hierarchical plugins would be a benefit for this library. It' way more simple (from my developer view), quite a few workarounds would be obsolete, and you just know that you can have hierarchical plugins if you need, or not, if you don't. But yea, it would add complexity.

Yeah, that's the thing. It's always a trade off. The main benefit of using django-content-editor is that you have very little additional code -- almost everything is handled by the Django admin interface's inlines/formsets. Once you leave that path you need much more code to handle all the new cases you get. So, it's either simple in the way how the data structure directly represents the tree or simple in the way how it's implemented. You cannot have both.

ah, just now realized the gallery = plugins.popleft(). Thanks again for the insights.

Yes, you just need to add a plugin with different marks in-between, or add the GalleryPlugin itself (since it doesn't have the gallery mark in my example!) to start another gallery.

matthiask avatar Aug 06 '24 12:08 matthiask

It wouldn't be a "real" tree in the sense of django-tree-queries or something, but would still be a tree with nodes and leaves and everything.

yes, nodes and leaves, no need for left, right and other such things ;) is this feature in the development pipeline, or is it more of a nice to have, for you?

benzkji avatar Aug 06 '24 13:08 benzkji

I'm not 100% certain it will happen but it's definitely something I want to do. Automatic grouping of plugins is nice, but a more explicit way to present it would be better.

matthiask avatar Aug 06 '24 13:08 matthiask

In cases like that I tend to use a normal model of its own e.g. Gallery, which may be a PluginBase or not depending on it having mixed content. And for the Page a GalleryPlugin, which is just a foreign key to Gallery. So editing is two steps: editing a gallery and adding it to the page via the GalleryPlugin. Bonus: you can add the plugin multiple times if needed and when you have say hundreds of images in one gallery sorting them may be easier in its own model.

gradel avatar Aug 06 '24 15:08 gradel

Yes, I like that approach as well!

matthiask avatar Aug 06 '24 15:08 matthiask

@gradel yep, know this, that's my default with forms.

benzkji avatar Aug 06 '24 20:08 benzkji

By the way, nested sections are now possible: https://406.ch/writing/django-content-editor-now-supports-nested-sections/

matthiask avatar Oct 10 '24 14:10 matthiask

Played around and built an accordion app with "chained" foreign keys to mimic a plugin tree. https://github.com/gradel/feincms3-accordion

gradel avatar Oct 16 '24 01:10 gradel

The feincms3 renderer now contains some code to handle these sections:

https://github.com/feincms/feincms3/blob/cbb751558824a786b403864dd9518073c0d3506b/feincms3/renderer.py#L324-L363

matthiask avatar Jun 20 '25 09:06 matthiask