Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Avoid downcasting of text-enabled child plugins if plugins are already downcasted #58

Merged
merged 4 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions djangocms_text/cms_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ def get_form_class(self, request, plugins, plugin):
rendered_text = plugin_tags_to_admin_html(
text=instance.body,
context=context,
child_plugin_instances=instance.child_plugin_instances,
)
else:
rendered_text = None
Expand Down Expand Up @@ -606,7 +607,8 @@ def get_child_plugin_candidates(cls, slot, page):
]
return text_enabled_plugins

def render_plugin_icon(self, plugin):
@staticmethod
def render_plugin_icon(plugin):
icon = getattr(plugin, "text_icon", None)
if icon is None:
return
Expand Down Expand Up @@ -683,7 +685,9 @@ def render(self, context, instance, placeholder):

context.update(
{
"body": plugin_tags_to_admin_html(body, context),
"body": plugin_tags_to_admin_html(
body, context, child_plugin_instances=instance.child_plugin_instances
),
"placeholder": placeholder,
"object": instance,
"editor_settings": editor_settings,
Expand All @@ -696,7 +700,9 @@ def render(self, context, instance, placeholder):
body = render_dynamic_attributes(instance.body, admin_objects=False, remove_attr=True)
context.update(
{
"body": plugin_tags_to_user_html(body, context),
"body": plugin_tags_to_user_html(
body, context, child_plugin_instances=instance.child_plugin_instances
),
"placeholder": placeholder,
"object": instance,
}
Expand Down
32 changes: 19 additions & 13 deletions djangocms_text/utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import re
from collections import OrderedDict
from functools import WRAPPER_ASSIGNMENTS, wraps
from typing import Optional

from django.template.defaultfilters import force_escape
from django.template import Context
from django.template.loader import render_to_string

from cms import __version__
from cms.models import CMSPlugin
from cms.utils.urlutils import admin_reverse

from classytags.utils import flatten_context
Expand All @@ -24,11 +27,11 @@
cms_placeholder_add_plugin = "cms_page_add_plugin"


def _render_cms_plugin(plugin, context):
def _render_cms_plugin(plugin: CMSPlugin, context):
context = flatten_context(context)
context["plugin"] = plugin

# This my fellow ckeditor enthusiasts is a hack..
# This my fellow enthusiasts is a hack..

# If I let djangoCMS render the plugin using {% render_plugin %}
# it will wrap the output in the toolbar markup which we don't want.
Expand All @@ -47,7 +50,7 @@ def _render_cms_plugin(plugin, context):
return response


def random_comment_exempt(view_func):
def random_comment_exempt(view_func: callable) -> callable:
# Borrowed from
# https://github.com/lpomfrey/django-debreach/blob/f778d77ffc417/debreach/decorators.py#L21
# This is a no-op if django-debreach is not installed
Expand All @@ -59,7 +62,7 @@ def wrapped_view(*args, **kwargs):
return wraps(view_func, assigned=WRAPPER_ASSIGNMENTS)(wrapped_view)


def plugin_to_tag(obj, content="", admin=False):
def plugin_to_tag(obj: CMSPlugin, content: str = "", admin: bool = False):
plugin_attrs = OrderedDict(
id=obj.pk,
icon_alt=force_escape(obj.get_instance_icon_alt()),
Expand Down Expand Up @@ -92,13 +95,16 @@ def _find_plugins():
return [int(_id) for _id in _find_plugins()]


def _plugin_tags_to_html(text, output_func):
def _plugin_tags_to_html(text: str, output_func: callable, child_plugin_instances: Optional[list[CMSPlugin]]) -> str:
"""
Convert plugin object 'tags' into the form for public site.

context is the template context to use, placeholder is the placeholder name
"""
plugins_by_id = get_plugins_from_text(text)
if child_plugin_instances is not None:
plugins_by_id = {plugin.pk: plugin for plugin in child_plugin_instances}
else:
plugins_by_id = get_plugins_from_text(text)

def _render_tag(m):
try:
Expand All @@ -115,29 +121,29 @@ def _render_tag(m):
return OBJ_ADMIN_RE.sub(_render_tag, text)


def plugin_tags_to_user_html(text, context):
def plugin_tags_to_user_html(text: str, context: Context, child_plugin_instances: list[CMSPlugin]) -> str:
def _render_plugin(obj, match):
return _render_cms_plugin(obj, context)

return _plugin_tags_to_html(text, output_func=_render_plugin)
return _plugin_tags_to_html(text, output_func=_render_plugin, child_plugin_instances=child_plugin_instances)


def plugin_tags_to_admin_html(text, context):
def plugin_tags_to_admin_html(text: str, context: Context, child_plugin_instances: list[CMSPlugin]) -> str:
def _render_plugin(obj, match):
plugin_content = _render_cms_plugin(obj, context)
return plugin_to_tag(obj, content=plugin_content, admin=True)

return _plugin_tags_to_html(text, output_func=_render_plugin)
return _plugin_tags_to_html(text, output_func=_render_plugin, child_plugin_instances=child_plugin_instances)


def plugin_tags_to_db(text):
def plugin_tags_to_db(text: str) -> str:
def _strip_plugin_content(obj, match):
return plugin_to_tag(obj).strip()

return _plugin_tags_to_html(text, output_func=_strip_plugin_content)
return _plugin_tags_to_html(text, output_func=_strip_plugin_content, child_plugin_instances=None)


def replace_plugin_tags(text, id_dict, regex=OBJ_ADMIN_RE):
def replace_plugin_tags(text: str, id_dict, regex: str = OBJ_ADMIN_RE) -> str:
from cms.models import CMSPlugin

plugins_by_id = CMSPlugin.objects.in_bulk(id_dict.values())
Expand Down
2 changes: 1 addition & 1 deletion private/js/cms.editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class CMSEditor {
this.CMS.$(window).on('cms-content-refresh', () => {
if (document.querySelector('template.cms-plugin')) {
// django CMS core does not wrap newly inserted inline editable fields
this.CMS.API.Helpers.reloadBrowser();
// this.CMS.API.Helpers.reloadBrowser();
} else {
this._resetInlineEditors();
}
Expand Down
10 changes: 5 additions & 5 deletions tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def _replace_plugin_contents(self, text, new_plugin_content):
def _do_replace(obj, match):
return plugin_to_tag(obj, content=new_plugin_content)

return _plugin_tags_to_html(text, output_func=_do_replace)
return _plugin_tags_to_html(text, output_func=_do_replace, child_plugin_instances=None)

def add_plugin_to_text(self, text_plugin, plugin):
text_plugin.body = f"{text_plugin.body} {plugin_to_tag(plugin)}"
Expand Down Expand Up @@ -488,6 +488,7 @@ def test_change_form_has_rendered_plugin_content(self):
text_with_rendered_plugins = plugin_tags_to_admin_html(
text=text_plugin.body,
context=context,
child_plugin_instances=None,
)

endpoint = self.get_change_plugin_uri(text_plugin)
Expand Down Expand Up @@ -575,7 +576,6 @@ def test_user_cant_edit_child_plugins_directly(self):
text_plugin.body,
new_plugin_content='<img src="">',
)

endpoint = self.get_change_plugin_uri(text_plugin)
response = self.client.post(endpoint, {"body": overridden_text})
text_plugin.refresh_from_db()
Expand Down Expand Up @@ -1144,7 +1144,7 @@ def test_get_child_plugin_candidates_whitelist(self, get_text_enabled_plugins_mo

with patch("djangocms_text.settings.TEXT_CHILDREN_WHITELIST", ["PluginA"]):
candidates = TextPlugin.get_child_plugin_candidates(slot, page)
self.assertEqual(candidates, [plugin_a])
self.assertEqual(list(candidates), [plugin_a])

@patch("cms.plugin_pool.plugin_pool.get_text_enabled_plugins")
def test_get_child_plugin_candidates_blacklist(self, get_text_enabled_plugins_mock):
Expand All @@ -1157,7 +1157,7 @@ def test_get_child_plugin_candidates_blacklist(self, get_text_enabled_plugins_mo

with patch("djangocms_text.settings.TEXT_CHILDREN_BLACKLIST", ["PluginA"]):
candidates = TextPlugin.get_child_plugin_candidates(slot, page)
self.assertEqual(candidates, [plugin_b])
self.assertEqual(list(candidates), [plugin_b])

@patch("cms.plugin_pool.plugin_pool.get_text_enabled_plugins")
def test_get_child_plugin_candidates_no_whitelist_blacklist(self, get_text_enabled_plugins_mock):
Expand All @@ -1171,4 +1171,4 @@ def test_get_child_plugin_candidates_no_whitelist_blacklist(self, get_text_enabl
with patch("djangocms_text.settings.TEXT_CHILDREN_WHITELIST", None):
with patch("djangocms_text.settings.TEXT_CHILDREN_BLACKLIST", []):
candidates = TextPlugin.get_child_plugin_candidates(slot, page)
self.assertEqual(candidates, [plugin_a, plugin_b])
self.assertEqual(list(candidates), [plugin_a, plugin_b])