--- /dev/null
+default_app_config = 'better_sessions.apps.BetterSessionsConfig'
--- /dev/null
+from django.contrib import admin
+
+from .models import UserSession
+
+
+@admin.register(UserSession)
+class UserSessionAdmin(admin.ModelAdmin):
+ list_display = ('user', 'key', 'created', 'updated')
+ list_filter = ('user',)
+ date_hierarchy = 'updated'
+ search_fields = ('user__username', 'key')
+ fields = ('user', 'key', 'created', 'updated')
+ readonly_fields = ('created', 'updated')
+
+ def get_queryset(self, request):
+ return super(UserSessionAdmin, self).get_queryset(request).select_related('user')
--- /dev/null
+from django.apps import AppConfig
+
+
+class BetterSessionsConfig(AppConfig):
+ name = 'better_sessions'
+ verbose_name = "Better Sessions"
+
+ def ready(self):
+ import signals
--- /dev/null
+from .settings import WARN_AFTER, EXPIRE_AFTER
+
+
+def settings(_):
+ return {'session_warn_after': WARN_AFTER, 'session_expire_after': EXPIRE_AFTER}
--- /dev/null
+import time
+
+from django.contrib.auth import logout
+
+from .settings import EXPIRE_AFTER
+
+
+class BetterSessionsMiddleware(object):
+ def process_request(self, request):
+ """ Update last activity time or logout. """
+ if request.user.is_authenticated():
+ now = time.time()
+ last_activity = request.session.get('last_activity', now)
+
+ print repr(now), repr(last_activity), now - last_activity
+
+ if now - last_activity > EXPIRE_AFTER:
+ print 'expired!'
+ logout(request)
+ else:
+ print 'prolong'
+ request.session['last_activity'] = now
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='UserSession',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('key', models.CharField(max_length=40, verbose_name='Klucz sesji')),
+ ('created', models.DateTimeField(auto_now_add=True, verbose_name='Utworzono')),
+ ('updated', models.DateTimeField(auto_now=True, verbose_name='Uaktualniono')),
+ ('user', models.OneToOneField(related_name='session', verbose_name='U\u017cytkownik', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'ordering': ('-updated',),
+ 'verbose_name': 'Sesja u\u017cytkownika',
+ 'verbose_name_plural': 'Sesje u\u017cytkownik\xf3w',
+ },
+ ),
+ ]
--- /dev/null
+# coding=utf-8
+from django.conf import settings
+from django.db import models
+from django.contrib.sessions.models import Session
+
+
+class UserSession(models.Model):
+ user = models.OneToOneField(settings.AUTH_USER_MODEL, verbose_name=u"Użytkownik", related_name='session')
+ key = models.CharField(u"Klucz sesji", max_length=40)
+ created = models.DateTimeField(u"Utworzono", auto_now_add=True)
+ updated = models.DateTimeField(u"Uaktualniono", auto_now=True)
+
+ class Meta:
+ verbose_name = u"Sesja użytkownika"
+ verbose_name_plural = u"Sesje użytkowników"
+ ordering = ('-updated',)
+
+ def __unicode__(self):
+ return '{} - {}'.format(self.user, self.key)
--- /dev/null
+import warnings
+
+from django.conf import settings
+
+__all__ = ['EXPIRE_AFTER', 'WARN_AFTER']
+
+# Limit number of active session per user to only one
+SINGLE_SESSION = getattr(settings, 'BETTER_SESSIONS_SINGLE_SESSION', True)
+
+# Time (in seconds) before the user should be warned that its session will expire because of inactivity
+WARN_AFTER = getattr(settings, 'BETTER_SESSIONS_WARN_AFTER', 30)
+
+# Time (in seconds) before the user should be logged out if inactive
+EXPIRE_AFTER = getattr(settings, 'BETTER_SESSIONS_EXPIRE_AFTER', 45)
+
+if not getattr(settings, 'SESSION_EXPIRE_AT_BROWSER_CLOSE', False):
+ warnings.warn('settings.SESSION_EXPIRE_AT_BROWSER_CLOSE is not True')
--- /dev/null
+from django.contrib.auth import user_logged_in
+from django.contrib.sessions.models import Session
+
+from better_sessions.models import UserSession
+
+from .settings import SINGLE_SESSION
+
+
+def post_user_login(sender, request, user, **kwargs):
+ try:
+ Session.objects.filter(session_key=user.session.key).delete()
+ except UserSession.DoesNotExist:
+ user.session = UserSession()
+
+ user.session.key = request.session.session_key
+ user.session.save()
+
+
+if SINGLE_SESSION:
+ user_logged_in.connect(post_user_login)
--- /dev/null
+"use strict";
+
+var psnc = psnc || {};
+
+psnc.BetterSession = function (options) {
+ $.extend(this, this.defaults, options);
+
+ // account for network delays and rendering time
+ this.warnAfter -= 5;
+ this.expireAfter -= 5;
+
+ this.postponeWarn = _.debounce(this.warn, this.warnAfter * 1000);
+ this.postponeExpire = _.debounce(this.expire, this.expireAfter * 1000);
+
+ $(this.warnSel).find('button').on('click', $.proxy(this.ping, this));
+
+ // update session whenever user loaded sth with ajax...
+ $(document).ajaxComplete($.proxy(this.update, this, true));
+ // ...or in another window
+ $(window).on('storage', $.proxy(this.update, this));
+
+ this.update(true);
+
+ return this;
+};
+
+psnc.BetterSession.prototype = {
+ defaults: {
+ expired: false,
+ warnAfter: 3300,
+ expireAfter: 3600,
+ key: 'lastActivity',
+ pingUrl: '/session/ping/',
+ warnSel: '#better-sessions-warn',
+ expireSel: '#better-sessions-expire',
+ timerSel: '#better-sessions-timer'
+ },
+
+ update: _.throttle(function (local) {
+ if (this.expired) return;
+
+ $(this.warnSel).hide();
+ clearInterval(this.timer);
+
+ // postpone expire message and warning
+ this.postponeWarn();
+ this.postponeExpire();
+
+ // update timestamp only if it is local event
+ if (local === true)
+ localStorage.setItem(this.key, new Date());
+ }, 1000),
+
+ ping: function () {
+ var self = this;
+ $.post(this.pingUrl, {}, function(response) {
+ // session is implicitly extended after every ajax request
+ if (response.status === 'expired')
+ self.expire();
+ }, 'json');
+ },
+
+ warn: function () {
+ this.timer = setInterval($.proxy(this.updateTimer, this), 1000);
+ this.updateTimer();
+ $(this.warnSel).show();
+ },
+
+ expire: function () {
+ this.expired = true;
+
+ $(this.warnSel).hide();
+ $(this.expireSel).show();
+ clearInterval(this.timer);
+ },
+
+ updateTimer: function() {
+ var remaining = Date.parse(localStorage.getItem(this.key)) - new Date() + (this.expireAfter * 1000);
+ $(this.timerSel).text((humanizeDuration(remaining, {language: 'pl', round: true}) || 'chwilę'))
+ }
+};
--- /dev/null
+<div id="better-sessions-warn" class="alert alert-warning" role="alert">
+ <strong>Uwaga!</strong> Twoja sesja wygaśnie za <span id="better-sessions-timer"></span>.
+ Kliknij <button class="btn btn-default">tutaj</button> aby przedłużyć sesję.
+</div>
+
+<div id="better-sessions-expire" class="alert alert-danger" role="alert" hidden>
+ <strong>Uwaga!</strong> Twoja sesja wygasła. Przeładowanie strony może spowodować utratę danych wprowadzonych w formularzach.
+</div>
--- /dev/null
+from django.test import TestCase
+
+# Create your tests here.
--- /dev/null
+from django.conf.urls import patterns, url
+
+from better_sessions.views import ping
+
+urlpatterns = patterns('',
+ url('ping/$', ping, name='ping')
+)
--- /dev/null
+from django.http.response import JsonResponse
+
+
+def ping(request):
+ return JsonResponse({'status': 'ok' if request.user.is_authenticated() else 'expired'})