Changes between Initial Version and Version 1 of CookBookDualSessionMiddleware


Ignore:
Timestamp:
Jan 22, 2007, 3:16:44 PM (18 years ago)
Author:
Vadim Macagon
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • CookBookDualSessionMiddleware

    v1 v1  
     1The default [http://www.djangoproject.com/documentation/sessions/ SessionMiddleware] that comes with Django lets you pick if you want all sessions to be browser-length or persistent, if you want to use different types of sessions based on a users preference (e.g. the good old "Remember Me" check-box) you're gonna have to write your own middleware. Fortunately it's pretty easy, here's my attempt.
     2
     3{{{
     4#!python
     5from django.conf import settings
     6from django.contrib.sessions.models import Session
     7from django.contrib.sessions.middleware import SessionWrapper
     8from django.utils.cache import patch_vary_headers
     9import datetime
     10
     11class DualSessionMiddleware(object):
     12    """Session middleware that allows you to turn individual browser-length
     13    sessions into persistent sessions and vice versa.
     14   
     15    This middleware can be used to implement the common "Remember Me" feature
     16    that allows individual users to decide when their session data is discarded.
     17    If a user ticks the "Remember Me" check-box on your login form create
     18    a persistent session, if they don't then create a browser-length session.
     19   
     20    This middleware replaces SessionMiddleware, to enable this middleware:
     21    - Add this middleware to the MIDDLEWARE_CLASSES setting in settings.py,
     22      replacing the SessionMiddleware entry.
     23    - In settings.py add this setting:
     24      PERSISTENT_SESSION_KEY = 'sessionpersistent'
     25    - Tweak any other regular SessionMiddleware settings (see the sessions doc),
     26      the only session setting that's ignored by this middleware is
     27      SESSION_EXPIRE_AT_BROWSER_CLOSE.
     28     
     29    Once this middleware is enabled all sessions will be browser-length by
     30    default.
     31   
     32    To make an individual session persistent simply do this:
     33   
     34    session[settings.PERSISTENT_SESSION_KEY] = True
     35   
     36    To make a persistent session browser-length again simply do this:
     37   
     38    session[settings.PERSISTENT_SESSION_KEY] = False
     39    """
     40   
     41    def process_request(self, request):
     42        request.session = SessionWrapper(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None))
     43
     44    def process_response(self, request, response):
     45        # If request.session was modified, or if response.session was set, save
     46        # those changes and set a session cookie.
     47        patch_vary_headers(response, ('Cookie',))
     48        try:
     49            modified = request.session.modified
     50        except AttributeError:
     51            pass
     52        else:
     53            if modified or settings.SESSION_SAVE_EVERY_REQUEST:
     54                session_key = request.session.session_key or Session.objects.get_new_session_key()
     55               
     56                if not request.session.get(settings.PERSISTENT_SESSION_KEY, False):
     57                    # session will expire when the user closes the browser
     58                    max_age = None
     59                    expires = None
     60                else:
     61                    max_age = settings.SESSION_COOKIE_AGE
     62                    expires = datetime.datetime.strftime(datetime.datetime.utcnow() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT")
     63               
     64                new_session = Session.objects.save(session_key,
     65                                                   request.session._session,
     66                                                   datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
     67                response.set_cookie(settings.SESSION_COOKIE_NAME, session_key,
     68                                    max_age = max_age, expires = expires,
     69                                    domain = settings.SESSION_COOKIE_DOMAIN,
     70                                    secure = settings.SESSION_COOKIE_SECURE or None)
     71        return response
     72}}}
     73
     74I think the class doc provides all the installation and deployment info you'll need (just make sure you read the Django docs about [http://www.djangoproject.com/documentation/middleware/ middleware] first). Here's a sample login view that makes use of this middleware to implement the "Remember Me" check-box.
     75
     76{{{
     77#!python
     78from django.contrib import auth
     79from django.contrib.auth import REDIRECT_FIELD_NAME
     80from django.http import HttpResponseRedirect
     81from mysite.accounts.forms import LoginForm
     82
     83def login(request):
     84    """Display and processs the login form."""
     85    no_cookies = False
     86    account_disabled = False
     87    invalid_login = False
     88    redirect_to = request.REQUEST.get(REDIRECT_FIELD_NAME, '')
     89    if request.method == 'POST':
     90        if request.session.test_cookie_worked():
     91            request.session.delete_test_cookie()
     92            form = LoginForm(request.POST)
     93            if form.is_valid():
     94                user = auth.authenticate(username = form.clean_data['username'],
     95                                         password = form.clean_data['password'])
     96                if user:
     97                    if user.is_active:
     98                        auth.login(request, user)
     99                        request.session[settings.PERSISTENT_SESSION_KEY] = form.clean_data['remember_user']
     100                        # login successful, redirect
     101                        return HttpResponseRedirect('/')
     102                    else:
     103                        account_disabled = True
     104                else:
     105                    invalid_login = True
     106        else:
     107            no_cookies = True
     108            form = None
     109    else:
     110        form = LoginForm()
     111   
     112    # cookie must be successfully set/retrieved for the form to be processed   
     113    request.session.set_test_cookie()
     114    return render_to_response('accounts/login.html',
     115                              { 'no_cookies': no_cookies,
     116                                'account_disabled': account_disabled,
     117                                'invalid_login': invalid_login,
     118                                'form': form,
     119                                REDIRECT_FIELD_NAME: redirect_to },
     120                              context_instance = RequestContext(request))
     121}}}
     122
     123And here's the sample login form used by the view.
     124
     125{{{
     126#!python
     127from django import newforms as forms
     128from django.newforms import widgets
     129from django.contrib.auth.models import User
     130
     131class LoginForm(forms.Form):
     132    """Login form for users."""
     133    username = forms.RegexField(r'^[a-zA-Z0-9_]{1,30}$',
     134                                max_length = 30,
     135                                min_length = 1,
     136                                error_message = 'Must be 1-30 alphanumeric characters or underscores.')
     137    password = forms.CharField(min_length = 6,
     138                               max_length = 128,
     139                               widget = widgets.PasswordInput,
     140                               label = 'Password')
     141    remember_user = forms.BooleanField(required = False,
     142                                       label = 'Remember Me')
     143   
     144    def clean(self):
     145        try:
     146            user = User.objects.get(username__iexact = self.clean_data['username'])
     147        except User.DoesNotExist, KeyError:
     148            raise forms.ValidationError('Invalid username, please try again.')
     149       
     150        if not user.check_password(self.clean_data['password']):
     151            raise forms.ValidationError('Invalid password, please try again.')
     152       
     153        return self.clean_data
     154}}}
     155
     156If you've noticed anything that could be done better please point it out. This hasn't been tested in production yet, so you should run your own tests to make sure everything is working as expected.
Back to Top