26

I want to store some things in a database, and am using the current session as a foreign key: from models.py

class Visited(models.Model):
    session = models.ForeignKey(Session)
    page = models.ForeignKey(Page)
    times_visited = models.PositiveIntegerField()
    ip_address = models.IPAddressField()
    date_last_visited = models.DateTimeField()
    def __unicode__(self):
        return u'(%s, %s)' % (str(self.session.session_key), str(self.page.name))

To make a new entry for this model I'm using the following to get the current session (in views.py):

Session.objects.get(session_key=request.session.session_key)

However if it is the first time a user has visited the site, and thus do not have a cookie set yet, the code above will produce a DoesNotExist error.


I know that even if there is now cookie set you can still set session objects. So I can think of a few hacks to make this work, such as:

  • Set unique identifier as a session object (in addition to the session key)
  • Temporarily store the data I wish to add to the database a session object, and use a decorator function to check if it exists before using a session.
  • Just use the session objects and not store anything in the database (this would be technically possible, but for my implementation it would depend on Python dictionaries -with a few hundred entries- being at least as efficient as a database for things like sorting.)


But I would like a better solution I can live with. Are there any generally used or good solutions to this problem? Or am I even referencing sessions properly in my model?

Thank you for your help.

Jon Cox
  • 10,622
  • 22
  • 78
  • 123

2 Answers2

58

request.session is a SessionStore object with a unique session_key.

The session_key is created as soon as the attribute is accessed. But the session object itself is only saved to the database after the view has been processed (in the process_response method of the session middleware) by calling the save method of the SessionStore object.

It's not really documented, but looking at the source code I guess you are supposed to create a new session object like this:

if not request.session.exists(request.session.session_key):
    request.session.create() 

You could also create custom session middleware, that makes sure your new session object is always available before any of your views tries to access it:

from django.conf import settings
from django.contrib.sessions.middleware import SessionMiddleware

class CustomSessionMiddleware(SessionMiddleware):
    def process_request(self, request):
        engine = import_module(settings.SESSION_ENGINE)
        session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
        request.session = engine.SessionStore(session_key)
        if not request.session.exists(request.session.session_key):
            request.session.create() 

(Of course you must reference your new session middleware via the SESSION_ENGINE inside your settings.py)

But be aware - this approach will generate a new session object for every request if the user's browser does not support cookies ...

Mark S
  • 51
  • 5
codecraft
  • 1,163
  • 9
  • 11
  • Thank you for your excellent explanation of how it all works (or rather why it doesn't work). I went with a variation of the second "hack" that I mentioned, for the sake of simplicity if not elegance. – Jon Cox Feb 27 '11 at 13:55
  • I am not too familiar with Django or Python, but I am pretty sure this answer is exactly what I need. I need to swap out the session if a session id is passed from the client (don't ask). I am a little confused though, do I reference this class in both `MIDDLEWARE_CLASSES` in addition to 'django.contrib.sessions.middleware.SessionMiddleware' and also `SESSION_ENGINE`? Surely referencing it in `SESSION_ENGINE` causes this line to recurse? `engine = import_module(settings.SESSION_ENGINE)`. Am I missing the point? – Steve Aug 05 '14 at 09:21
  • I don't think `request.session.create()` sets the `Set-Cookie` header. – Flimm Jan 13 '16 at 17:03
  • I had to set something in the session like `request.session['foo'] = 'bar'` in order to stop `SessionMiddleWare` from running `delete_cookie`. – Flimm Jan 13 '16 at 17:25
  • This was very helpful. Can't believe (as of today) this isn't in the django docs. It seems like doing .clear() then .create() to clear user history would be a common enough event. – Ezekiel Kruglick May 05 '16 at 02:08
  • Very good solution. But as soon the user will log in, there will be conflicts. In my case everything worked fine, deserved an upvote, surely. But soon, my cms toolbar disappeared. So I ran into investigation, undo code step by step. As soon, I disabled the middleware it worked. I suspect that the problem is, that this middleware creates a session key, which is the purpose. But as soon the user loggs in, another session key is generated which then brings to conflict. – Peter Aug 02 '16 at 12:00
1

if you want to destroy session i would suggest a better idea is first use django shell.

from django.contrib.sessions.models import Session
Session.objects.all() #you see all sessions
Session.objects.all().delete() 

in last query you can filter according to your need. and hit a query

Himanshu
  • 11
  • 2