15

I have recently implemented Django's excellent cache framework. However from what I understand Django will not cache a view that is passed parameters in a get request. I have an Ajax view that is passed get parameters that I would like to cache for X seconds, what would be an easy way to do this?

In psuedo code I currently have a URL:

http://mysites/ajaxthing/?user=foo&items=10

I would like to cache any this url as long as it has the same get parameters.

I'm currently using the cache decorators in my view:

myview(stuff)

myview = cache_page(myview, 60 * 3)

I did read about django's vary headers but it went a little over my head, and I'm not even sure its the correct solution

Tristan Brotherton
  • 2,533
  • 7
  • 32
  • 38
  • 2
    Is there some reason you don't want to just use: `http://mysites/ajaxthing/user/foo/items/10/` This way you can side step this problem nicely. – TM. Oct 19 '09 at 19:57
  • Please read euqidron's answer below, this behaviour has changed in Django 1.3. – Harley Holcombe May 02 '11 at 12:03
  • The premise of this question is that "Django will not cache a view that is passed parameters in a get request" but per Kyle's answer below, this is not true. Django includes query parameters in the cache key. – YPCrumble Nov 26 '19 at 23:04
  • This should be no longer an issue in Django 1.3+. See: https://docs.djangoproject.com/en/dev/topics/cache/#using-vary-headers – nord Mar 27 '11 at 00:26

5 Answers5

19

Right, vary headers is not the correct solution, it's used when you want to cache based on client request headers like user-agent etc.

You'll need to use low-level API or template fragment caching. It depends on your views really.

With low-level API it looks something like this:

from django.core.cache import cache

def get_user(request):
    user_id = request.GET.get("user_id")
    user = cache.get("user_id_%s"%user_id)
    if user is None:
        user = User.objects.get(pk=user_id)
        cache.set("user_id_%s"%user_id, user, 10*60) # 10 minutes
    ...
    ..
    .
Seb
  • 17,141
  • 7
  • 38
  • 27
9

Yes, you can use django-view-cache-utils, here is code for your case:

from view_cache_utils import cache_page_with_prefix
from django.utils.hashcompat import md5_constructor
...
@cache_page_with_prefix(60*15, lambda request: md5_constructor(request.get_full_path()).hexdigest())
def my_view(request):
    ...
Roman Dolgiy
  • 1,521
  • 1
  • 15
  • 21
  • 2
    this approach is nice, but we hit two major issues with the code, 1. there could be conflict for two views with same URL arguments, 2. the str(request.GET) may return differently, because dictionary entry display has no specified order. I later used this code instead: @cache_page_with_prefix(60*15, lambda request: md5_constructor(request.build_absolute_uri()).hexdigest()). – Walty Yeung Aug 30 '11 at 00:50
  • @roman-dolgiy what is the equivalent of md5_constructor in django2? – Jenish Feb 16 '19 at 06:30
8

From my reading of the source code and empirical testing, the @cache_page decorator natively handles GET parameters correctly (at least in Django 2.2).

Digging through the source:

  1. The decorator is defined in django.views.decorators.cache
  2. It calls django.utils.decorators.decorator_from_middleware_with_args()
  3. Which calls django.utils.decorators.make_middleware_decorator()
  4. Which is a silly level of complex. A veritable onion of functions returning functions. The important bit is it calls middleware.process_request() where 'middleware' is django.middleware.cache.CacheMiddleware.
  5. Which calls django.utils.cache.get_cache_key() to generate the cache key.
  6. Which calls django.utils.cache._generate_cache_header_key().
  7. Which calls request.build_absolute_uri() where 'request' is django.http.request.HttpRequest
  8. Which calls django.http.request.HttpRequest.get_full_path()
  9. Which calls django.http.request.HttpRequest._get_full_path()
  10. Which, finally, includes self.META.get('QUERY_STRING', '') in the string it returns.

On the flip side, when the response is complete a similar path goes through middleware.process_response() which eventually calls django.utils.cache._generate_cache_header_key() to determine where to store the response in the cache.

Empirically, I can see that requests to the decorated view are being cached and the response changes when the GET parameters change.

Kyle
  • 828
  • 8
  • 8
  • 1
    I can confirm this. @cache_page decorator natively handles querystring parameters (?param1=value1&param2=value2) correctly. If the querystring is the same, then the cache is used. Tested in Django 3.2.9. – Augusto Destrero Nov 13 '21 at 08:22
3

It appears that you no longer need to do anything more complicated than placing @cache_page([length of time]) above your View function you are trying to cache, irrespective of whether you have parameters in the URL.

For example, if you have a url like:

http://example.com/user/some_user_id

Your view function in views.py would look something like this:

from django.views.decorators.cache import cache_page
...

@cache_page(60 * 10)
def get_user_detail(request, user_id=None):
    ...
    return render(...)
Vivek
  • 137
  • 1
  • 11
  • I tried this but got an AttributeError from the urls.py when using generic views: url(r'^(?P[0-9]+)/teams/(?P[0-9]+)/detail/$', views.TeamDetailView.as_view(), name='team_detail'), AttributeError: 'function' object has no attribute 'as_view' – simi May 09 '16 at 15:44
-1

a bit late, but you can use django-view-cache-utils for that.

Mikhail Korobov
  • 21,908
  • 8
  • 73
  • 65