Django

Code

Changeset 6333

Show
Ignore:
Timestamp:
09/15/07 16:29:14 (8 months ago)
Author:
jacob
Message:

Fixed #2066: session data can now be stored in the cache or on the filesystem. This should be fully backwards-compatible (the database cache store is still the default). A big thanks to John D'Agostino for the bulk of this code.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/AUTHORS

    r6291 r6333  
    8888    flavio.curella@gmail.com 
    8989    Jure Cuhalev <gandalf@owca.info> 
     90    John D'Agostino <john.dagostino@gmail.com> 
    9091    dackze+django@gmail.com 
    9192    David Danier <goliath.mailinglist@gmx.de> 
  • django/trunk/django/conf/global_settings.py

    r6256 r6333  
    272272############ 
    273273 
    274 SESSION_COOKIE_NAME = 'sessionid'         # Cookie name. This can be whatever you want. 
    275 SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # Age of cookie, in seconds (default: 2 weeks). 
    276 SESSION_COOKIE_DOMAIN = None              # A string like ".lawrence.com", or None for standard domain cookie. 
    277 SESSION_COOKIE_SECURE = False             # Whether the session cookie should be secure (https:// only). 
    278 SESSION_SAVE_EVERY_REQUEST = False        # Whether to save the session data on every request. 
    279 SESSION_EXPIRE_AT_BROWSER_CLOSE = False   # Whether sessions expire when a user closes his browser. 
     274SESSION_COOKIE_NAME = 'sessionid'                       # Cookie name. This can be whatever you want. 
     275SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2               # Age of cookie, in seconds (default: 2 weeks). 
     276SESSION_COOKIE_DOMAIN = None                            # A string like ".lawrence.com", or None for standard domain cookie. 
     277SESSION_COOKIE_SECURE = False                           # Whether the session cookie should be secure (https:// only). 
     278SESSION_SAVE_EVERY_REQUEST = False                      # Whether to save the session data on every request. 
     279SESSION_EXPIRE_AT_BROWSER_CLOSE = False                 # Whether sessions expire when a user closes his browser. 
     280SESSION_ENGINE = 'django.contrib.sessions.backends.db'  # The module to store session data 
     281SESSION_FILE_PATH = '/tmp/'                             # Directory to store session files if using the file session module 
    280282 
    281283######### 
  • django/trunk/django/contrib/sessions/middleware.py

    r5712 r6333  
    11from django.conf import settings 
    2 from django.contrib.sessions.models import Session 
    3 from django.core.exceptions import SuspiciousOperation 
    42from django.utils.cache import patch_vary_headers 
    53from email.Utils import formatdate 
     
    108TEST_COOKIE_VALUE = 'worked' 
    119 
    12 class SessionWrapper(object): 
    13     def __init__(self, session_key): 
    14         self.session_key = session_key 
    15         self.accessed = False 
    16         self.modified = False 
     10class SessionMiddleware(object): 
    1711 
    18     def __contains__(self, key): 
    19         return key in self._session 
    20  
    21     def __getitem__(self, key): 
    22         return self._session[key] 
    23  
    24     def __setitem__(self, key, value): 
    25         self._session[key] = value 
    26         self.modified = True 
    27  
    28     def __delitem__(self, key): 
    29         del self._session[key] 
    30         self.modified = True 
    31  
    32     def keys(self): 
    33         return self._session.keys() 
    34  
    35     def items(self): 
    36         return self._session.items() 
    37  
    38     def get(self, key, default=None): 
    39         return self._session.get(key, default) 
    40  
    41     def pop(self, key, *args): 
    42         self.modified = self.modified or key in self._session 
    43         return self._session.pop(key, *args) 
    44  
    45     def set_test_cookie(self): 
    46         self[TEST_COOKIE_NAME] = TEST_COOKIE_VALUE 
    47  
    48     def test_cookie_worked(self): 
    49         return self.get(TEST_COOKIE_NAME) == TEST_COOKIE_VALUE 
    50  
    51     def delete_test_cookie(self): 
    52         del self[TEST_COOKIE_NAME] 
    53  
    54     def _get_session(self): 
    55         # Lazily loads session from storage. 
    56         self.accessed = True 
    57         try: 
    58             return self._session_cache 
    59         except AttributeError: 
    60             if self.session_key is None: 
    61                 self._session_cache = {} 
    62             else: 
    63                 try: 
    64                     s = Session.objects.get(session_key=self.session_key, 
    65                         expire_date__gt=datetime.datetime.now()) 
    66                     self._session_cache = s.get_decoded() 
    67                 except (Session.DoesNotExist, SuspiciousOperation): 
    68                     self._session_cache = {} 
    69                     # Set the session_key to None to force creation of a new 
    70                     # key, for extra security. 
    71                     self.session_key = None 
    72             return self._session_cache 
    73  
    74     _session = property(_get_session) 
    75  
    76 class SessionMiddleware(object): 
    7712    def process_request(self, request): 
    78         request.session = SessionWrapper(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)) 
     13      engine = __import__(settings.SESSION_ENGINE, {}, {}, ['']) 
     14      request.session = engine.SessionStore(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)) 
    7915 
    8016    def process_response(self, request, response): 
     
    9026                patch_vary_headers(response, ('Cookie',)) 
    9127            if modified or settings.SESSION_SAVE_EVERY_REQUEST: 
    92                 if request.session.session_key: 
    93                     session_key = request.session.session_key 
    94                 else: 
    95                     obj = Session.objects.get_new_session_object() 
    96                     session_key = obj.session_key 
    97  
    9828                if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE: 
    9929                    max_age = None 
     
    10232                    max_age = settings.SESSION_COOKIE_AGE 
    10333                    rfcdate = formatdate(time.time() + settings.SESSION_COOKIE_AGE) 
     34                     
    10435                    # Fixed length date must have '-' separation in the format 
    10536                    # DD-MMM-YYYY for compliance with Netscape cookie standard 
    106                     expires = (rfcdate[:7] + "-" + rfcdate[8:11] 
    107                                + "-" + rfcdate[12:26] + "GMT") 
    108                 new_session = Session.objects.save(session_key, request.session._session, 
    109                     datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE)) 
    110                 response.set_cookie(settings.SESSION_COOKIE_NAME, session_key, 
     37                    expires = datetime.datetime.strftime(datetime.datetime.utcnow() + \ 
     38                              datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT") 
     39 
     40                # Save the seesion data and refresh the client cookie. 
     41                request.session.save() 
     42                response.set_cookie(settings.SESSION_COOKIE_NAME, request.session.session_key, 
    11143                    max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, 
    11244                    secure=settings.SESSION_COOKIE_SECURE or None) 
     45                     
    11346        return response 
  • django/trunk/django/contrib/sessions/models.py

    r6270 r6333  
    1 import base64, md5, random, sys, datetime, os, time 
     1import base64, md5, random, sys, datetime 
    22import cPickle as pickle 
    33from django.db import models 
     
    7575    expire_date = models.DateTimeField(_('expire date')) 
    7676    objects = SessionManager() 
     77 
    7778    class Meta: 
    7879        db_table = 'django_session' 
  • django/trunk/django/contrib/sessions/tests.py

    r5876 r6333  
    11r""" 
    2 >>> s = SessionWrapper(None) 
    32 
    4 Inject data into the session cache. 
    5 >>> s._session_cache = {} 
    6 >>> s._session_cache['some key'] = 'exists' 
     3>>> from django.contrib.sessions.backends.db import SessionStore as DatabaseSession 
     4>>> from django.contrib.sessions.backends.cache import SessionStore as CacheSession 
     5>>> from django.contrib.sessions.backends.file import SessionStore as FileSession 
    76 
    8 >>> s.accessed 
     7>>> db_session = DatabaseSession() 
     8>>> db_session.modified 
    99False 
    10 >>> s.modified 
     10>>> db_session['cat'] = "dog" 
     11>>> db_session.modified 
     12True 
     13>>> db_session.pop('cat') 
     14'dog' 
     15>>> db_session.pop('some key', 'does not exist') 
     16'does not exist' 
     17>>> db_session.save() 
     18>>> db_session.exists(db_session.session_key) 
     19True 
     20>>> db_session.delete(db_session.session_key) 
     21>>> db_session.exists(db_session.session_key) 
    1122False 
    1223 
    13 >>> s.pop('non existant key', 'does not exist') 
     24>>> file_session = FileSession() 
     25>>> file_session.modified 
     26False 
     27>>> file_session['cat'] = "dog" 
     28>>> file_session.modified 
     29True 
     30>>> file_session.pop('cat') 
     31'dog' 
     32>>> file_session.pop('some key', 'does not exist') 
    1433'does not exist' 
    15 >>> s.accessed 
     34>>> file_session.save() 
     35>>> file_session.exists(file_session.session_key) 
    1636True 
    17 >>> s.modified 
     37>>> file_session.delete(file_session.session_key) 
     38>>> file_session.exists(file_session.session_key) 
    1839False 
    1940 
    20 >>> s.pop('some key') 
    21 'exists' 
    22 >>> s.accessed 
     41>>> cache_session = CacheSession() 
     42>>> cache_session.modified 
     43False 
     44>>> cache_session['cat'] = "dog" 
     45>>> cache_session.modified 
    2346True 
    24 >>> s.modified 
    25 True 
    26  
    27 >>> s.pop('some key', 'does not exist') 
     47>>> cache_session.pop('cat') 
     48'dog' 
     49>>> cache_session.pop('some key', 'does not exist') 
    2850'does not exist' 
     51>>> cache_session.save() 
     52>>> cache_session.delete(cache_session.session_key) 
     53>>> cache_session.exists(cache_session.session_key) 
     54False 
    2955""" 
    30  
    31 from django.contrib.sessions.middleware import SessionWrapper 
    3256 
    3357if __name__ == '__main__': 
  • django/trunk/django/test/client.py

    r6211 r6333  
    55from django.conf import settings 
    66from django.contrib.auth import authenticate, login 
    7 from django.contrib.sessions.models import Session 
    8 from django.contrib.sessions.middleware import SessionWrapper 
    97from django.core.handlers.base import BaseHandler 
    108from django.core.handlers.wsgi import WSGIRequest 
     
    133131        "Obtain the current session variables" 
    134132        if 'django.contrib.sessions' in settings.INSTALLED_APPS: 
     133            engine = __import__(settings.SESSION_ENGINE, {}, {}, ['']) 
    135134            cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None) 
    136135            if cookie: 
    137                 return SessionWrapper(cookie.value) 
     136                return engine.SessionClass(cookie.value) 
    138137        return {} 
    139138    session = property(_session) 
     
    248247        user = authenticate(**credentials) 
    249248        if user and user.is_active and 'django.contrib.sessions' in settings.INSTALLED_APPS: 
    250             obj = Session.objects.get_new_session_object(
     249            engine = __import__(settings.SESSION_ENGINE, {}, {}, ['']
    251250 
    252251            # Create a fake request to store login details 
    253252            request = HttpRequest() 
    254             request.session = SessionWrapper(obj.session_key
     253            request.session = engine.SessionClass(
    255254            login(request, user) 
    256255 
    257256            # Set the cookie to represent the session 
    258             self.cookies[settings.SESSION_COOKIE_NAME] = obj.session_key 
     257            self.cookies[settings.SESSION_COOKIE_NAME] = request.session.session_key 
    259258            self.cookies[settings.SESSION_COOKIE_NAME]['max-age'] = None 
    260259            self.cookies[settings.SESSION_COOKIE_NAME]['path'] = '/' 
     
    263262            self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None 
    264263 
    265             # Set the session values 
    266             Session.objects.save(obj.session_key, request.session._session, 
    267                 datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE)) 
     264            # Save the session values 
     265            request.session.save()    
    268266 
    269267            return True 
  • django/trunk/docs/sessions.txt

    r5869 r6333  
    1111================= 
    1212 
    13 Sessions are implemented via a piece of middleware_ and a Django model
    14  
    15 To enable session functionality, do these two things
     13Sessions are implemented via a piece of middleware_
     14 
     15To enable session functionality, do the following
    1616 
    1717    * Edit the ``MIDDLEWARE_CLASSES`` setting and make sure 
     
    1919      The default ``settings.py`` created by ``django-admin.py startproject`` has 
    2020      ``SessionMiddleware`` activated. 
    21  
    22     * Add ``'django.contrib.sessions'`` to your ``INSTALLED_APPS`` setting, and 
    23       run ``manage.py syncdb`` to install the single database table that stores 
    24       session data. 
     21       
     22    * Add ``'django.contrib.sessions'`` to your ``INSTALLED_APPS`` setting, 
     23      and run ``manage.py syncdb`` to install the single database table 
     24      that stores session data. 
     25       
     26      **New in development version**: this step is optional if you're not using 
     27      the database session backend; see `configuring the session engine`_.  
    2528 
    2629If you don't want to use sessions, you might as well remove the 
     
    2932 
    3033.. _middleware: ../middleware/ 
     34 
     35Configuring the session engine 
     36============================== 
     37 
     38**New in development version**. 
     39 
     40By default, Django stores sessions in your database (using the model 
     41``django.contrib.sessions.models.Session``). Though this is convenient, in 
     42some setups it's faster to store session data elsewhere, so Django can be 
     43configured to store session data on your filesystem or in your cache. 
     44 
     45Using file-based sessions 
     46------------------------- 
     47 
     48To use file-based sessions, set the ``SESSION_ENGINE`` setting to 
     49``"django.contrib.sessions.backends.file"``. 
     50 
     51You might also want to set the ``SESSION_FILE_PATH`` setting (which 
     52defaults to ``/tmp``) to control where Django stores session files. Be 
     53sure to check that your web server has permissions to read and write to 
     54this location. 
     55 
     56Using cache-based sessions 
     57-------------------------- 
     58 
     59To store session data using Django's cache system, set ``SESSION_ENGINE`` 
     60to ``"django.contrib.sessions.backends.cache"``. You'll want to make sure 
     61you've configured your cache; see the `cache documentation`_ for details. 
     62 
     63.. _cache documentation: ../cache/ 
     64 
     65.. note:: 
     66 
     67    You probably don't want to use cache-based sessions if you're not using 
     68    the memcached cache backend. The local memory and simple cache backends 
     69    don't retain data long enough to be good choices, and it'll be faster 
     70    to use file or database sessions directly instead of sending everything 
     71    through the file or database cache backends. 
    3172 
    3273Using sessions in views 
     
    154195=========================== 
    155196 
    156 Internally, each session is just a normal Django model. The ``Session`` model 
     197The ``SessionStore`` which implements the session storage method can be imported 
     198and a API is available to manipulate the session data outside of a view:: 
     199 
     200    >>> from django.contrib.sessions.engines.db import SessionStore 
     201    >>> s = SessionStore(session_key='2b1189a188b44ad18c35e113ac6ceead') 
     202    >>> s['last_login'] = datetime.datetime(2005, 8, 20, 13, 35, 10) 
     203    >>> s['last_login'] 
     204    datetime.datetime(2005, 8, 20, 13, 35, 0) 
     205    >>> s.save() 
     206 
     207Or if you are using the ``django.contrib.sessions.engine.db`` each  
     208session is just a normal Django model. The ``Session`` model 
    157209is defined in ``django/contrib/sessions/models.py``. Because it's a normal 
    158210model, you can access sessions using the normal Django database API:: 
     
    161213    >>> s = Session.objects.get(pk='2b1189a188b44ad18c35e113ac6ceead') 
    162214    >>> s.expire_date 
    163     datetime.datetime(2005, 8, 20, 13, 35, 12) 
     215    datetime.datetime(2005, 8, 20, 13, 35, 12)   
    164216 
    165217Note that you'll need to call ``get_decoded()`` to get the session dictionary. 
     
    246298A few `Django settings`_ give you control over session behavior: 
    247299 
     300SESSION_ENGINE 
     301-------------- 
     302 
     303**New in Django development version** 
     304 
     305Default: ``django.contrib.sessions.backends.db`` 
     306 
     307Controls where Django stores session data. Valid values are: 
     308 
     309    * ``'django.contrib.sessions.backends.db'``       
     310    * ``'django.contrib.sessions.backends.file'``     
     311    * ``'django.contrib.sessions.backends.cache'`` 
     312     
     313See `configuring the session engine`_ for more details. 
     314 
     315SESSION_FILE_PATH 
     316----------------- 
     317 
     318**New in Django development version** 
     319 
     320Default: ``/tmp/`` 
     321 
     322If you're using file-based session storage, this sets the directory in 
     323which Django will store session data. 
     324 
    248325SESSION_COOKIE_AGE 
    249326------------------ 
  • django/trunk/docs/settings.txt

    r6316 r6333  
    734734``ADMINS`` and ``MANAGERS``. 
    735735 
     736SESSION_ENGINE 
     737-------------- 
     738 
     739**New in Django development version** 
     740 
     741Default: ``django.contrib.sessions.backends.db`` 
     742 
     743Controls where Django stores session data. Valid values are: 
     744 
     745    * ``'django.contrib.sessions.backends.db'``       
     746    * ``'django.contrib.sessions.backends.file'``     
     747    * ``'django.contrib.sessions.backends.cache'`` 
     748     
     749See the `session docs`_ for more details. 
     750 
    736751SESSION_COOKIE_AGE 
    737752------------------ 
     
    775790Whether to expire the session when the user closes his or her browser. 
    776791See the `session docs`_. 
     792 
     793SESSION_FILE_PATH 
     794----------------- 
     795 
     796**New in Django development version** 
     797 
     798Default: ``/tmp/`` 
     799 
     800If you're using file-based session storage, this sets the directory in 
     801which Django will store session data. See the `session docs`_ for 
     802more details. 
    777803 
    778804SESSION_SAVE_EVERY_REQUEST