├── sessionuser ├── models.py ├── __init__.py └── middleware.py ├── MANIFEST.in ├── .gitignore ├── setup.py ├── LICENSE.py └── README.txt /sessionuser/models.py: -------------------------------------------------------------------------------- 1 | # Nothing to see here -------------------------------------------------------------------------------- /sessionuser/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.1' -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.txt 2 | include README.txt -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | dev.db 3 | local_settings.py 4 | build/ 5 | src/ 6 | pip-log.txt 7 | media/js/*.r*.js 8 | media/css/*.r*.css 9 | *DS_Store 10 | *~ 11 | dist 12 | django_session_user.egg-info 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='django-session-user', 5 | version=__import__('sessionuser').__version__, 6 | description='Stores the user\'s information in the session, reucing database queries by a lot.', 7 | long_description=open('README.txt').read(), 8 | author='Eric Florenzano', 9 | author_email='floguy@gmail.com', 10 | url='https://github.com/ericflo/django-session-user', 11 | packages=['sessionuser'], 12 | zip_safe=False, 13 | classifiers=[ 14 | 'Development Status :: 4 - Beta', 15 | 'Environment :: Web Environment', 16 | 'Intended Audience :: Developers', 17 | 'License :: OSI Approved :: BSD License', 18 | 'Operating System :: OS Independent', 19 | 'Programming Language :: Python', 20 | 'Framework :: Django', 21 | ] 22 | ) -------------------------------------------------------------------------------- /LICENSE.py: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Eric Florenzano 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | * Neither the name of the author nor the names of other 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | =================== 2 | django-session-user 3 | =================== 4 | 5 | This app is a simple piece of middleware that can be added to your Django 6 | project which will store and retrieve the logged-in user's information from 7 | the session. 8 | 9 | 10 | Installation 11 | ------------ 12 | 13 | Add the sesionuser middleware line to your MIDDLEWARE_CLASSES after the 14 | AuthenticationMiddleware: 15 | 16 | MIDDLEWARE_CLASSES = ( 17 | # ... 18 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 19 | 'sessionuser.middleware.SessionUserMiddleware', 20 | # ... 21 | ) 22 | 23 | 24 | Why do this? 25 | ------------ 26 | 27 | Your server is already fetching the contents of the user's session, which 28 | already contains the user's identity. Why not store the rest of the user 29 | class's data along with it? That way you don't have to make a request to the 30 | database for every authenticated web request. 31 | 32 | Additionally if you are using a cookie-based session backend (like 33 | django-cookie-sessions, written by yours truly) you can have other systems, 34 | maybe even non-Django systems, which read the cookie and know more information 35 | about the user. 36 | 37 | 38 | Customizable Settings 39 | --------------------- 40 | 41 | COOKIE_USER_REFRESH_TIME [= 14400]: 42 | 43 | The number of seconds that need to elapse before the user is fetched from 44 | the database instead of trusting the cookie. This is useful for making 45 | sure that even if the user's properties are changed in the database, 46 | the user's cookie will still be updated. -------------------------------------------------------------------------------- /sessionuser/middleware.py: -------------------------------------------------------------------------------- 1 | import calendar 2 | import copy 3 | import datetime 4 | import time 5 | 6 | from django.conf import settings 7 | from django.db.models.signals import post_save 8 | 9 | from django.contrib import auth 10 | from django.contrib.auth.models import User 11 | 12 | COOKIE_USER_DATA_KEY = '_UD' 13 | COOKIE_USER_DATA_TS_KEY = '_UDT' 14 | # Defaults to 4 hours in seconds 15 | COOKIE_USER_REFRESH_TIME = getattr(settings, 'COOKIE_USER_REFRESH_TIME', 16 | 14400) 17 | 18 | auth_get_user = copy.deepcopy(auth.get_user) 19 | 20 | 21 | def datetime_to_timestamp(dt): 22 | ts = calendar.timegm(dt.timetuple()) + (dt.microsecond / 1000000.0) 23 | return long(ts * 1000000.0) 24 | 25 | def timestamp_to_datetime(ts): 26 | return datetime.datetime.utcfromtimestamp(ts / 1000000.0) 27 | 28 | def cookie_set_user(request, force=False): 29 | user = request.user 30 | data = [ 31 | user.username, 32 | user.first_name, 33 | user.last_name, 34 | user.email, 35 | user.password, 36 | user.is_staff, 37 | user.is_active, 38 | user.is_superuser, 39 | datetime_to_timestamp(user.last_login), 40 | datetime_to_timestamp(user.date_joined), 41 | ] 42 | if force or data != request.session.get(COOKIE_USER_DATA_KEY): 43 | request.session[COOKIE_USER_DATA_KEY] = data 44 | request.session[COOKIE_USER_DATA_TS_KEY] = time.time() 45 | 46 | def cookie_get_user(request): 47 | # If it's been more than COOKIE_USER_REFRESH_TIME since the last time that 48 | # we set the user data stuff, then pull the user from the backend rather 49 | # than from the cookie. 50 | if COOKIE_USER_DATA_TS_KEY in request.session: 51 | diff = time.time() - request.session[COOKIE_USER_DATA_TS_KEY] 52 | if diff > COOKIE_USER_REFRESH_TIME: 53 | request._force_update_user = True 54 | return auth_get_user(request) 55 | 56 | user_id = request.session.get(auth.SESSION_KEY) 57 | if not user_id: 58 | return auth_get_user(request) 59 | 60 | data = request.session.get(COOKIE_USER_DATA_KEY) 61 | if not data: 62 | return auth_get_user(request) 63 | 64 | user = User() 65 | try: 66 | user.id = user_id 67 | user.username = data[0] 68 | user.first_name = data[1] 69 | user.last_name = data[2] 70 | user.email = data[3] 71 | user.password = data[4] 72 | user.is_staff = data[5] 73 | user.is_active = data[6] 74 | user.is_superuser = data[7] 75 | user.last_login = timestamp_to_datetime(data[8]) 76 | user.date_joined = timestamp_to_datetime(data[9]) 77 | except (IndexError, TypeError): 78 | return auth_get_user(request) 79 | return user 80 | 81 | 82 | class SessionUserMiddleware(object): 83 | 84 | def __init__(self): 85 | """ 86 | It makes me sad that ``auth.get_user`` can't be customized, but instead we 87 | have to monkeypatch it. 88 | """ 89 | auth.get_user = cookie_get_user 90 | 91 | def process_request(self, request): 92 | if getattr(request, 'user', None) is None: 93 | return None 94 | 95 | # If the user isn't authenticated, then there's no way that the 96 | # "current user" could see any user attribute changes, since there is 97 | # no "current user" 98 | if not request.user.is_authenticated(): 99 | # Set a property we can check later to see if the user logged in 100 | request._force_update_user = True 101 | return None 102 | 103 | # We create a function that's has a closure on the current request 104 | def post_user_save(sender, instance, **kwargs): 105 | # If the saved user is different from the current user, bail early 106 | if instance.id != request.user.id: 107 | return None 108 | # Otherwise, replace the request's user with the newly-saved user 109 | request.user = instance 110 | 111 | # And then we actually save a reference to it on the request 112 | request.post_user_save = post_user_save 113 | 114 | # Connect the signal 115 | post_save.connect(post_user_save, sender=User) 116 | 117 | def process_response(self, request, response): 118 | if getattr(request, 'user', None) is None: 119 | return response 120 | 121 | # If there's a post_user_save function on the request, disconnect it 122 | post_user_save = getattr(request, 'post_user_save', None) 123 | if post_user_save: 124 | post_save.disconnect(post_user_save, sender=User) 125 | 126 | # If the user is now logged in, make sure the cookie matches the data 127 | # that's stored on the request's user object 128 | if request.user.is_authenticated(): 129 | # If we've explicitly set the force flag, make it True 130 | force = getattr(request, '_force_update_user', False) 131 | cookie_set_user(request, force=force) 132 | return response --------------------------------------------------------------------------------