1+ from collections import defaultdict
2+ from functools import lru_cache
13from operator import attrgetter
24
35from django .core .exceptions import ImproperlyConfigured
6+ from django .db import models
47from django .template import TemplateDoesNotExist , TemplateSyntaxError
58from django .template .defaultfilters import slugify
69from django .urls import URLResolver , include , re_path
1013from django .utils .translation import activate , deactivate_all , get_language
1114
1215from cms .exceptions import PluginAlreadyRegistered , PluginNotRegistered
13- from cms .models .pagemodel import Page
16+ from cms .models .placeholdermodel import Placeholder
1417from cms .plugin_base import CMSPluginBase
15- from cms .utils .conf import get_cms_setting
1618from cms .utils .helpers import normalize_name
1719
1820
1921class PluginPool :
2022 def __init__ (self ):
2123 self .plugins = {}
24+ self .root_plugin_cache = {}
2225 self .discovered = False
23- self .global_restrictions_cache = {
24- # Initialize the global restrictions cache for each CMS_PLACEHOLDER_CONF
25- # granularity that contains "parent_classes" or "child_classes" overwrites
26- None : {},
27- ** {key : {} for key , value in get_cms_setting ("PLACEHOLDER_CONF" ).items ()
28- if "parent_classes" in value or "child_classes" in value },
29- }
26+ self .global_restrictions_cache = defaultdict (dict )
3027 self .global_template_restrictions = any (".htm" in (key or "" ) for key in self .global_restrictions_cache )
3128
3229 def _clear_cached (self ):
30+ self .root_plugin_cache = {}
31+ self .get_all_plugins_for_model .cache_clear ()
3332 if "registered_plugins" in self .__dict__ :
3433 del self .__dict__ ["registered_plugins" ]
3534 if "plugins_with_extra_menu" in self .__dict__ :
@@ -118,9 +117,9 @@ def register_plugin(self, plugin):
118117 raise PluginAlreadyRegistered (
119118 f"Cannot register { plugin !r} , a plugin with this name ({ plugin_name !r} ) is already registered."
120119 )
121-
122120 plugin .value = plugin_name
123121 self .plugins [plugin_name ] = plugin
122+ self ._clear_cached ()
124123 return plugin
125124
126125 def unregister_plugin (self , plugin ):
@@ -133,13 +132,46 @@ def unregister_plugin(self, plugin):
133132 if plugin_name not in self .plugins :
134133 raise PluginNotRegistered ("The plugin %r is not registered" % plugin )
135134 del self .plugins [plugin_name ]
135+ self ._clear_cached ()
136+
137+ @lru_cache # noqa: B019
138+ def get_all_plugins_for_model (self , model : type [models .Model ]) -> list [type [CMSPluginBase ]]:
139+ """
140+ Retrieve all plugins that can be used to edit the given model.
141+
142+ This method applies two levels of filtering:
143+
144+ 1. Plugin-level filtering (allowed_models on plugin):
145+ - If a plugin has allowed_models defined, the model must be in that list
146+ - If allowed_models is None, the plugin is available for all models
147+
148+ 2. Model-level filtering (allowed_plugins on model):
149+ - If the model has allowed_plugins defined, only those plugins are returned
150+ - If allowed_plugins is None, all plugins (passing filter 1) are returned
151+ - If allowed_plugins is an empty list [], no plugins are returned
152+
153+ Args:
154+ model: The Django model class to get plugins for
155+
156+ Returns:
157+ List of plugin classes that can be used with this model
158+ """
159+ obj_type = f"{ model ._meta .app_label } .{ model ._meta .model_name } " if model else "None"
160+ assert obj_type != "cms.page"
161+ obj_allowed_plugins = getattr (model , "allowed_plugins" , None )
162+ # Filters for allowed_models
163+ plugins = (plugin for plugin in self .plugins .values () if not plugin .allowed_models or obj_type in plugin .allowed_models )
164+ # Filters for allowed_plugins
165+ if obj_allowed_plugins is not None :
166+ plugins = (plugin for plugin in plugins if plugin .__name__ in obj_allowed_plugins )
167+ return list (plugins )
136168
137169 def get_all_plugins (
138- self , placeholder = None , page = None , setting_key = "plugins" , include_page_only = True , root_plugin = True
170+ self , placeholder = None , page = None , setting_key = "plugins" , include_page_only = True , root_plugin = False
139171 ):
140172 from cms .utils .placeholder import get_placeholder_conf
141173
142- plugins = self .plugins .values ()
174+ plugins = self .get_all_plugins_for_model ( page . __class__ ) if page else self . plugins .values ()
143175 template = (
144176 lazy (page .get_template , str )() if page and hasattr (page , "get_template" ) else None
145177 ) # Make template lazy to avoid unnecessary db access
@@ -161,11 +193,6 @@ def get_all_plugins(
161193 or ()
162194 )
163195
164- if not include_page_only :
165- # Filters out any plugin marked as page only because
166- # the include_page_only flag has been set to False
167- plugins = (plugin for plugin in plugins if not plugin .page_only )
168-
169196 if allowed_plugins :
170197 # Check that plugins are in the list of the allowed ones
171198 plugins = (plugin for plugin in plugins if plugin .__name__ in allowed_plugins )
@@ -179,6 +206,13 @@ def get_all_plugins(
179206 plugins = (plugin for plugin in plugins if not plugin .requires_parent_plugin (placeholder , page ))
180207 return plugins
181208
209+ def get_root_plugins (self , placeholder : Placeholder ) -> list [type [CMSPluginBase ]]:
210+ template = placeholder .source .get_template () if hasattr (placeholder .source , "get_template" ) else "None"
211+ key = f"{ template } :{ placeholder .slot } "
212+ if key not in self .root_plugin_cache :
213+ self .root_plugin_cache [key ] = list (self .get_all_plugins (placeholder .slot , placeholder .source , root_plugin = True ))
214+ return self .root_plugin_cache [key ]
215+
182216 def get_text_enabled_plugins (self , placeholder , page ) -> list [type [CMSPluginBase ]]:
183217 plugins = set (self .get_all_plugins (placeholder , page , root_plugin = False ))
184218 plugins .update (self .get_all_plugins (placeholder , page , setting_key = "text_only_plugins" , root_plugin = False ))
@@ -228,7 +262,7 @@ def plugins_with_extra_placeholder_menu(self) -> list[type[CMSPluginBase]]:
228262 plugin_classes = [cls for cls in self .registered_plugins if cls ._has_extra_placeholder_menu_items ]
229263 return plugin_classes
230264
231- def get_restrictions_cache (self , request_cache : dict , instance : CMSPluginBase , page : Page | None = None ) :
265+ def get_restrictions_cache (self , request_cache : dict , instance : CMSPluginBase , obj : models . Model ) -> defaultdict [ str , dict ] :
232266 """
233267 Retrieve the restrictions cache for a given plugin instance.
234268
@@ -249,21 +283,22 @@ def get_restrictions_cache(self, request_cache: dict, instance: CMSPluginBase, p
249283 dict: The restrictions cache for the given plugin instance - or the cache valid for the request.
250284 """
251285 plugin_class = self .get_plugin (instance .plugin_type )
286+ object_class = f"{ obj ._meta .app_label } .{ obj ._meta .model_name } " if obj else ""
252287 if not self .can_cache_globally (plugin_class ):
253288 return request_cache
254289 slot = instance .placeholder .slot
255290 if self .global_template_restrictions :
256- template = plugin_class ._get_template_for_conf (page )
291+ template = plugin_class ._get_template_for_conf (obj ) if obj else ""
257292 else :
258293 template = ""
259294
260- if template and f"{ template } { slot } " in self .global_restrictions_cache :
261- return self .global_restrictions_cache [f"{ template } { slot } " ]
262- if template and template in self .global_restrictions_cache :
263- return self .global_restrictions_cache [template ]
264- if slot and slot in self .global_restrictions_cache :
265- return self .global_restrictions_cache [slot ]
266- return self .global_restrictions_cache [None ]
295+ if template and f"{ object_class } : { template } { slot } " in self .global_restrictions_cache :
296+ return self .global_restrictions_cache [f"{ object_class } : { template } { slot } " ]
297+ if template and f" { object_class } : { template } " in self .global_restrictions_cache :
298+ return self .global_restrictions_cache [f" { object_class } : { template } " ]
299+ if slot and f" { object_class } : { slot } " in self .global_restrictions_cache :
300+ return self .global_restrictions_cache [f" { object_class } : { slot } " ]
301+ return self .global_restrictions_cache [object_class ]
267302
268303 restriction_methods = ("get_require_parent" , "get_child_class_overrides" , "get_parent_classes" )
269304
0 commit comments