Fixed #22634 -- Made the database-backed session backends more extensible.

Introduced an AbstractBaseSession model and hooks providing the option
of overriding the model class used by the session store and the session
store class used by the model.
This commit is contained in:
Sergey Kolosov 2014-05-16 18:18:34 +02:00 committed by Tim Graham
parent 956df84a61
commit 22bb548900
12 changed files with 370 additions and 78 deletions

View file

@ -254,7 +254,10 @@ Minor features
:mod:`django.contrib.sessions`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* ...
* The session model and ``SessionStore`` classes for the ``db`` and
``cached_db`` backends are refactored to allow a custom database session
backend to build upon them. See
:ref:`extending-database-backed-session-engines` for more details.
:mod:`django.contrib.sitemaps`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -514,8 +514,10 @@ access sessions using the normal Django database API::
>>> s.expire_date
datetime.datetime(2005, 8, 20, 13, 35, 12)
Note that you'll need to call ``get_decoded()`` to get the session dictionary.
This is necessary because the dictionary is stored in an encoded format::
Note that you'll need to call
:meth:`~base_session.AbstractBaseSession.get_decoded()` to get the session
dictionary. This is necessary because the dictionary is stored in an encoded
format::
>>> s.session_data
'KGRwMQpTJ19hdXRoX3VzZXJfaWQnCnAyCkkxCnMuMTExY2ZjODI2Yj...'
@ -670,6 +672,177 @@ Technical details
* Django only sends a cookie if it needs to. If you don't set any session
data, it won't send a session cookie.
The ``SessionStore`` object
---------------------------
When working with sessions internally, Django uses a session store object from
the corresponding session engine. By convention, the session store object class
is named ``SessionStore`` and is located in the module designated by
:setting:`SESSION_ENGINE`.
All ``SessionStore`` classes available in Django inherit from
:class:`~backends.base.SessionBase` and implement data manipulation methods,
namely:
* ``exists()``
* ``create()``
* ``save()``
* ``delete()``
* ``load()``
* :meth:`~backends.base.SessionBase.clear_expired`
In order to build a custom session engine or to customize an existing one, you
may create a new class inheriting from :class:`~backends.base.SessionBase` or
any other existing ``SessionStore`` class.
Extending most of the session engines is quite straightforward, but doing so
with database-backed session engines generally requires some extra effort (see
the next section for details).
.. _extending-database-backed-session-engines:
Extending database-backed session engines
=========================================
.. versionadded:: 1.9
Creating a custom database-backed session engine built upon those included in
Django (namely ``db`` and ``cached_db``) may be done by inheriting
:class:`~base_session.AbstractBaseSession` and either ``SessionStore`` class.
``AbstractBaseSession`` and ``BaseSessionManager`` are importable from
``django.contrib.sessions.base_session`` so that they can be imported without
including ``django.contrib.sessions`` in :setting:`INSTALLED_APPS`.
.. class:: base_session.AbstractBaseSession
.. versionadded:: 1.9
The abstract base session model.
.. attribute:: session_key
Primary key. The field itself may contain up to 40 characters. The
current implementation generates a 32-character string (a random
sequence of digits and lowercase ASCII letters).
.. attribute:: session_data
A string containing an encoded and serialized session dictionary.
.. attribute:: expire_date
A datetime designating when the session expires.
Expired sessions are not available to a user, however, they may still
be stored in the database until the :djadmin:`clearsessions` management
command is run.
.. classmethod:: get_session_store_class()
Returns a session store class to be used with this session model.
.. method:: get_decoded()
Returns decoded session data.
Decoding is performed by the session store class.
You can also customize the model manager by subclassing
:class:`~django.contrib.sessions.base_session.BaseSessionManager`:
.. class:: base_session.BaseSessionManager
.. versionadded:: 1.9
.. method:: encode(session_dict)
Returns the given session dictionary serialized and encoded as a string.
Encoding is performed by the session store class tied to a model class.
.. method:: save(session_key, session_dict, expire_date)
Saves session data for a provided session key, or deletes the session
in case the data is empty.
Customization of ``SessionStore`` classes is achieved by overriding methods
and properties described below:
.. class:: backends.db.SessionStore
Implements database-backed session store.
.. classmethod:: get_model_class()
.. versionadded:: 1.9
Override this method to return a custom session model if you need one.
.. method:: create_model_instance(data)
.. versionadded:: 1.9
Returns a new instance of the session model object, which represents
the current session state.
Overriding this method provides the ability to modify session model
data before it's saved to database.
.. class:: backends.cached_db.SessionStore
Implements cached database-backed session store.
.. attribute:: cache_key_prefix
.. versionadded:: 1.9
A prefix added to a session key to build a cache key string.
Example
-------
The example below shows a custom database-backed session engine that includes
an additional database column to store an account ID (thus providing an option
to query the database for all active sessions for an account)::
from django.contrib.sessions.backends.db import SessionStore as DBStore
from django.contrib.sessions.base_session import AbstractBaseSession
from django.db import models
class CustomSession(AbstractBaseSession):
account_id = models.IntegerField(null=True, db_index=True)
class Meta:
app_label = 'mysessions'
@classmethod
def get_session_store_class(cls):
return SessionStore
class SessionStore(DBStore):
@classmethod
def get_model_class(cls):
return CustomSession
def create_model_instance(self, data):
obj = super(SessionStore, self).create_model_instance(data)
try:
account_id = int(data.get('_auth_user_id'))
except (ValueError, TypeError):
account_id = None
obj.account_id = account_id
return obj
If you are migrating from the Django's built-in ``cached_db`` session store to
a custom one based on ``cached_db``, you should override the cache key prefix
in order to prevent a namespace clash::
class SessionStore(CachedDBStore):
cache_key_prefix = 'mysessions.custom_cached_db_backend'
# ...
Session IDs in URLs
===================