In each view of my application I need to have navigation menu prepared. So right now in every view I execute complicated query and store the menu in a dictionary which is passed to a template. In templates the variable in which I have the data is surrounded with "cache", so even though the queries are quite costly, it doesn't bother me.
But I don't want to repeat myself in every view. I guessed that the best place to prepare the menu is in my own context processor. And so I did write one, but I noticed that even when I don't use the data from the context processor, the queries used to prepare the menu are executed. Is there a way to "lazy load" such data from CP or do I have to use "low level" cache in CP? Or maybe there's a better solution to my problem?
Django has a SimpleLazyObject
. In Django 1.3, this is used by the auth context processor (source code). This makes user
available in the template context for every query, but the user is only accessed when the template contains {{ user }}
.
You should be able to do something similar in your context processor.
from django.utils.functional import SimpleLazyObject
def my_context_processor(request):
def complicated_query():
do_stuff()
return result
return {
'result': SimpleLazyObject(complicated_query)
If you pass a callable object into the template context, Django will evaluate it when it is used in the template. This provides one simple way to do laziness - just pass in callables:
def my_context_processor(request):
def complicated_query():
do_stuff()
return result
return {'result': complicated_query}
The problem with this is it does not memoize the call - if you use it multiple times, complicated_query
gets called multiple times.
The fix is to use something like SimpleLazyObject
as in the other answer, or to use something like this memoize decorator:
def memoize_nullary(f):
"""
Memoizes a function that takes no arguments.
"""
def func():
if not hasattr(func, 'retval'):
func.retval = f()
return func.retval
return func
def my_context_processor(request):
@memoize_nullary
def complicated_query():
do_stuff()
return result
return {'result': complicated_query}
Or, if the function already exists, you would do it like this:
from somewhere import complicated_query
def my_context_processor(request):
return {'result': memoize_nullary(complicated_query)}
I would prefer this method over SimpleLazyObject
because the latter can produce some strange bugs sometimes.
(I was the one who originally implemented LazyObject
and SimpleLazyObject
, and discovered for myself that there is curse on any code artefact labelled simple.)