How to cache a list of requests in Django views? - django

I'd like to keep the list of 'Last Seen' 20 requests into a Django view. Also I want to avoid creating a separate model for it and just keep the requested urls in memcached, by pushing each new request to a fixed size queue and then retrieve the queue in views.
But since cache is just key:value dictionary I'm wondering how best to achieve this?

One way to do this with memcache would be to store a pickled python object as a single key in the cache.
In this case we could use a Python deque which has exactly the properties we'd want for a list of 20 most recent items
Each time we record a new page view we need to update the deque, which means to get it, unpickle it, append, pickle it and set the new value back to memcache.
Fortunately Django's cache framework will handle the pickling and unpickling for us. However one thing we need to take care of is the possibility of a race condition - in other words if another process also updates the deque after we get our copy and before we have a chance to set it back to the cache.
For this reason we should use memcache's CAS ('compare-and-set') operation. There is an extended Django cache backend that enables CAS available here:
https://github.com/anentropic/django-cas-cache
pip install django-cas-cache
We'd have some code in a custom Django middleware to update the cache on each page view, looking roughly like this:
middleware.py
from collections import deque
from django.core.cache import cache
class LastSeenMiddleware(object):
def process_response(request, response):
# you might want some logic like this to only
# record successful requests
if response.status != 200:
return response
# in case we don't already have a deque, try to add
# (add will not overwrite if key already exists)
added = cache.add('last_seen', deque([request.path], maxlen=20))
if not added:
def update(val):
val.append(request.path)
return val
cache.cas('last_seen', update)
return response
views.py
from django.core.cache import cache
from django.shortcuts import render_to_response
def myview(request):
last_seen = cache.get('last_seen')
# whatever
return render_to_response('mytemplate.html', {'last_seen': last_seen})
settings.py
CACHES = {
'default': {
'BACKEND': 'cascache.backends.memcached.MemcachedCASCache',
'LOCATION': '127.0.0.1:11211',
}
}
# as a response processor, our middleware should probably come
# first in this list, in order to run *last*
MIDDLEWARE_CLASSES = (
'myapp.middleware.LastSeenMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
)

Related

How to cache view for all users in Django

I'm trying to use a Memcached instance of AWS ElastiCache with a Django project. It seems to be caching a view for a user, but if you come in on a different PC, it isn't cached until called from that PC (or same PC with different browser).
I'm not sure what I've got wrong.
Within settings.py I have
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': os.environ.get('CACHE_LOCATION','127.0.0.1:11211'),
}
}
MIDDLEWARE = [
'core.middleware.DenyIndexMiddleware',
'core.middleware.XForwardedForMiddleware',
'core.middleware.PrimaryHostRedirectMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.contrib.redirects.middleware.RedirectFallbackMiddleware',
'masquerade.middleware.MasqueradeMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.contrib.sites.middleware.CurrentSiteMiddleware',
'cms.middleware.user.CurrentUserMiddleware',
'cms.middleware.page.CurrentPageMiddleware',
'cms.middleware.toolbar.ToolbarMiddleware',
'cms.middleware.language.LanguageCookieMiddleware',
'cms.middleware.utils.ApphookReloadMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
]
I've then cached the views using cache_page
path('<str:service_type>/<str:location>/', cache_page(60*60)(views.canonical_search), name="canonical-search"),
How do I cache the site so that the page is cached irrespective of the user?
EDIT
I've noticed that it never caches when using the user is logged in.
Watch out for the Vary header, that cache_page() takes into account.
Usually, some middlewares may add a Vary header, for example :
CsrfViewMiddleware adds Cookie,
GZipMiddlewareadds Accept-Encoding
LanguageCookieMiddleware may add Accept-Language
meaning that as soon as you have a different Cookie (session), encoding, or language, you have a different version of cache for your page.
As for you case, the CsrfViewMiddleware may be the problem, you can add the decorator #csrf_exempt to your view so that the Vary: Cookie header is not set in the response.
More info at https://docs.djangoproject.com/en/3.0/topics/cache/#using-vary-headers
Though Django documentation, you can read this:
Django’s cache framework
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
}
}
If you need to store a page in the local
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': 'c:/foo/bar',
}
}
But, my recommendation is store the results (database result, assets, etc), as #PVSK show in this thread:
from django.core.cache import cache
def sample(request):
cached_data = cache.get_many(['query1', 'query2'])
if cached_data:
return render(request, 'sample.html', {'query1': cached_data['query1'], 'query2': cached_data['query2']})
else:
queryset1 = Model.objects.all()
queryset2 = Model2.objects.all()
cache.set_many({'query1': queryset1 , 'query2': queryset2 }, None)
return render(request, 'sample.html', {'query1': queryset1 , 'query2': queryset2})
Hm, at first I wondered if you're running into some cache default limitations. You are not using OPTIONS in your CACHE backend definition, so the cache is limited to 300 entries per default.
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': os.environ.get('CACHE_LOCATION','127.0.0.1:11211'),
'OPTIONS': {
'MAX_ENTRIES': 1000
}
}
}
The next possible problem, that we also had, is that the cache_key generation takes the full QUERY_STRING into account, so (the ?param=bla). But you already stated that the url is the same for all users.
Next up, as SebCorbin correctly identified, are the potential Vary issues.
UpdateCacheMiddleware will never cache a cookie-setting response to a cookie-less request.
def process_response(self, request, response):
#...
# Don't cache responses that set a user-specific (and maybe security
# sensitive) cookie in response to a cookie-less request.
if not request.COOKIES and response.cookies and has_vary_header(response, 'Cookie'):
return response
# ...
The order of execution of middlewares is top-to-bottom for process_request and bottom-to-top for process_response.
I suspect that one of lower middlewares (or the view) is setting a cookie, so you might be able to work around this issue by moving the 'django.middleware.cache.UpdateCacheMiddleware' below the offending middleware, but you risk loosing features if you don't move feature-middlewares like LocaleMiddleware as well.
If your view code is setting cookies you'll need to switch to the low-level cache API to cache costly operations (or move the cookie logic into a middleware that lives above the UpdateCacheMiddleware middleware).

how to define a custom wsgi middleware for django

I want to write a custom wsgi middleware, which is called on every incoming request. It checks the url, and if the user is authenticated and allows the request to proceed or rejects it.
What is the best way to add wsgi middleware in django ?
Why do you want to do this as a WSGI middleware, specifically? Django doesn't operate particularly well with those - there was some work a few years ago to try and harmonize Django middleware with WSGI middleware, but it didn't really get anywhere.
Django has its own version of middleware, which is very well documented, and your request could be done in about three lines.
You do not need a wsgi middleware here and can easily use django middleware.
some_app/middleware.py
from django.http import HttpResponseForbidden
class AuthenticateMiddleware(object):
def process_request(self, request):
#do something with request.path
if request.user.is_authenticated():
#can do something or just pass
#but do not return a response from here
else:
#return a response from here so that view doesn't get called
return HttpResponseForbidden()
process_request() of any middleware is called before the view is processed. If you return an instance of HttpResponse from this method then the view will not be called. Since HttpResponseForbidden is a subclass of HttpResponse, so the view will not be called if the user is not authenticated.
You need to add this custom middleware to MIDDLEWARE_CLASSES.
settings.py
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'some_app.middleware.AuthenticationMiddleware',
)

You cannot add messages without installing django.contrib.messages.middleware.MessageMiddleware

I am new to Django.
I am trying to run an app and I need to add a new user to admin. The server is running. When I enter the info fir the new user and hit "save" I get the error below. I am using django-trunk.
MessageFailure at /admin/auth/user/add/
You cannot add messages without installing
django.contrib.messages.middleware.MessageMiddleware
Request Method: POST
Request URL: http://localhost:8000/admin/auth/user/add/
Django Version: 1.6.dev20130403090717
Exception Type: MessageFailure
Exception Value: You cannot add messages without installing django.contrib.messages.middleware.MessageMiddleware
Any ideas of what might be happening?
For me the problem was specific to unit testing. It turns out that some middleware won't work in some kinds of unit tests, more info here:
https://code.djangoproject.com/ticket/17971
and here:
Why don't my Django unittests know that MessageMiddleware is installed?
My solution was to just mock out the messages framework for those tests, there may be better solutions (the django test client?)
Check if you have django.contrib.messages in INSTALLED_APPS and django.contrib.messages.middleware.MessageMiddleware in MIDDLEWARE_CLASSES.
If you are running normal django code, you should add
django.contrib.messages.middleware.MessageMiddleware to your middlewares as others have suggested
If you are running a test case and using request factory then as #hwjp answered above, it's a bug (that won't be fixed). The request factory doesn't call the middlewares and developers don't intend to change this behaviour.
There is however a simple solution.
in your test settings file (i.e settings/test.py) add the following line
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
in your test code you can write code like this
request = RequestFactory().get("/")
# Add support django messaging framework
request._messages = messages.storage.default_storage(request)
and that's it. Passing this request object to any code that uses django messages will work without a problem.
Check if it is
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
)
instead of
MIDDLEWARE = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
)
Tuple name should be MIDDLEWARE_CLASSES
MIDDLEWARE_CLASSES depreciated https://docs.djangoproject.com/en/2.1/releases/1.10/#id3
2018 update
In django 2.0+ name in settings was changed.
Right now use MIDDLEWARE instead of MIDDLEWARE_CLASSES name of list in settings!
If the request is needed in tests, it can be mocked as suggested by #Ramast.
I found the solution mentioned in the bug ticket (closed, won't fix) to be helpful.
from django.contrib.messages.storage.fallback import FallbackStorage
from django.test import RequestFactory
def dummy_request():
"""Dummy request for testing purposes with message support."""
request = RequestFactory().get('/')
# Add support django messaging framework
setattr(request, 'session', 'session')
setattr(request, '_messages', FallbackStorage(request))
return request
Probably you put a wrong WSGI_request when usually called request as a parameter to add_message() method
I met the same error.
You have to notice the order of middleware in MIDDLEWARE_CLASSES.
Insert the corresponding middleware in the final.Like this,
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
)
Note the order arrangement.
If your issue is simulating these middlewares during unit testing, you can spoof this by writing a couple of little helpers:
def add_session_to_request(request: HttpRequest) -> HttpRequest:
"""Adds session support to a RequestFactory generated request."""
middleware = SessionMiddleware(get_response=lambda request: None)
middleware.process_request(request)
request.session.save()
return request
def add_messages_to_request(request: HttpRequest) -> HttpRequest:
"""Adds message/alert support to a RequestFactory generated request."""
request._messages = FallbackStorage(request)
return request
You can then call these in your test function at the point your request needs to have the middleware bound and just pass in the request you're using, which should keep your tests happy. :)
# To suppress the errors raised when triggering .messages through APIRequestFactory(),
# we need to bind both a session and messages storage on the fly
add_session_to_request(replacement_request)
add_messages_to_request(replacement_request)

django post request data caching

hi have a template with a form and many inputs that pass some data trough a POST request to a view, that process them and send the result to another template. in the final template, if i use the browser back button to jump to the first view, i can see again old data. i refresh the page and i insert new data, i submit again but some old data remain when i see the final view. the problem remain even if i restart the debug server. how can i prevent it? it seems that there's some data-caching that i can solve only flushing browser cache. this is the view code: http://dpaste.com/640956/ and the first template code: http://dpaste.com/640960/
any idea?
tnx - luke
Is not django who populate form. Is cache navigator. You should switch off cache navigator. I use a custom middleware to do this:
from django.http import HttpResponse
class NoCacheMiddleware(object):
def process_response(self, request, response):
response['Pragma'] = 'no-cache'
response['Cache-Control'] = 'no-cache must-revalidate proxy-revalidate no-store'
return response
Remember to add middleware on settings.py:
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'ghap.utils.middleware.NoCacheMiddleware',
)
Maybe autocomplete="off" in the form tag can help you.
https://developer.mozilla.org/en/How_to_Turn_Off_Form_Autocompletion

Django - Losing Auth Session

I am with some trouble in Django...
After login I am losing auth session for some pages.
If I access "accounts/login/","accounts/logout/",""accounts/register/" the session always will be there, but if I access different page I cant access the user variable.
This is strange because I am using the same "base.html" for all pages and inside has the logic "if user.is_authenticated", how I said this condition is true just when I access pages that have "accounts" in the URL.
in the settings file I enabled theses three middleware:
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
)
Thanks
Just a guess here: are you including RequestContext in your context in the views that you cannot access user?
In other words, if you call generic views the RequestContext is automatically included but if you are using render_to_response() then you need to call it like this:
return render_to_response('template_name',
{ your context dict },
context_instance=RequestContext(request))