template tag for highlighting currently active page

  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

  1. Parse custom template tag's args or kwargs by t_rybik 3 years, 7 months ago
  2. Active page class for selected menu items by kunitoki 1 year, 6 months ago
  3. Active class for navigation link by cschand 4 years, 7 months ago
  4. Active link by ronnie 2 years, 4 months ago
  5. Page numbers with ... like in Digg by Ciantic 5 years ago

Comments

(Forgotten your password?)