Why is session unexpectedly saved in Django?
Asked Answered
P

2

1

I could properly experiment when session is and isn't saved in Django as shown in my answer referring When sessions are saved. *I use Django 4.2.3.

But if I run all cases together as shown below (The 1st run):

# "views.py"

from django.http import HttpResponse

def test(request):
    
    # The 1st case
    request.session["foo"] = "bar"
    
    # The 2nd case
    del request.session["foo"]

    # The 3rd case
    request.session["foo"] = {}

    # The 4th case
    request.session["foo"]["bar"] = "baz"

    return HttpResponse('Test')

Then, the 4th case is unexpectedly saved as shown below (The 2nd run):

# "views.py"

from django.http import HttpResponse

def test(request):
    print(request.session.get('foo')) # {'bar': 'baz'}
    return HttpResponse('Test')

Because if I run the 1st, 2nd and 3rd cases together except the 4th case as shown below (The 1st run):

# "views.py"

from django.http import HttpResponse

def test(request):
    
    # The 1st case
    request.session["foo"] = "bar"
    
    # The 2nd case
    del request.session["foo"]

    # The 3rd case
    request.session["foo"] = {}

    return HttpResponse('Test')

Then, run only the 4th case as shown below (The 2nd run):

# "views.py"

from django.http import HttpResponse

def test(request):

    # The 4th case
    request.session["foo"]["bar"] = "baz"

    return HttpResponse('Test')

Then, the 4th case is not saved as shown below (The 3rd run):

# "views.py"

from django.http import HttpResponse

def test(request):
    print(request.session.get('foo')) # {}
    return HttpResponse('Test')

Actually, I don't use the code below in the code above as you can see:

request.session.modified = True

And, I set SESSION_SAVE_EVERY_REQUEST False in settings.py as shown below:

# "settings.py"

SESSION_SAVE_EVERY_REQUEST = False

So, why is the 4th case unexpectedly saved if I run all cases together?

Pentobarbital answered 6/7, 2023 at 18:55 Comment(7)
I would indeed expect that Django does not save the fourth one, especially since it does not even know the subitem was saved. Question: how did you check/test when the request is saved, that seems to be the missing point here.Ruthenious
If you for example use memory as "session storage", there is not really something that is "saved", it just remains in memory. So some backends don't really have a "save" mechanism to begin with.Ruthenious
@Willem_VO_supports_mod_strike I think the 4th case will be saved to django_session table in database.Pentobarbital
@SupKaiKazuyaIto: so it was saved? I can't reproduce that, or at least not with SESSION_SAVE_EVERY_REQUEST set to False etc.Ruthenious
When you do request.session["foo"] = {} (3rd case) the session is altered and will be saved. Then you do request.session["foo"]["bar"] = "baz" (4th case) and since you do it before the session is saved this data will be part of the session too.Genic
@Matthias: ah, I think that the session is saved at the end, if request.session.modified = True, so if you don't set request.session['foo'] = {} in the same request, then it will not save.Ruthenious
Djangos middleware deals with request.session.modified during the response. In other words: the session is saved when the response is done, not every time when request.session is altered. You can find the code in django/contrib/sessions/middleware.py.Genic
R
0

The reason this saves is because Django does not immediately saves changes to the session variables.

during the response, it will check if request.modified is set to True (or just save anyway in case SESSION_SAVE_EVERY_REQUEST is set to True). This means that it will serialize the session at the state of that moment, and save it in the backend you picked.

This thus means that if something "triggered" the request.session to believe that it has been modified, it will save the final state, regardless whether the last modification of the object triggered the session modification "indicator". So it can sometimes "piggyback" on modifications that have been made.

This means that if we have two views:

def view1(request):
    request.session['foo'] = {}
    return HttpResponse('response1')


def view2(request):
    request.session.get('foo', {})['bar'] = 42
    return HttpResponse('response2')

we can first trigger view1 and at the end, it will indicate the sessions have been modified, and save it. If we then make a request to view2 it will not update, since updating request.session['foo']['bar'] = 42 indeed does not trigger the session object that it is modified, and if during the response request.session.modified is still False so it will not update.

In short you thus can see the sessions as some sort of proxy-object. When you need it, it will retrieve it from the database and deserialize it. During the response, it will see if it has been modified (in a manner that the session object detected), and save the updated object once to the database.

Ruthenious answered 6/7, 2023 at 19:20 Comment(0)
P
0

The 1st case makes request.session.modified True (False by default), then the session is saved in process_response() in SessionMiddleware class after completing test(). That's why the 4th case is unexpectedly saved. *All cases except the 4th case can make request.session.modified True and you can see When sessions are saved:

# "views.py"

from django.http import HttpResponse

def test(request):
    
    print(request.session.modified) # False

    # The 1st case
    request.session["foo"] = "bar"

    print(request.session.modified) # True
    
    # The 2nd case
    del request.session["foo"]

    # The 3rd case
    request.session["foo"] = {}

    # The 4th case
    request.session["foo"]["bar"] = "baz"

    return HttpResponse('Test')
Pentobarbital answered 8/7, 2023 at 4:24 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.