Skip to content

Commit feb7a28

Browse files
authored
Merge 1712ad0 into dc20af8
2 parents dc20af8 + 1712ad0 commit feb7a28

18 files changed

+259
-201
lines changed

cms/models/pluginmodel.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from __future__ import annotations
22

33
import os
4+
import re
45
import warnings
56
from datetime import date
6-
from functools import cache
7+
from functools import cache, cached_property
78

89
from django.core.exceptions import ObjectDoesNotExist
910
from django.db import connection, connections, models, router
@@ -202,21 +203,23 @@ def __repr__(self):
202203
return display
203204

204205
def get_plugin_name(self):
205-
from cms.plugin_pool import plugin_pool
206-
207-
return plugin_pool.get_plugin(self.plugin_type).name
206+
return self.plugin_class.name
208207

209208
def get_short_description(self):
210209
instance = self.get_plugin_instance()[0]
211210
if instance is not None:
212211
return force_str(instance)
213212
return _("<Empty>")
214213

215-
def get_plugin_class(self):
214+
@cached_property
215+
def plugin_class(self):
216216
from cms.plugin_pool import plugin_pool
217217

218218
return plugin_pool.get_plugin(self.plugin_type)
219219

220+
def get_plugin_class(self):
221+
return self.plugin_class
222+
220223
def get_plugin_class_instance(self, admin=None):
221224
plugin_class = self.get_plugin_class()
222225
# needed so we have the same signature as the original ModelAdmin
@@ -264,7 +267,15 @@ def get_bound_plugin(self):
264267
return self._inst
265268

266269
def get_plugin_info(self, children=None, parents=None):
267-
plugin_class = self.get_plugin_class()
270+
plugin_class = self.plugin_class
271+
272+
if not hasattr(CMSPlugin, '_edit_url'):
273+
CMSPlugin._edit_url = admin_reverse('cms_placeholder_edit_plugin', args=(0,))
274+
CMSPlugin._add_url = admin_reverse('cms_placeholder_add_plugin')
275+
CMSPlugin._delete_url = admin_reverse('cms_placeholder_delete_plugin', args=(0,))
276+
CMSPlugin._move_url = admin_reverse('cms_placeholder_move_plugin')
277+
CMSPlugin._copy_url = admin_reverse('cms_placeholder_copy_plugins')
278+
268279
return {
269280
'type': 'plugin',
270281
'position': self.position,
@@ -278,7 +289,13 @@ def get_plugin_info(self, children=None, parents=None):
278289
'plugin_parent_restriction': parents or [],
279290
'disable_edit': plugin_class.disable_edit,
280291
'disable_child_plugins': plugin_class.disable_child_plugins,
281-
'urls': self.get_action_urls(),
292+
'urls': {
293+
'edit_plugin': re.sub(r"/0/", f"/{self.pk}/", CMSPlugin._edit_url),
294+
'add_plugin': CMSPlugin._add_url,
295+
'delete_plugin': re.sub(r"/0/", f"/{self.pk}/", CMSPlugin._delete_url),
296+
'move_plugin': CMSPlugin._move_url,
297+
'copy_plugin': CMSPlugin._copy_url,
298+
}
282299
}
283300

284301
def refresh_from_db(self, *args, **kwargs):
@@ -449,22 +466,21 @@ def get_action_urls(self, js_compat=True):
449466
# TODO: Remove this condition
450467
# once the javascript files have been refactored
451468
# to use the new naming schema (ending in _url).
452-
data = {
469+
return {
453470
'edit_plugin': admin_reverse('cms_placeholder_edit_plugin', args=(self.pk,)),
454471
'add_plugin': admin_reverse('cms_placeholder_add_plugin'),
455472
'delete_plugin': admin_reverse('cms_placeholder_delete_plugin', args=(self.pk,)),
456473
'move_plugin': admin_reverse('cms_placeholder_move_plugin'),
457474
'copy_plugin': admin_reverse('cms_placeholder_copy_plugins'),
458475
}
459476
else:
460-
data = {
477+
return {
461478
'edit_url': admin_reverse('cms_placeholder_edit_plugin', args=(self.pk,)),
462479
'add_url': admin_reverse('cms_placeholder_add_plugin'),
463480
'delete_url': admin_reverse('cms_placeholder_delete_plugin', args=(self.pk,)),
464481
'move_url': admin_reverse('cms_placeholder_move_plugin'),
465482
'copy_url': admin_reverse('cms_placeholder_copy_plugins'),
466483
}
467-
return data
468484

469485

470486
def get_plugin_media_path(instance, filename):

cms/plugin_base.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,7 @@ def __new__(cls, name, bases, attrs):
8585
return new_plugin
8686

8787

88-
T = TypeVar("T", bound=Callable)
89-
90-
91-
def template_slot_caching(method: T) -> T:
88+
def template_slot_caching(method: Callable) -> Callable:
9289
"""
9390
Decorator that enables global caching for methods based on placeholder slots and templates.
9491
@@ -109,12 +106,8 @@ def get_child_class_overrides(cls, slot: str, page: Optional[Page] = None, insta
109106
pass
110107
"""
111108

112-
@wraps(method)
113-
def wrapper(*args, **kwargs):
114-
return method(*args, **kwargs)
115-
116-
wrapper._template_slot_caching = True
117-
return cast(T, wrapper)
109+
method._template_slot_caching = True
110+
return method
118111

119112

120113
class CMSPluginBase(admin.ModelAdmin, metaclass=CMSPluginBaseMetaclass):
@@ -736,7 +729,7 @@ def get_child_plugin_candidates(cls, slot: str, page: Optional[Page] = None) ->
736729
# where only text-only plugins are allowed.
737730
from cms.plugin_pool import plugin_pool
738731

739-
return sorted(plugin_pool.get_all_plugins(slot, page, root_plugin=False), key=attrgetter("module", "name"))
732+
return plugin_pool.get_all_plugins(slot, page, root_plugin=False)
740733

741734
@classmethod
742735
@template_slot_caching
@@ -753,8 +746,12 @@ def get_child_classes(
753746
installed_plugins = cls.get_child_plugin_candidates(slot, page)
754747

755748
if child_classes:
749+
# Override skips check if current class is valid parent of child classes
756750
return [plugin.__name__ for plugin in installed_plugins if plugin.__name__ in child_classes]
757751

752+
if only_uncached:
753+
installed_plugins = [plugin for plugin in installed_plugins if not plugin.cache_parent_classes]
754+
758755
child_classes = []
759756
plugin_type = cls.__name__
760757

@@ -767,12 +764,11 @@ def get_child_classes(
767764
# If there are no restrictions then the plugin
768765
# is a valid child class.
769766
for plugin_class in installed_plugins:
770-
if not only_uncached or not plugin_class.cache_parent_classes:
771-
allowed_parents = plugin_class.get_parent_classes(slot, page, instance)
772-
if not allowed_parents or plugin_type in allowed_parents:
773-
# Plugin has no parent restrictions or
774-
# Current plugin (self) is a configured parent
775-
child_classes.append(plugin_class.__name__)
767+
allowed_parents = plugin_class.get_parent_classes(slot, page, instance)
768+
if not allowed_parents or plugin_type in allowed_parents:
769+
# Plugin has no parent restrictions or
770+
# Current plugin (self) is a configured parent
771+
child_classes.append(plugin_class.__name__)
776772

777773
return child_classes
778774

cms/plugin_pool.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@ def __init__(self):
2222
self.plugins = {}
2323
self.discovered = False
2424
self.global_restrictions_cache = {
25+
# Initialize the global restrictions cache for each CMS_PLACEHOLDER_CONF
26+
# granularity that contains "parent_classes" or "child_classes" overwrites
2527
None: {},
26-
**{key: {} for key in get_cms_setting("PLACEHOLDER_CONF").keys()},
28+
**{key: {} for key, value in get_cms_setting("PLACEHOLDER_CONF").items()
29+
if "parent_classes" in value or "child_classes" in value},
2730
}
2831
self.global_template_restrictions = any(".htm" in (key or "") for key in self.global_restrictions_cache)
2932

@@ -135,7 +138,6 @@ def get_all_plugins(
135138
):
136139
from cms.utils.placeholder import get_placeholder_conf
137140

138-
self.discover_plugins()
139141
plugins = self.plugins.values()
140142
template = (
141143
lazy(page.get_template, str)() if page else None
@@ -185,7 +187,6 @@ def get_plugin(self, name) -> type[CMSPluginBase]:
185187
"""
186188
Retrieve a plugin from the cache.
187189
"""
188-
self.discover_plugins()
189190
return self.plugins[name]
190191

191192
def get_patterns(self) -> list[URLResolver]:
@@ -210,7 +211,6 @@ def get_patterns(self) -> list[URLResolver]:
210211
return url_patterns
211212

212213
def get_system_plugins(self) -> list[str]:
213-
self.discover_plugins()
214214
return [plugin.__name__ for plugin in self.plugins.values() if plugin.system]
215215

216216
@cached_property
@@ -240,7 +240,7 @@ def get_restrictions_cache(self, request_cache: dict, instance: CMSPluginBase, p
240240
be recalculated for each request.
241241
242242
Args:
243-
request_cache (dict): The current request cache.
243+
request_cache (dict): The current request cache (only filled is non globally cacheable).
244244
instance (CMSPluginBase): The plugin instance for which to retrieve the restrictions cache.
245245
page (Optional[Page]): The page associated with the plugin instance, if any.
246246
@@ -256,11 +256,11 @@ def get_restrictions_cache(self, request_cache: dict, instance: CMSPluginBase, p
256256
else:
257257
template = ""
258258

259-
if f"{template} {slot}" in self.global_restrictions_cache:
259+
if template and f"{template} {slot}" in self.global_restrictions_cache:
260260
return self.global_restrictions_cache[f"{template} {slot}"]
261261
if template and template in self.global_restrictions_cache:
262262
return self.global_restrictions_cache[template]
263-
if slot in self.global_restrictions_cache:
263+
if slot and slot in self.global_restrictions_cache:
264264
return self.global_restrictions_cache[slot]
265265
return self.global_restrictions_cache[None]
266266

cms/plugin_rendering.py

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,10 @@
3939
logger = logging.getLogger(__name__)
4040

4141

42-
def _unpack_plugins(parent_plugin: CMSPlugin) -> list[CMSPlugin]:
43-
found_plugins = []
44-
42+
def _unpack_plugins(parent_plugin: CMSPlugin) -> Generator[CMSPlugin, None, None]:
43+
yield parent_plugin
4544
for plugin in parent_plugin.child_plugin_instances or []:
46-
found_plugins.append(plugin)
47-
48-
if plugin.child_plugin_instances:
49-
found_plugins.extend(_unpack_plugins(plugin))
50-
return found_plugins
45+
yield from _unpack_plugins(plugin)
5146

5247

5348
class RenderedPlaceholder:
@@ -95,12 +90,12 @@ class BaseRenderer:
9590

9691
def __init__(self, request: HttpRequest):
9792
self.request = request
98-
self._cached_templates = {}
9993
self._cached_plugin_classes = {}
10094
self._placeholders_content_cache = {}
10195
self._placeholders_by_page_cache = {}
10296
self._rendered_placeholders = OrderedDict()
10397
self._rendered_plugins_by_placeholder = {}
98+
self._plugins_with_perms = None
10499

105100
@cached_property
106101
def current_page(self) -> Page:
@@ -128,20 +123,25 @@ def plugin_pool(self) -> PluginPool:
128123
def request_language(self) -> str:
129124
return get_language_from_request(self.request)
130125

126+
def get_plugins_with_perms(self) -> list[CMSPlugin]:
127+
if self._plugins_with_perms is None:
128+
registered_plugins = self.plugin_pool.registered_plugins
129+
can_add_plugin = partial(
130+
has_plugin_permission, user=self.request.user, permission_type="add"
131+
)
132+
self._plugins_with_perms = [
133+
plugin
134+
for plugin in registered_plugins
135+
if can_add_plugin(plugin_type=plugin.value)
136+
]
137+
138+
return self._plugins_with_perms
139+
131140
def get_placeholder_plugin_menu(
132141
self, placeholder: Placeholder, page: Optional[Page] = None
133142
):
134-
registered_plugins = self.plugin_pool.registered_plugins
135-
can_add_plugin = partial(
136-
has_plugin_permission, user=self.request.user, permission_type="add"
137-
)
138-
plugins = [
139-
plugin
140-
for plugin in registered_plugins
141-
if can_add_plugin(plugin_type=plugin.value)
142-
]
143143
plugin_menu = get_toolbar_plugin_struct(
144-
plugins=plugins,
144+
plugins=self.get_plugins_with_perms(),
145145
slot=placeholder.slot,
146146
page=page,
147147
)
@@ -165,7 +165,8 @@ def get_plugin_toolbar_js(self, plugin: CMSPlugin, page: Optional[Page] = None):
165165
)
166166
child_classes, parent_classes = get_plugin_restrictions(
167167
plugin=plugin,
168-
restrictions_cache=placeholder_cache,
168+
page=page,
169+
restrictions_cache=placeholder_cache, # Store non-global plugin-restriction in placeholder_cache
169170
)
170171
content = get_plugin_toolbar_js(
171172
plugin,
@@ -736,13 +737,7 @@ def get_plugins_to_render(self, *args, **kwargs):
736737
plugins = super().get_plugins_to_render(*args, **kwargs)
737738

738739
for plugin in plugins:
739-
yield plugin
740-
741-
if not plugin.child_plugin_instances:
742-
continue
743-
744-
for plugin in _unpack_plugins(plugin):
745-
yield plugin
740+
yield from _unpack_plugins(plugin)
746741

747742
def render_placeholder(self, placeholder, language, page=None):
748743
rendered_plugins = self.render_plugins(

cms/templatetags/cms_js_tags.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,12 @@ def bool(value):
3434
def render_cms_structure_js(context, renderer, obj):
3535
markup_bits = []
3636
obj_placeholders_by_slot = rescan_placeholders_for_obj(obj)
37-
declared_placeholders = get_declared_placeholders_for_obj(obj)
3837
try:
3938
lang = context["request"].toolbar.request_language
4039
except AttributeError:
4140
lang = None
4241

43-
for placeholder_node in declared_placeholders:
44-
obj_placeholder = obj_placeholders_by_slot.get(placeholder_node.slot)
45-
42+
for obj_placeholder in obj_placeholders_by_slot.values():
4643
if obj_placeholder:
4744
placeholder_js = renderer.render_placeholder(obj_placeholder, language=lang, page=obj)
4845
markup_bits.append(placeholder_js)

cms/test_utils/util/context_managers.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from tempfile import _exists, mkdtemp, template
66

77
from django.contrib.auth import get_user_model
8+
from django.test.utils import override_settings
89
from django.utils.translation import activate, get_language
910

1011
from cms.apphook_pool import apphook_pool
@@ -173,3 +174,15 @@ def __init__(self):
173174
def __call__(self, *args, **kwargs):
174175
self.call_count += 1
175176
self.calls.append((args, kwargs))
177+
178+
179+
@contextmanager
180+
def override_placeholder_conf(CMS_PLACEHOLDER_CONF):
181+
from cms.utils.placeholder import _clear_placeholder_conf_cache
182+
183+
# Call _get_placeholder_settings after changing the setting
184+
with override_settings(CMS_PLACEHOLDER_CONF=CMS_PLACEHOLDER_CONF):
185+
_clear_placeholder_conf_cache() # Clear cache if needed
186+
yield
187+
# Call _get_placeholder_settings after resetting the setting
188+
_clear_placeholder_conf_cache() # Clear cache if needed

cms/tests/test_page_admin.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from cms.test_utils.util.context_managers import (
3232
LanguageOverride,
3333
UserLoginContext,
34+
override_placeholder_conf,
3435
)
3536
from cms.toolbar.utils import get_object_edit_url
3637
from cms.utils.compat import DJANGO_4_2, DJANGO_5_1
@@ -1501,7 +1502,7 @@ def test_global_limit_on_plugin_move(self):
15011502
plugin_2 = add_plugin(**data)
15021503
plugin_3 = add_plugin(**data)
15031504
with UserLoginContext(self, superuser):
1504-
with self.settings(CMS_PLACEHOLDER_CONF=self.placeholderconf):
1505+
with override_placeholder_conf(self.placeholderconf):
15051506
data = self._get_move_data(plugin_1, position=1, placeholder=target_placeholder)
15061507
endpoint = self.get_move_plugin_uri(plugin_1)
15071508
response = self.client.post(endpoint, data) # first
@@ -1529,7 +1530,7 @@ def test_type_limit_on_plugin_move(self):
15291530
plugin_1 = add_plugin(**data)
15301531
plugin_2 = add_plugin(**data)
15311532
with UserLoginContext(self, superuser):
1532-
with self.settings(CMS_PLACEHOLDER_CONF=self.placeholderconf):
1533+
with override_placeholder_conf(self.placeholderconf):
15331534
data = self._get_move_data(plugin_1, position=1, placeholder=target_placeholder)
15341535
endpoint = self.get_move_plugin_uri(plugin_1)
15351536
response = self.client.post(endpoint, data) # first

0 commit comments

Comments
 (0)