I'm trying to expire a view-level cache on a model's post_save (that was set via https://docs.djangoproject.com/en/1.3/topics/cache/?from=olddocs#the-per-view-cache). I did some googling and found this answer here on SO: Expire a view-cache in Django? but it's not working for me.
I asked around in the #django room on freenode and the consensus was that this was probably due to the recent caching changes made in 1.3
Does anyone have any idea on how I can wipe out the cache entry for a model keyed off it's get_absolute_url()?
I figured it out!
Cheers to ilvar for pointing me in the right direction. My implementation is below. I created a property named cache_key and added a post_save receiver onto the sub class of the models whose view-level caches I needed to clear after they had been updated. Suggestions for improvement are always welcome!
from django.conf import settings
from django.core.cache import cache
from django.db.models.signals import post_save
from django.http import HttpRequest
from django.utils.cache import _generate_cache_header_key
from someapp.models import BaseModelofThisClass
class SomeModel(BaseModelofThisClass):
...
#property
def cache_key(self):
# Create a fake request object
request = HttpRequest()
# Set the request method
request.method = "GET"
# Set the request path to be this object's permalink.
request.path = self.get_absolute_url()
# Grab the key prefix (if it exists) from settings
key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
# Generate this object's cache key using django's key generator
key = _generate_cache_header_key(key_prefix, request)
# This is a bit hacky but necessary as I don't know how to do it
# properly otherwise. While generating a cache header key, django
# uses the language of the HttpRequest() object as part of the
# string. The fake request object, by default, uses
# settings.LANGUAGE_CODE as it's language (in my case, 'en-us')
# while the true request objects that are used in building views
# use settings.LANGUAGES ('en'). Instead of replacing the segment
# of the string after the fact it would be far better create a more
# closely mirrored HttpRequest() object prior to passing it to
# _generate_cache_header_key().
key = key.replace(settings.LANGUAGE_CODE, settings.LANGUAGES[settings.DEFAULT_LANGUAGE][0])
return key
#receiver(post_save)
def clear_cache_for_this_item(sender, instance, **kwargs):
# If this is a sub-class of another model
if sender not in BaseModelofThisClass.__subclasses__():
return
else:
cache.delete(instance.cache_key)
Django's caching middleware is using this to generate a cache key for the request. So, you can make a fake request with desired path and get a cache key. Then simply delete it from the cache.
P.S. cache_page decorator also uses that middlewares so it should work the same way.
Related
I have a django server with an admin panel.
Different users make changes there and this is saved via auditlog in the database and displayed in the "history".
But there are situations when a user enters under the account of another user and makes changes on his behalf.
In order to identify from which device this or that change was made, it was a nice decision to also record data about the IP of the user from whom the change was made, and his unique device number.
By overloading several methods in the "AuditlogMiddleware" class, I got the desired result via "uuid.UUID(int=uuid.getnode())".
(Tested locally, because the prod server is heavily loaded and there is no way to do test committees)
from __future__ import unicode_literals
import threading
import time
from auditlog.middleware import AuditlogMiddleware
threadlocal = threading.local()
class ExtendedAuditlogMiddleware(AuditlogMiddleware):
def process_request(self, request):
threadlocal.auditlog = {
'signal_duid': (self.__class__, time.time()),
'remote_addr': request.META.get('REMOTE_ADDR'),
}
super(ExtendedAuditlogMiddleware, self).process_request(request)
**#changes here
import uuid
threadlocal.auditlog['additional_data'] = str(uuid.UUID(int=uuid.getnode()))+" | "+request.META["USERNAME"]**
# #staticmethod
def set_actor(self, user, sender, instance, signal_duid, **kwargs):
super(ExtendedAuditlogMiddleware, self).set_actor(user, sender, instance, signal_duid, **kwargs)
**#changes here
instance.additional_data = threadlocal.auditlog['additional_data']**
But the problem is that I think I get the UUID not of the user, but of the server, because there is no access to the user, i guess. I couldn't find the information, and I couldn't come up with my own solution either.
Question - is it even possible to get information from the server about django admin users' devices??
If not, what can i use instead of UUID to identify which device was used while making changes in django admin panel??
Thank you all in advance!
try to use javascript in side of your template to send this info thrugh
I want to customize the field texture in django’s new “readonly mode”: E.g. foreign keys shall be displayed as links.
In general I identified the following options:
Implement custom fields for every model – results in code duplication
Reimplement django’s display_for_field method
Basically I could copy & paste the django.contrib.admin.utils module, insert my changes, override sys.modules['django.contrib.admin.utils'] = myutils but that's ugly because of maintainability in case of Django updates in the future.
So I decided to override only the display_for_fields method of django.contrib.admin.utils using the following approach to avoid duplication of Django code:
Override display_for_field function in django.contrib.admin.utils in settings.py:
from myapp.contrib.admin import utils
utils.override_method()
In myapp.utils.py:
from django.contrib.admin import utils
from django.contrib.admin.utils import *
def display_for_field_mod(value, field, empty_value_display):
if isinstance(field, models.ForeignKey) and value:
if field.related_model.__name__.lower() != 'user':
link_string = 'admin:myapp_' + field.related_model.__name__.lower() + '_change'
link = reverse(link_string, args=(value.id,))
return format_html('{}', link, value)
else:
return formats.localize(value)
else:
return display_for_field(value, field, empty_value_display)
def override_method():
utils.display_for_field = display_for_field_mod
But the problem is: display_for_field gets imported in django.contrib.admin.helpers using:
from django.contrib.admin.utils import (
display_for_field, [...]
)
So due to the scope of the imported funtion I cannot override this function from outside.
Do I miss some other obvious possibility? Is there a clean method to achieve this or is the only option to duplicate/modify django’s original code?
I came across a similar issue. If anyone's still looking for a solution, you just need to override the display_for_field in helpers, e.g.:
from django.contrib.admin import helpers, utils
def override_method():
helpers.display_for_field = display_for_field_mod
utils.display_for_field = display_for_field_mod
Only overriding helpers is sufficient, but it's a good idea to override utils as well, just in case.
Using Django, if I set a session variable within the post() method of a django.views.generic.edit.FormView class, that variable is then available for future requests.
e.g.
def post(self, request, *args, **kwargs):
"""
Store useful data in session variable for future requests
"""
if 'useful_data' in request.POST:
request.session['useful_data'] = useful_data
return HttpResponseRedirect(self.get_success_url())
If however, I attempt to set a session variable via the form_valid() method of a django.views.generic.edit.FormView class, the changes I make to the variable seem to disappear before the next request.
e.g.
def form_valid(self, form):
"""
Store useful data in session variable for future requests
"""
# Useful data that I only want to update if the form was validated
self.request.session['useful_data'].update(form.cleaned_data['useful_data'])
return HttpResponseRedirect(self.get_success_url())
So, how can I make persistent changes to session variables from the form_valid() method of a django.views.generic.edit.FormView class?
I suspect the problem could be the update part:
self.request.session['useful_data'].update(form.cleaned_data['useful_data'])
If we take a look at the documentation, the issue could be django does not know that the session has been modified and thus does not change it.
To validate this, make it explicit that session has been modified:
self.request.session['useful_data'].update(form.cleaned_data['useful_data'])
self.request.session.modified = True
Or just assign the value without using update(), just like in the documentation:
self.request.session['useful_data'] = form.cleaned_data['useful_data']
Hope it helps!
I use the blobstoreuploadhandler and hence must return a self.redirect but I need to pass values to my template. How can I do it? If I can't use template values then I suppose I can use session variables and I've included the beaker session library but I can't understand how to access the session variables in django template. Any idea how I should do it?
I use default builtin django with google app engine and I can access session variables with a request handler but I don't understand how to do it in templates:
class Sessiontest(webapp.RequestHandler):
def get(self):
# Get the session object from the environ
self.session = self.request.environ['beaker.session']
# Check to see if a value is in the session
if 'counter' in self.session:
counter = self.session['counter'] + 1
self.session['counter'] = counter
else:
self.session['counter'] = 1
counter = 1
self.session.save()
self.response.out.write('counter: %d' % counter)
Thanks
Update/edit: My problem is almost exactly like this Accessing session variable in Django template with Google App Engine (Webapp) - Python but with the library beaker instead of gaeutilities
Update: Here's some of the code. we see that using HTTP GET to pass the values won't be very good since there's an anti-spam test that should hide the values:
def post(self, view):
message = ''
challenge = self.request.get('recaptcha_challenge_field').encode('utf-8')
response = self.request.get('recaptcha_response_field').encode('utf-8')
remoteip = os.environ['REMOTE_ADDR']
cResponse = captcha.submit(
challenge,
response,
CAPTCHA_PRV_KEY,
remoteip)
if cResponse.is_valid:
isHuman=True
else:#failed anti-spam test and can try again
isHuman=False
#Reprint the form
import util
template_values = {'isHuman':isHuman,'user' : users.get_current_user(),}
template_values.update(dict(current_user=self.current_user, facebook_app_id=FACEBOOK_APP_ID))
template_values.update(dict(capture=captcha.displayhtml(public_key = CAPTCHA_PUB_KEY, use_ssl = False, error = None)))
path = os.path.join(os.path.dirname(__file__), 'market', 'market_insert.html')
self.redirect("/ai") # Here the values aren't passed on and I must make a redirect
If you are doing a redirect you might have to redirect with the variables that you wish to keep in the GET string. So you redirect from
/myview/
to
/myview2/?variable1=value
However, I think you should really look to see why you are doing redirects. I tend to do them after a POST to a form, and if the user needs to be logged on, I redirect to a login screen with
/authentication/login/?next=/view/they/wanted/to/see
Otherwise you could keep things in cookies but its not the best way to proceed.
How about letting your class inherit from multiple classes, both requesthandler class and blobstoreuploadhandler, in that way you can both render your template with values with the functions in the requesthandler, and use the functions in blobstoreuploadhandler?
A class definition with multiple base classes looks as follows:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
...
<statement-N>
I've been building a handler class for each method I want to map to the url file. Is my approach correct or wrong? because I don't seem to find a way to map a resource to a method they all map to an entire class.
Regards,
The documentations seems very clear https://bitbucket.org/jespern/django-piston/wiki/Documentation#!resources
from piston.handler import BaseHandler
from myapp.models import Blogpost
class BlogpostHandler(BaseHandler):
allowed_methods = ('GET',)
model = Blogpost
def read(self, request, post_slug):
...
Piston lets you map resource to
models, and by doing so, it will do a
lot of the heavy lifting for you.
A resource can be just a class, but
usually you would want to define at
least 1 of 4 methods:
read is called on GET requests, and
should never modify data (idempotent.)
create is called on POST, and creates
new objects, and should return them
(or rc.CREATED.)
update is called on PUT, and should
update an existing product and return
them (or rc.ALL_OK.)
delete is called on DELETE, and should
delete an existing object. Should not
return anything, just rc.DELETED.
Also https://bitbucket.org/jespern/django-piston/wiki/Documentation#!mapping-urls
In urls.py:
from django.conf.urls.defaults import *
from piston.resource import Resource
from mysite.myapp.api.handlers import BlogpostHandler
blogpost_handler = Resource(BlogpostHandler)
urlpatterns = patterns('',
url(r'^blogpost/(?P<post_slug>[^/]+)/', blogpost_handler),
url(r'^blogposts/', blogpost_handler),
)