Skip to content

Instantly share code, notes, and snippets.

@martinrusev
Created April 3, 2014 17:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save martinrusev/9958739 to your computer and use it in GitHub Desktop.
Save martinrusev/9958739 to your computer and use it in GitHub Desktop.
Django lazy context processor
# https://djangosnippets.org/snippets/3011/
# 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())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment