From 2f85e629b3ca4a69e1b785aa3e374c452fa5596a Mon Sep 17 00:00:00 2001 From: Alberto Resco Perez Date: Wed, 1 Oct 2014 11:37:10 +0800 Subject: [PATCH 1/3] Added cache backend --- user_sessions/backends/cache.py | 96 +++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 user_sessions/backends/cache.py diff --git a/user_sessions/backends/cache.py b/user_sessions/backends/cache.py new file mode 100644 index 0000000..50241b7 --- /dev/null +++ b/user_sessions/backends/cache.py @@ -0,0 +1,96 @@ +from django.conf import settings +from django.contrib import auth +from django.contrib.sessions.backends.base import SessionBase, CreateError +from django.core.cache import caches +from django.utils.six.moves import xrange + +KEY_PREFIX = "user_sessions.cache" + + +class SessionStore(SessionBase): + """ + Implements cache-based session store. + """ + def __init__(self, user_agent, ip, session_key=None): + self._cache = caches[settings.SESSION_CACHE_ALIAS] + super(SessionStore, self).__init__(session_key) + self.user_agent, self.ip, self.user_id = user_agent, ip, None + + def __setitem__(self, key, value): + if key == auth.SESSION_KEY: + self.user_id = value + super(SessionStore, self).__setitem__(key, value) + + @property + def cache_key(self): + return KEY_PREFIX + self._get_or_create_session_key() + + def load(self): + try: + s = self._cache.get(self.cache_key, None) + self.user_id = s['user_id'] + # do not overwrite user_agent/ip, as those might have been updated + if self.user_agent != s['user_agent'] or self.ip != s['ip']: + self.modified = True + except Exception: + # Some backends (e.g. memcache) raise an exception on invalid + # cache keys. If this happens, reset the session. See #17810. + s = None + if s is not None: + return s['session_data'] + self.create() + return {} + + def exists(self, session_key): + return (KEY_PREFIX + session_key) in self._cache + + def create(self): + # Because a cache can fail silently (e.g. memcache), we don't know if + # we are failing to create a new session because of a key collision or + # because the cache is missing. So we try for a (large) number of times + # and then raise an exception. That's the risk you shoulder if using + # cache backing. + for i in xrange(10000): + self._session_key = self._get_new_session_key() + try: + self.save(must_create=True) + except CreateError: + continue + self.modified = True + return + raise RuntimeError( + "Unable to create a new session key. " + "It is likely that the cache is unavailable.") + + def save(self, must_create=False): + if must_create: + func = self._cache.add + else: + func = self._cache.set + result = func(self.cache_key, { + 'session_data': self._get_session(no_load=must_create), + 'user_agent': self.user_agent, + 'user_id': self.user_id, + 'ip': self.ip + }, + self.get_expiry_age()) + if must_create and not result: + raise CreateError + + def clear(self): + super(SessionStore, self).clear() + self.user_id = None + + def delete(self, session_key=None): + if session_key is None: + if self.session_key is None: + return + session_key = self.session_key + self._cache.delete(KEY_PREFIX + session_key) + + @classmethod + def clear_expired(cls): + pass + +# At bottom to avoid circular import +from ..models import Session From cd8a62a90de75e6ff42b63870425e48e920a69de Mon Sep 17 00:00:00 2001 From: Alberto Resco Perez Date: Wed, 1 Oct 2014 11:47:51 +0800 Subject: [PATCH 2/3] Remove unused import --- user_sessions/backends/cache.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/user_sessions/backends/cache.py b/user_sessions/backends/cache.py index 50241b7..bd921c1 100644 --- a/user_sessions/backends/cache.py +++ b/user_sessions/backends/cache.py @@ -91,6 +91,3 @@ def delete(self, session_key=None): @classmethod def clear_expired(cls): pass - -# At bottom to avoid circular import -from ..models import Session From df3a231b74afaa1c77f6534452912d59065cba58 Mon Sep 17 00:00:00 2001 From: Alberto Resco Perez Date: Fri, 3 Oct 2014 09:50:56 +0800 Subject: [PATCH 3/3] Added documentation --- README.rst | 20 +------------------- docs/installation.rst | 31 +++++++++++++++++++++++++++++-- docs/reference.rst | 5 +++-- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/README.rst b/README.rst index 8e0e830..1b6f7ef 100644 --- a/README.rst +++ b/README.rst @@ -49,25 +49,7 @@ And also in a custom layout: .. image:: http://i.imgur.com/d7kZtr9.png -Installation -============ -1. ``pip install django-user-sessions`` -2. In ``INSTALLED_APPS`` replace ``'django.contrib.sessions'`` with - ``'user_sessions'``. -3. In ``MIDDLEWARE_CLASSES`` replace - ``'django.contrib.sessions.middleware.SessionMiddleware'`` with - ``'user_sessions.middleware.SessionMiddleware'``. -4. Add ``SESSION_ENGINE = 'user_sessions.backends.db'``. -5. Add ``url(r'', include('user_sessions.urls', 'user_sessions')),`` to your - ``urls.py``. -6. Run ``python manage.py syncdb`` (or ``migrate``) and browse to - ``/account/sessions/``. - -GeoIP ------ -You need to setup GeoIP for the location detection to work. See the Django -documentation on `installing GeoIP`_. - +.. include:: docs/installation.rst Contribute ========== diff --git a/docs/installation.rst b/docs/installation.rst index 567b637..63f7234 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -6,15 +6,42 @@ Installation 3. In ``MIDDLEWARE_CLASSES`` replace ``'django.contrib.sessions.middleware.SessionMiddleware'`` with ``'user_sessions.middleware.SessionMiddleware'``. -4. Add ``SESSION_ENGINE = 'user_sessions.backends.db'``. +4. Setup ``SESSION_ENGINE`` setting: Please see `Session Engine`_ to choose the + best option for your project. 5. Add ``url(r'', include('user_sessions.urls', 'user_sessions')),`` to your ``urls.py``. -6. Run ``python manage.py syncdb`` (or ``migrate``) and start hacking! GeoIP ----- You need to setup GeoIP for the location detection to work. See the Django documentation on `installing GeoIP`_. + +Session Engine +-------------- +``django-user-sessions`` supports two types of Session Engine. + +database-backed + Sessions will be stored in the database:: + + SESSION_ENGINE = 'user_sessions.backends.db' + + You will need to run ``python manage.py syncdb`` (or ``migrate``) + +cached + Session data is stored using Django's cache system. Depending on the cache system can be + persistent or not:: + + SESSION_ENGINE = 'user_sessions.backends.db' + + For configuring the Django's cache system, please read `configuring Cache`_. + +For for information about sessions, see the Django documentation on `configuring SessionEngine`_. + + .. _installing GeoIP: https://docs.djangoproject.com/en/1.6/ref/contrib/gis/geoip/ +.. _configuring SessionEngine: + https://docs.djangoproject.com/en/1.6/topics/http/sessions/ +.. _configuring Cache: + https://docs.djangoproject.com/en/1.6/topics/cache/ diff --git a/docs/reference.rst b/docs/reference.rst index 86bfe8d..1510e23 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -11,7 +11,8 @@ Models Session Backends ---------------- -.. autoclass:: user_sessions.backends.db.SessionStore +.. autoclass:: user_sessions.backends.db.SessionStore.. +.. autoclass:: user_sessions.backends.cache.SessionStore.. Template Tags ------------- @@ -25,4 +26,4 @@ Views Unit tests ---------- -.. autoclass:: user_sessions.utils.tests.Client \ No newline at end of file +.. autoclass:: user_sessions.utils.tests.Client