Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modifying Dictionary in Django Session Does Not Modify Session

Tags:

django

session

I am storing dictionaries in my session referenced by a string key:

>>> request.session['my_dict'] = {'a': 1, 'b': 2, 'c': 3} 

The problem I encountered was that when I modified the dictionary directly, the value would not be changed during the next request:

>>> request.session['my_dict'].pop('c') 3 >>> request.session.has_key('c') False # looks okay... ... # Next request >>> request.session.has_key('c') True # what gives! 
like image 408
Carl G Avatar asked Jan 30 '10 04:01

Carl G


People also ask

How does Django maintain session?

Django provides a session framework that lets you store and retrieve data on a per-site-visitor basis. Django abstracts the process of sending and receiving cookies, by placing a session ID cookie on the client side, and storing all the related data on the server side. So the data itself is not stored client side.

How do I use file based sessions in Django?

If you want to use a database-backed session, you need to add 'django. contrib. sessions' to your INSTALLED_APPS setting. Once you have configured your installation, run manage.py migrate to install the single database table that stores session data.

How long do Django sessions last?

As you mentioned in your question, sessions in Django live for as long as SESSION_COOKIE_AGE determines (which defaults to 2 weeks) from the last time it was "accessed". Two exceptions for that: you can set an expiry time to a session yourself, and then it depends on that.


2 Answers

As the documentation states, another option is to use

SESSION_SAVE_EVERY_REQUEST=True 

which will make this happen every request anyway. Might be worth it if this happens a lot in your code; I'm guessing the occasional additional overhead wouldn't be much and it is far less than the potential problems from neglecting from including the

request.session.modified = True 

line each time.

like image 122
Jordan Reiter Avatar answered Oct 14 '22 18:10

Jordan Reiter


I apologize for "asking" a question to which I already know the answer, but this was frustrating enough that I thought the answer should be recorded on stackoverflow. If anyone has something to add to my explanation I will award the "answer". I couldn't find the answer by searching based on the problem, but after searching based upon the answer I found that my "problem" is documented behavior. Also turns out another person had this problem.

It turns out that SessionBase is a dictionary-like object that keeps track of when you modify it's keys, and manually sets an attribute modified (there's also an accessed). If you mess around with objects within those keys, however, SessionBase has no way to know that the objects are modified, and therefore your changes might not get stored in whatever backend you are using. (I'm using a database backend; I presume this problem applies to all backends, though.) This problem might not apply to models, since the backend is probably storing a reference to the model (and therefore would receive any changes when it loaded the model from the database), but the problem does apply to dictionaries (and perhaps any other base python types that must be stored entirely in the session store.)

The trick is that whenever you modify objects in the session that the session won't notice, you must explicitly tell the session that it is modified:

>>> request.session.modified = True 

Hope this helps someone.

The way I got around this was to encapsulate any pop actions on the session into a method that takes care of the details (this method also accepts a view parameter so that session variables can be view-specific):

def session_pop(request, view, key, *args, **kwargs):     """     Either returns and removes the value of the key from request.session, or,     if request.session[key] is a list, returns the result of a pop on this     list.     Also, if view is not None, only looks within request.session[view.func_name]     so that I can store view-specific session variables.     """     # figure out which dictionary we want to operate on.     dicto = {}     if view is None:         dicto = request.session     else:         if request.session.has_key(view.func_name):             dicto = request.session[view.func_name]      if dicto.has_key(key):          # This is redundant if `dicto == request.session`, but rather than         #  duplicate the logic to test whether we popped a list underneath         #  the root level of the session, (which is also determined by `view`)         #  just explicitly set `modified`         #  since we certainly modified the session here.         request.session.modified = True          # Return a non-list         if not type(dicto[key]) == type(list()):             return dicto.pop(key)          # pop a list         else:             if len(dicto[key]) > 0:                 return dicto[key].pop()      # Parse out a default from the args/kwargs     if len(args) > 0:         default = args[0]     elif kwargs.has_key('default'):         default = kwargs['default']     else:         # If there wasn't one, complain         raise KeyError('Session does not have key "{0}" and no default was provided'.format(key))     return default 
like image 23
Carl G Avatar answered Oct 14 '22 17:10

Carl G