If you’re using generic views in Django, you probably do something like this in your URLconf:
pages = Page.objects.all()
[...]
url(r"^pages/(?P<slug>.*)$", object_detail, { "queryset" : pages }),
Now, say you want to show the title of these pages in a menu on each page. You add a new context_processor:
from myapp.pages.models import Page
pages = Page.objects.all()
def add_pages():
return { "pages" : pages }
When you add a page now, you’ll notice that it won’t appear in your menu because the queryset has been evaluated the first time it has been accessed, and will remain the same as long as the python interpreter isn’t restarted. However, generic views appear to do the same thing and still show the most current content. They accomplish this by doing
queryset = queryset._clone()
on each call. A cloned queryset will always be non-evaluated at first, which means that generic views will hit the database each time they’re called. You could of course do that as well, but you feel bad about grabbing those pages out of the database at every single request. My solution for this in pyplog was the following:
from django.db.models.signals import post_delete, post_save
class CachedQuerySetManager:
"""
Manages a list of querysets that are automatically updated
whenever an object is added to or deleted from one of them.
"""
def __init__(self):
self.querysets = {}
def __getitem__(self, key):
return self.querysets[key]["queryset"]
def add(self, model, manager = "_default_manager"):
self.querysets[model] = {
"queryset" : getattr(model, manager).all(),
"manager" : manager,
}
post_delete.connect(self.update, sender = model)
post_save.connect(self.update, sender = model)
def remove(self, model):
post_delete.disconnect(self.update, sender = model)
post_save.disconnect(self.update, sender = model)
self.querysets.__delitem__(model)
def update(self, sender, **kwargs):
self.querysets[sender]["queryset"] = \
getattr(sender, self.querysets[sender]["manager"]).all()
cached_querysets = CachedQuerySetManager()
The above example would look like this:
from myapp.pages.models import Page
from myapp.util.query import cached_querysets
cached_querysets.add(Page)
def add_pages():
return { "pages" : cached_querysets[Page] }
Now the queryset is reset everytime a page is added or deleted. Note that you should of course only do this with QuerySets that you can afford to completely keep in memory (read: just a couple of objects). The QuerySet passed to a generic view never gets evaluated itself, so that it’s very cheap to have it around. The above code however keeps the evaluated QuerySet and will increase memory usage in favor of database load.
You probably won’t need this if you’re using traditional caching (memcached), but for the folks that choose to go without it, this little helper might come in handy.
Note that the CachedQuerySetManager class above is part of
pyplog and therefore published under the GPL license. See pyplog’s LICENSE file for more information.