-
Notifications
You must be signed in to change notification settings - Fork 1
/
etc_misc.py
162 lines (115 loc) · 4.82 KB
/
etc_misc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
from copy import deepcopy
from functools import partial
from django import VERSION
from django import template
from django.template import TemplateDoesNotExist
from django.template.base import UNKNOWN_SOURCE, Lexer, Parser
from django.template.loader_tags import do_include, Node
try:
from django.template.loader_tags import construct_relative_path
# To prevent AttributeError: 'Parser' object has no attribute 'origin'
def construct_relative_path_(parser, name):
return construct_relative_path(parser.origin.template_name, name)
except ImportError:
construct_relative_path_ = lambda parser, name: name # Sorry sub here for now.
from ..toolbox import get_site_url
if VERSION >= (1, 9, 0):
get_lexer = partial(Lexer)
else:
get_lexer = partial(Lexer, origin=UNKNOWN_SOURCE)
register = template.Library()
@register.simple_tag(takes_context=True)
def site_url(context):
"""Tries to get a site URL from environment and settings.
See toolbox.get_site_url() for description.
Example:
{% load etc_misc %}
{% site_url %}
"""
return get_site_url(request=context.get('request', None))
class DynamicIncludeNode(Node):
context_key = '__include_context'
def __init__(self, template, *args, extra_context=None, isolated_context=False, **kwargs):
self.fallback = kwargs.pop('fallback', None)
# Below is implementation from Django 2.1 generic IncludeNode.
self.template = template
self.extra_context = extra_context or {}
self.isolated_context = isolated_context
super(DynamicIncludeNode, self).__init__(*args, **kwargs)
def render_(self, tpl_new, context):
template = deepcopy(self.template) # Do not mess with global template for threadsafety.
template.var = tpl_new # Cheat a little
# Below is implementation from Django 2.1 generic IncludeNode.
template = template.resolve(context)
# Does this quack like a Template?
if not callable(getattr(template, 'render', None)):
# If not, try the cache and get_template().
template_name = template
cache = context.render_context.dicts[0].setdefault(self, {})
template = cache.get(template_name)
if template is None:
template = context.template.engine.get_template(template_name)
cache[template_name] = template
# Use the base.Template of a backends.django.Template.
elif hasattr(template, 'template'):
template = template.template
values = {
name: var.resolve(context)
for name, var in self.extra_context.items()
}
if self.isolated_context:
return template.render(context.new(values))
with context.push(**values):
return template.render(context)
def render(self, context):
render_ = self.render_
try:
return render_(
tpl_new=Parser(get_lexer(self.template.var).tokenize()).parse().render(context),
context=context)
except TemplateDoesNotExist:
fallback = self.fallback
if not fallback: # pragma: nocover
raise
return render_(tpl_new=fallback.var, context=context)
@register.tag('include_')
def include_(parser, token):
"""Similar to built-in ``include`` template tag, but allowing
template variables to be used in template name and a fallback template,
thus making the tag more dynamic.
.. warning:: Requires Django 1.8+
Example:
{% load etc_misc %}
{% include_ "sub_{{ postfix_var }}.html" fallback "default.html" %}
"""
bits = token.split_contents()
dynamic = False
# We fallback to built-in `include` if a template name contains no variables.
if len(bits) >= 2:
dynamic = '{{' in bits[1]
if dynamic:
fallback = None
bits_new = []
for bit in bits:
if fallback is True:
# This bit is a `fallback` argument.
fallback = bit
continue
if bit == 'fallback':
fallback = True
else:
bits_new.append(bit)
if fallback:
fallback = parser.compile_filter(construct_relative_path_(parser, fallback))
token.contents = ' '.join(bits_new)
token.contents = token.contents.replace('include_', 'include')
include_node = do_include(parser, token)
if dynamic:
# swap simple include with dynamic
include_node = DynamicIncludeNode(
include_node.template,
extra_context=include_node.extra_context,
isolated_context=include_node.isolated_context,
fallback=fallback or None,
)
return include_node