helpers.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. # -*- coding: utf-8 -*-
  2. from django.conf import settings
  3. from django.template.base import VariableNode, Variable, Context, Template
  4. from django.template.loader import get_template
  5. from django.template.loader_tags import BlockNode, ExtendsNode
  6. try:
  7. from django.template import engines
  8. except ImportError:
  9. engines = None
  10. def _get_nodelist(tpl):
  11. if isinstance(tpl, Template):
  12. return tpl.nodelist
  13. else:
  14. return tpl.template.nodelist
  15. def is_variable_extend_node(node):
  16. if hasattr(node, 'parent_name_expr') and node.parent_name_expr:
  17. return True
  18. if hasattr(node, 'parent_name') and hasattr(node.parent_name, 'filters'):
  19. if (node.parent_name.filters or
  20. isinstance(node.parent_name.var, Variable)):
  21. return True
  22. return False
  23. def get_context():
  24. if engines is not None:
  25. context = Context()
  26. context.template = Template('')
  27. return context
  28. else:
  29. return Context()
  30. def _extend_blocks(extend_node, blocks):
  31. """
  32. Extends the dictionary `blocks` with *new* blocks in the parent node
  33. (recursive)
  34. """
  35. # we don't support variable extensions
  36. if is_variable_extend_node(extend_node):
  37. return
  38. parent = extend_node.get_parent(get_context())
  39. # Search for new blocks
  40. for node in _get_nodelist(parent).get_nodes_by_type(BlockNode):
  41. if node.name not in blocks:
  42. blocks[node.name] = node
  43. else:
  44. # set this node as the super node (for {{ block.super }})
  45. block = blocks[node.name]
  46. seen_supers = []
  47. while (hasattr(block.super, 'nodelist') and
  48. block.super not in seen_supers):
  49. seen_supers.append(block.super)
  50. block = block.super
  51. block.super = node
  52. # search for further ExtendsNodes
  53. for node in _get_nodelist(parent).get_nodes_by_type(ExtendsNode):
  54. _extend_blocks(node, blocks)
  55. break
  56. def _extend_nodelist(extend_node):
  57. """
  58. Returns a list of namespaces found in the parent template(s) of this
  59. ExtendsNode
  60. """
  61. # we don't support variable extensions (1.3 way)
  62. if is_variable_extend_node(extend_node):
  63. return []
  64. blocks = extend_node.blocks
  65. _extend_blocks(extend_node, blocks)
  66. found = []
  67. for block in blocks.values():
  68. found += _scan_namespaces(block.nodelist, block)
  69. parent_template = extend_node.get_parent(get_context())
  70. # if this is the topmost template, check for namespaces outside of blocks
  71. if not _get_nodelist(parent_template).get_nodes_by_type(ExtendsNode):
  72. found += _scan_namespaces(
  73. _get_nodelist(parent_template),
  74. None
  75. )
  76. else:
  77. found += _scan_namespaces(
  78. _get_nodelist(parent_template),
  79. extend_node
  80. )
  81. return found
  82. def _scan_namespaces(nodelist, current_block=None):
  83. from sekizai.templatetags.sekizai_tags import RenderBlock
  84. found = []
  85. for node in nodelist:
  86. # check if this is RenderBlock node
  87. if isinstance(node, RenderBlock):
  88. # resolve it's name against a dummy context
  89. found.append(node.kwargs['name'].resolve({}))
  90. found += _scan_namespaces(node.blocks['nodelist'], node)
  91. # handle {% extends ... %} tags if check_inheritance is True
  92. elif isinstance(node, ExtendsNode):
  93. found += _extend_nodelist(node)
  94. # in block nodes we have to scan for super blocks
  95. elif isinstance(node, VariableNode) and current_block:
  96. if node.filter_expression.token == 'block.super':
  97. if hasattr(current_block.super, 'nodelist'):
  98. found += _scan_namespaces(
  99. current_block.super.nodelist,
  100. current_block.super
  101. )
  102. return found
  103. def get_namespaces(template):
  104. compiled_template = get_template(template)
  105. return _scan_namespaces(_get_nodelist(compiled_template))
  106. def validate_template(template, namespaces):
  107. """
  108. Validates that a template (or it's parents if check_inheritance is True)
  109. contain all given namespaces
  110. """
  111. if getattr(settings, 'SEKIZAI_IGNORE_VALIDATION', False):
  112. return True
  113. found = get_namespaces(template)
  114. for namespace in namespaces:
  115. if namespace not in found:
  116. return False
  117. return True
  118. def get_varname():
  119. return getattr(settings, 'SEKIZAI_VARNAME', 'SEKIZAI_CONTENT_HOLDER')
  120. class Watcher(object):
  121. """
  122. Watches a context for changes to the sekizai data, so it can be replayed
  123. later. This is useful for caching.
  124. NOTE: This class assumes you ONLY ADD, NEVER REMOVE data from the context!
  125. """
  126. def __init__(self, context):
  127. self.context = context
  128. self.frozen = dict(
  129. (key, list(value)) for key, value in self.data.items()
  130. )
  131. @property
  132. def data(self):
  133. return self.context.get(get_varname(), {})
  134. def get_changes(self):
  135. sfrozen = set(self.frozen)
  136. sdata = set(self.data)
  137. new_keys = sfrozen ^ sdata
  138. changes = {}
  139. for key in new_keys:
  140. changes[key] = list(self.data[key])
  141. shared_keys = sfrozen & sdata
  142. for key in shared_keys:
  143. old_set = set(self.frozen[key])
  144. new_values = [
  145. item for item in self.data[key] if item not in old_set
  146. ]
  147. changes[key] = new_values
  148. return changes