Modifying Dictionary in Django Session Does Not Modify Session Modifying Dictionary in Django Session Does Not Modify Session django django

Modifying Dictionary in Django Session Does Not Modify Session


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.


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


I'm not too surprised by this. I guess it's just like modifying the contents of a tuple:

a = (1,[2],3)print a>>> 1, [2], 3)a[1] = 4>>> Traceback (most recent call last):...  File "<stdin>", line 1, in <module>...  TypeError: 'tuple' object does not support item assignmentprint a>>> 1, [2], 3)a[1][0] = 4print a>>> 1, [4], 3)

But thanks anyway.