1# -*- coding: utf-8 -*- 2import operator 3import warnings 4from collections import OrderedDict 5 6from django.conf import settings 7from django.core.exceptions import ImproperlyConfigured 8from django.db.models.query_utils import Q 9from django.template import TemplateSyntaxError, NodeList, Variable, Context, Template, engines 10from django.template.base import VariableNode 11from django.template.loader import get_template 12from django.template.loader_tags import BlockNode, ExtendsNode, IncludeNode 13from django.utils import six 14 15from sekizai.helpers import get_varname 16 17from cms.exceptions import DuplicatePlaceholderWarning 18from cms.utils.conf import get_cms_setting 19 20 21def _get_nodelist(tpl): 22 if hasattr(tpl, 'template'): 23 return tpl.template.nodelist 24 else: 25 return tpl.nodelist 26 27 28def get_context(): 29 if engines is not None: 30 context = Context() 31 context.template = Template('') 32 return context 33 else: 34 return {} 35 36 37def get_placeholder_conf(setting, placeholder, template=None, default=None): 38 """ 39 Returns the placeholder configuration for a given setting. The key would for 40 example be 'plugins' or 'name'. 41 42 Resulting value will be the last from: 43 44 CMS_PLACEHOLDER_CONF[None] (global) 45 CMS_PLACEHOLDER_CONF['template'] (if template is given) 46 CMS_PLACEHOLDER_CONF['placeholder'] 47 CMS_PLACEHOLDER_CONF['template placeholder'] (if template is given) 48 """ 49 50 if placeholder: 51 keys = [] 52 placeholder_conf = get_cms_setting('PLACEHOLDER_CONF') 53 # 1st level 54 if template: 55 keys.append(u'%s %s' % (template, placeholder)) 56 # 2nd level 57 keys.append(placeholder) 58 # 3rd level 59 if template: 60 keys.append(template) 61 # 4th level 62 keys.append(None) 63 for key in keys: 64 try: 65 conf = placeholder_conf[key] 66 value = conf.get(setting, None) 67 if value is not None: 68 return value 69 inherit = conf.get('inherit') 70 if inherit: 71 if ' ' in inherit: 72 inherit = inherit.split(' ') 73 else: 74 inherit = (None, inherit) 75 value = get_placeholder_conf(setting, inherit[1], inherit[0], default) 76 if value is not None: 77 return value 78 except KeyError: 79 continue 80 return default 81 82 83def get_toolbar_plugin_struct(plugins, slot=None, page=None): 84 """ 85 Return the list of plugins to render in the toolbar. 86 The dictionary contains the label, the classname and the module for the 87 plugin. 88 Names and modules can be defined on a per-placeholder basis using 89 'plugin_modules' and 'plugin_labels' attributes in CMS_PLACEHOLDER_CONF 90 91 :param plugins: list of plugins 92 :param slot: placeholder slot name 93 :param page: the page 94 :return: list of dictionaries 95 """ 96 template = None 97 98 if page: 99 template = page.template 100 101 modules = get_placeholder_conf("plugin_modules", slot, template, default={}) 102 names = get_placeholder_conf("plugin_labels", slot, template, default={}) 103 104 main_list = [] 105 106 # plugin.value points to the class name of the plugin 107 # It's added on registration. TIL. 108 for plugin in plugins: 109 main_list.append({'value': plugin.value, 110 'name': names.get(plugin.value, plugin.name), 111 'module': modules.get(plugin.value, plugin.module)}) 112 return sorted(main_list, key=operator.itemgetter("module")) 113 114 115def validate_placeholder_name(name): 116 if not isinstance(name, six.string_types): 117 raise ImproperlyConfigured("Placeholder identifier names need to be of type string. ") 118 119 if not all(ord(char) < 128 for char in name): 120 raise ImproperlyConfigured("Placeholder identifiers names may not " 121 "contain non-ascii characters. If you wish your placeholder " 122 "identifiers to contain non-ascii characters when displayed to " 123 "users, please use the CMS_PLACEHOLDER_CONF setting with the 'name' " 124 "key to specify a verbose name.") 125 126 127class PlaceholderNoAction(object): 128 can_copy = False 129 130 def copy(self, **kwargs): 131 return False 132 133 def get_copy_languages(self, **kwargs): 134 return [] 135 136 137class MLNGPlaceholderActions(PlaceholderNoAction): 138 can_copy = True 139 140 def copy(self, target_placeholder, source_language, fieldname, model, target_language, **kwargs): 141 from cms.utils.copy_plugins import copy_plugins_to 142 trgt = model.objects.get(**{fieldname: target_placeholder}) 143 src = model.objects.get(master=trgt.master, language_code=source_language) 144 145 source_placeholder = getattr(src, fieldname, None) 146 if not source_placeholder: 147 return False 148 return copy_plugins_to(source_placeholder.get_plugins_list(), 149 target_placeholder, target_language) 150 151 def get_copy_languages(self, placeholder, model, fieldname, **kwargs): 152 manager = model.objects 153 src = manager.get(**{fieldname: placeholder}) 154 query = Q(master=src.master) 155 query &= Q(**{'%s__cmsplugin__isnull' % fieldname: False}) 156 query &= ~Q(pk=src.pk) 157 158 language_codes = manager.filter(query).values_list('language_code', flat=True).distinct() 159 return [(lc, dict(settings.LANGUAGES)[lc]) for lc in language_codes] 160 161 162def restore_sekizai_context(context, changes): 163 varname = get_varname() 164 sekizai_container = context.get(varname) 165 for key, values in changes.items(): 166 sekizai_namespace = sekizai_container[key] 167 for value in values: 168 sekizai_namespace.append(value) 169 170 171def _scan_placeholders(nodelist, node_class=None, current_block=None, ignore_blocks=None): 172 from cms.templatetags.cms_tags import Placeholder 173 174 if not node_class: 175 node_class = Placeholder 176 177 nodes = [] 178 179 if ignore_blocks is None: 180 # List of BlockNode instances to ignore. 181 # This is important to avoid processing overriden block nodes. 182 ignore_blocks = [] 183 184 for node in nodelist: 185 # check if this is a placeholder first 186 if isinstance(node, node_class): 187 nodes.append(node) 188 elif isinstance(node, IncludeNode): 189 # if there's an error in the to-be-included template, node.template becomes None 190 if node.template: 191 # Check if it quacks like a template object, if not 192 # presume is a template path and get the object out of it 193 if not callable(getattr(node.template, 'render', None)): 194 # If it's a variable there is no way to expand it at this stage so we 195 # need to skip it 196 if isinstance(node.template.var, Variable): 197 continue 198 else: 199 template = get_template(node.template.var) 200 else: 201 template = node.template 202 nodes += _scan_placeholders(_get_nodelist(template), node_class, current_block) 203 # handle {% extends ... %} tags 204 elif isinstance(node, ExtendsNode): 205 nodes += _get_placeholder_nodes_from_extend(node, node_class) 206 # in block nodes we have to scan for super blocks 207 elif isinstance(node, VariableNode) and current_block: 208 if node.filter_expression.token == 'block.super': 209 if not hasattr(current_block.super, 'nodelist'): 210 raise TemplateSyntaxError("Cannot render block.super for blocks without a parent.") 211 nodes += _scan_placeholders(_get_nodelist(current_block.super), node_class, current_block.super) 212 # ignore nested blocks which are already handled 213 elif isinstance(node, BlockNode) and node.name in ignore_blocks: 214 continue 215 # if the node has the newly introduced 'child_nodelists' attribute, scan 216 # those attributes for nodelists and recurse them 217 elif hasattr(node, 'child_nodelists'): 218 for nodelist_name in node.child_nodelists: 219 if hasattr(node, nodelist_name): 220 subnodelist = getattr(node, nodelist_name) 221 if isinstance(subnodelist, NodeList): 222 if isinstance(node, BlockNode): 223 current_block = node 224 nodes += _scan_placeholders(subnodelist, node_class, current_block, ignore_blocks) 225 # else just scan the node for nodelist instance attributes 226 else: 227 for attr in dir(node): 228 obj = getattr(node, attr) 229 if isinstance(obj, NodeList): 230 if isinstance(node, BlockNode): 231 current_block = node 232 nodes += _scan_placeholders(obj, node_class, current_block, ignore_blocks) 233 return nodes 234 235 236def _scan_static_placeholders(nodelist): 237 from cms.templatetags.cms_tags import StaticPlaceholderNode 238 239 return _scan_placeholders(nodelist, node_class=StaticPlaceholderNode) 240 241 242def get_placeholders(template): 243 compiled_template = get_template(template) 244 245 placeholders = [] 246 nodes = _scan_placeholders(_get_nodelist(compiled_template)) 247 clean_placeholders = [] 248 249 for node in nodes: 250 placeholder = node.get_declaration() 251 slot = placeholder.slot 252 253 if slot in clean_placeholders: 254 warnings.warn("Duplicate {{% placeholder \"{0}\" %}} " 255 "in template {1}." 256 .format(slot, template, slot), 257 DuplicatePlaceholderWarning) 258 else: 259 validate_placeholder_name(slot) 260 placeholders.append(placeholder) 261 clean_placeholders.append(slot) 262 return placeholders 263 264 265def get_static_placeholders(template, context): 266 compiled_template = get_template(template) 267 nodes = _scan_static_placeholders(_get_nodelist(compiled_template)) 268 placeholders = [node.get_declaration(context) for node in nodes] 269 placeholders_with_code = [] 270 271 for placeholder in placeholders: 272 if placeholder.slot: 273 placeholders_with_code.append(placeholder) 274 else: 275 warnings.warn('Unable to resolve static placeholder ' 276 'name in template "{}"'.format(template), 277 Warning) 278 return placeholders_with_code 279 280 281def _get_block_nodes(extend_node): 282 parent = extend_node.get_parent(get_context()) 283 parent_nodelist = _get_nodelist(parent) 284 parent_nodes = parent_nodelist.get_nodes_by_type(BlockNode) 285 parent_extend_nodes = parent_nodelist.get_nodes_by_type(ExtendsNode) 286 287 if parent_extend_nodes: 288 # Start at the top 289 # Scan the extends node from the parent (if any) 290 nodes = _get_block_nodes(parent_extend_nodes[0]) 291 else: 292 nodes = OrderedDict() 293 294 # Continue with the parent template nodes 295 for node in parent_nodes: 296 nodes[node.name] = node 297 298 # Move on to the current template nodes 299 current_nodes = _get_nodelist(extend_node).get_nodes_by_type(BlockNode) 300 301 for node in current_nodes: 302 if node.name in nodes: 303 # set this node as the super node (for {{ block.super }}) 304 node.super = nodes[node.name] 305 nodes[node.name] = node 306 return nodes 307 308 309def _get_placeholder_nodes_from_extend(extend_node, node_class): 310 """ 311 Returns a list of placeholders found in the parent template(s) of this 312 ExtendsNode 313 """ 314 # This is a dictionary mapping all BlockNode instances found 315 # in the template that contains the {% extends %} tag 316 block_nodes = _get_block_nodes(extend_node) 317 block_names = list(block_nodes.keys()) 318 319 placeholders = [] 320 321 for block in block_nodes.values(): 322 placeholders.extend(_scan_placeholders(_get_nodelist(block), node_class, block, block_names)) 323 324 # Scan topmost template for placeholder outside of blocks 325 parent_template = _find_topmost_template(extend_node) 326 placeholders += _scan_placeholders(_get_nodelist(parent_template), node_class, None, block_names) 327 return placeholders 328 329 330def _find_topmost_template(extend_node): 331 parent_template = extend_node.get_parent(get_context()) 332 for node in _get_nodelist(parent_template).get_nodes_by_type(ExtendsNode): 333 # Their can only be one extend block in a template, otherwise django raises an exception 334 return _find_topmost_template(node) 335 # No ExtendsNode 336 return extend_node.get_parent(get_context()) 337