This module defines a template tag {% ifactive %}
that lets you determine which page is currently active. A common use case is for highlighting the current page in a navigation menu.
It uses the same syntax for specifying views as the {% url %}
tag, and determines whether a particular page is active by checking if the same view is called with the same arguments, instead of just comparing URLs. As a result, it can handle cases where different URLs map to the same view.
Example usage:
<a {% ifactive request page1 %}class='active'{% endifactive %}
href='{% url page1 %}'>Page 1</a>
<a {% ifactive request page2 %}class='active'{% endifactive %}
href='{% url page2 %}'>Page 2</a>
...
<a {% ifactive request pageN %}class='active'{% endifactive %}
href='{% url pageN %}'>Page N</a>
It also can be extended to further reduce the amount of repetitive code. For instance, you could write a template tag that has class='active' as the block contents, and always gets the variable from context['request']:
def do_activeif(parser, token):
"e.g. <a {% activeif page1 %} href='{% url page1 %}'>Page 1</a>"
tag_args = token.contents.split(' ')
view_name = tag_args[1]
args, kwargs = _parse_url_args(parser, tag_args[2:])
return ActiveNode('request', view_name, args, kwargs, NodeList(TextNode('class="active"')))
register.tag('activeif', do_activeif)
Note that you will have to add the ActiveViewMiddleware to your settings.py:
MIDDLEWARE_CLASSES = (
...,
'path.to.this.module.ActiveViewMiddleware'
)
You may also want to add this template tag as a built-in, so you don't have
to call {% load %}
. In somewhere that's imported by default (e.g. where you
define your views), add:
from django import template
template.add_to_builtins('path.to.this.module')
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 163 164 165 166 167 168 169 170 171 172 | from django.utils.encoding import smart_str
from django.core.urlresolvers import get_callable, RegexURLResolver, get_resolver
from django.template import Node, NodeList, TextNode, TemplateSyntaxError, Library, resolve_variable
from django.conf import settings
register = Library()
def do_ifactive(parser, token):
"""
Defines a conditional block tag, "ifactive" that switches based on whether the active request
is being handled by a particular view (with optional args and kwargs).
Has the form:
{% ifactive request path.to.view %}
[Block to render if path.to.view is the active view]
{% else %}
[Block to render if path.to.view is not the active view]
{% endifactive %}
'request' is a context variable expression which resolves to the HttpRequest object for the current
request. (Additionally, the ActiveViewMiddleware must be installed for this to work.)
'path.to.view' can be a string with a python import path (which must be mentioned in the urlconf),
or a name of a urlpattern (i.e., same as the argument to the {% url %} tag).
You can also pass arguments or keyword arguments in the same form as accepted by the {% url %} tag,
e.g.:
{% ifactive request path.to.view var1="bar",var2=var.prop %}...{% endifactive %}
or:
{% ifactive request path.to.view "bar",var.prop %}...{% endifactive %}
The else block is optional.
"""
end_tag = 'endifactive'
active_nodes = parser.parse((end_tag,'else'))
end_token = parser.next_token()
if end_token.contents == 'else':
inactive_nodes = parser.parse((end_tag,))
parser.delete_first_token()
else:
inactive_nodes = None
tag_args = token.contents.split(' ')
if len(tag_args) < 3:
raise TemplateSyntaxError("'%s' takes at least two arguments"
" (context variable with the request, and path to a view)" % tag_args[0])
request_var = tag_args[1]
view_name = tag_args[2]
args, kwargs = _parse_url_args(parser, tag_args[3:])
return ActiveNode(request_var, view_name, args, kwargs, active_nodes, inactive_nodes)
register.tag('ifactive', do_ifactive)
class ActiveNode(Node):
def __init__(self, request_var, view_name, args, kwargs, active_nodes, inactive_nodes=None):
self.request_var = request_var
self.view_name = view_name
self.args = args
self.kwargs = kwargs
self.active_nodes = active_nodes
self.inactive_nodes = inactive_nodes
def render(self, context):
request = resolve_variable(self.request_var, context)
view, default_args = _get_view_and_default_args(self.view_name)
if getattr(request, '_view_func', None) is view:
resolved_args = [arg.resolve(context) for arg in self.args]
if request._view_args == resolved_args:
resolved_kwargs = dict([(k, v.resolve(context)) for k, v in self.kwargs.items()])
resolved_kwargs.update(default_args)
if request._view_kwargs == resolved_kwargs:
return self.active_nodes.render(context)
if self.inactive_nodes is not None:
return self.inactive_nodes.render(context)
else:
return ''
def _get_patterns_map(resolver, default_args=None):
"""
Recursively generates a map of
(pattern name or path to view function) -> (view function, default args)
"""
patterns_map = {}
if default_args is None:
default_args = {}
for pattern in resolver.url_patterns:
pattern_args = default_args.copy()
if isinstance(pattern, RegexURLResolver):
pattern_args.update(pattern.default_kwargs)
patterns_map.update(_get_patterns_map(pattern, pattern_args))
else:
pattern_args.update(pattern.default_args)
if pattern.name is not None:
patterns_map[pattern.name] = (pattern.callback, pattern_args)
# HACK: Accessing private attribute of RegexURLPattern
callback_str = getattr(pattern, '_callback_str', None)
if callback_str is not None:
patterns_map[pattern._callback_str] = (pattern.callback, pattern_args)
return patterns_map
_view_name_cache = None
def _get_view_and_default_args(view_name):
"""
Given view_name (a path to a view or a name of a urlpattern,
returns the view function and a dict containing any default kwargs
that are specified in the urlconf for that view.
"""
global _view_name_cache
if _view_name_cache is None:
_view_name_cache = _get_patterns_map(get_resolver(None))
try:
return _view_name_cache[view_name]
except KeyError:
raise KeyError("%s does not match any urlpatterns" % view_name)
def _parse_url_args(parser, bits):
"""
Parses URL parameters in the same way as the {% url %} tag.
"""
args = []
kwargs = {}
for bit in bits:
for arg in bit.split(","):
if '=' in arg:
k, v = arg.split('=', 1)
k = k.strip()
kwargs[smart_str(k,'ascii')] = parser.compile_filter(v)
elif arg:
args.append(parser.compile_filter(arg))
return args, kwargs
class ActiveViewMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
"""
Records the view function used on this request and its *args and **kwargs.
Needed by {% ifactive %} to determine if a particular view is currently active.
"""
request._view_func = view_func
request._view_args = list(view_args)
request._view_kwargs = view_kwargs
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 6 months ago
Comments
Please login first before commenting.