# 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()) }