Login

Lazy context processor.

Author:
spookylukey
Posted:
December 12, 2013
Language:
Python
Version:
Not specified
Score:
0 (after 0 ratings)

Sometimes you have context variables that are needed on many pages in a site, but not all. You only want them to be evaluated when actually needed, especially if they are expensive calculations that do DB queries etc. The pattern to use is shown: put a callable into the context, not the final value, and also wrap the callable with memoize_nullary.

 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
# Sometimes you have context variables that are needed on many pages in a site, 
# but not all. You only want them to be evaluated when actually needed, especially 
# if they are expensive calculations that do DB queries etc. The pattern to use is 
# shown: put a callable into the context, not the final value, and also wrap the 
# callable with memoize_nullary.
# 
# This solution uses the fact that the Django template language will detect and 
# call any callables when they are referred to. So, if a template contains:
# 
#     {{ foo }}
# 
# then it will look up 'foo'. If 'foo' is callable, the result will be the output 
# of foo().
# 
# So, we can pass in a function or lambda to the template context instead of a 
# final value.
# 
# For expensive calculations, however, we need to ensure that the callable will 
# only be called once. The template language doesn't ensure this. So if you have:
# 
#    {% if permissions %}
#        {% for p in permissions %}
#           ...
#        {% endfor %}
#    {% endif %}
# 
# and 'permissions' is callable, then 'permissions' will be called twice.
# 
# This can be fixed using the 'memoize_nullary' utility. Note that this is 
# applied inside the my_context_processor function, so that every time 
# 'my_context_processor' is called, memoize_nullary gets called, and returns 
# fresh function objects. If you applied memoize_nullary at the module level, 
# subsequent calls to my_context_processor from subsequent requests would end up 
# outputting values from the previous request.

# Utility needed:

def memoize_nullary(f):
    """
    Memoizes a function that takes no arguments.  The memoization lasts only as
    long as we hold a reference to the returned function.
    """
    def func():
        if not hasattr(func, 'retval'):
            func.retval = f()
        return func.retval
    return func


# Example:

def expensive_calculation():
    return sum(range(1, 1000000))

def expensive_calc_2(request):
    return request.user.permission_set.all()
    

def my_context_processor(request):
    return {
        'numbers': memoize_nullary(expensive_calculation),
        'permissions': memoize_nullary(lambda: expensive_calc_2(request)),

        # Could also do it inline like this:
        # 'permissions': memoize_nullary(request.user.permission_set.all)
        # or
        # 'permissions': memoize_nullary(lambda: request.user.permission_set.all())
    }

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 9 months, 3 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 4 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 5 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

Please login first before commenting.