Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added cache backend #22

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 1 addition & 19 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
==========
Expand Down
31 changes: 29 additions & 2 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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/
5 changes: 3 additions & 2 deletions docs/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------
Expand All @@ -25,4 +26,4 @@ Views

Unit tests
----------
.. autoclass:: user_sessions.utils.tests.Client
.. autoclass:: user_sessions.utils.tests.Client
93 changes: 93 additions & 0 deletions user_sessions/backends/cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
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