Ticket #7008: appengine_session_backend.3.jezdez.diff

File appengine_session_backend.3.jezdez.diff, 5.6 KB (added by jezdez, 7 years ago)

Uses session_key as key_name and Session.get_by_key_name() for better performance. Added session_key_prefix to circumvent BadArgumentError.

  • django/contrib/sessions/backends/appengine.py

     
     1r"""
     2>>> from django.conf import settings
     3>>> from django.contrib.sessions.backends.appengine import SessionStore as AppEngineSession
     4>>> from django.contrib.sessions.backends.base import SessionBase
     5>>> appengine_session = AppEngineSession()
     6>>> appengine_session.modified
     7False
     8>>> appengine_session['cat'] = "dog"
     9>>> appengine_session.modified
     10True
     11>>> appengine_session.pop('cat')
     12'dog'
     13>>> appengine_session.pop('some key', 'does not exist')
     14'does not exist'
     15>>> appengine_session.save()
     16>>> appengine_session.exists(appengine_session.session_key)
     17True
     18>>> appengine_session.delete(appengine_session.session_key)
     19>>> appengine_session.exists(appengine_session.session_key)
     20False
     21>>> appengine_session.setdefault('foo', 'bar')
     22'bar'
     23>>> appengine_session.setdefault('foo', 'baz')
     24'bar'
     25"""
     26import md5
     27import sys
     28import base64
     29import datetime
     30import cPickle as pickle
     31
     32from django.conf import settings
     33from django.contrib.sessions.backends.base import SessionBase
     34from django.core.exceptions import SuspiciousOperation
     35
     36# add the weird appengine location (here: MacOSX & Windows) to the pythonpath
     37# if script is started from the command shell, not needed if you are running
     38# the your app with dev_appserver.py
     39if __name__ == '__main__':
     40    sys.path.insert(0, '/usr/local/google_appengine/', )
     41   
     42    # needed for using datastore on the commandline
     43    from google.appengine.api import apiproxy_stub_map
     44    from google.appengine.api import datastore_file_stub
     45    apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap()
     46    apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3',
     47        datastore_file_stub.DatastoreFileStub('whatever',
     48            '/dev/null', '/dev/null'))
     49
     50try:
     51    from google.appengine.ext import db
     52except ImportError:
     53    raise ImportError("The app engine datastore module could not be found. Please add it to your PYTHONPATH (e.g. '/usr/local/google_appengine/' on MacOS)")
     54
     55class Session(db.Model):
     56    session_data = db.TextProperty(required=True)
     57    expire_date = db.DateTimeProperty(required=True)
     58
     59    def get_decoded(self):
     60        encoded_data = base64.decodestring(self.session_data)
     61        pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
     62        if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
     63            raise SuspiciousOperation, "User tampered with session cookie."
     64        try:
     65            return pickle.loads(pickled)
     66        # Unpickling can cause a variety of exceptions. If something happens,
     67        # just return an empty dictionary (an empty session).
     68        except:
     69            return {}
     70
     71class SessionStore(SessionBase):
     72    """
     73    Implements appengine session store
     74    """
     75    def __init__(self, session_key=None):
     76        self.session_key_prefix = 'sessionid:'
     77        super(SessionStore, self).__init__(session_key)
     78
     79    def _prefix_key(self, session_key=None):
     80        """
     81        Add prefix to session key to prevent BadArgumentError exceptions from
     82        appengine (Names may not begin with a digit).
     83        """
     84        if session_key is None:
     85            session_key = self.session_key
     86        return '%s:%s' % (self.session_key_prefix, session_key)
     87
     88    def load(self):
     89        try:
     90            s = Session.get_by_key_name(self._prefix_key())
     91            if s and s.expire_date >= datetime.datetime.now():
     92                return self.decode(s.session_data)
     93            raise SuspiciousOperation
     94        except SuspiciousOperation:
     95            # Create a new session_key for extra security.
     96            self.session_key = self._prefix_key(self._get_new_session_key())
     97            self._session_cache = {}
     98
     99            # Save immediately to minimize collision
     100            self.save()
     101            # Ensure the user is notified via a new cookie.
     102            self.modified = True
     103            return {}
     104
     105    def exists(self, session_key):
     106        if Session.get_by_key_name(self._prefix_key(session_key)):
     107            return True
     108        return False
     109
     110    def save(self):
     111        Session(key_name = self._prefix_key(self.session_key),
     112                session_data = self.encode(self._session),
     113                expire_date = (datetime.datetime.now() +
     114                    datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
     115        ).put()
     116
     117    def delete(self, session_key):
     118        s = Session.get_by_key_name(self._prefix_key(session_key))
     119        if s:
     120            s.delete()
     121
     122if __name__ == '__main__':
     123    import doctest
     124    doctest.testmod()
  • docs/sessions.txt

     
    7070    to use file or database sessions directly instead of sending everything
    7171    through the file or database cache backends.
    7272
     73Using appengine-based sessions
     74------------------------------
     75
     76To use appengine-based sessions, set the ``SESSION_ENGINE`` setting to
     77``"django.contrib.sessions.backends.appengine"``.
     78
     79You might also want to add the path to the Google App Engine backend to your
     80``PYTHONPATH``, e.g. ``/usr/local/google_appengine/`` on Mac OS X, if you want
     81to run the code on your computer. This is not needed if you run it with the
     82``dev_appserver.py`` or on Google's infrastructure.
     83
    7384Using sessions in views
    7485=======================
    7586
Back to Top