Skip to content

Commit f84136f

Browse files
authored
Merge pull request #8 from bugov/add-idle-check
Add type checking for SESSION_TIME, IDLE_TIME. int and datetime.timedelta are allowed.
2 parents 48e2328 + fd68224 commit f84136f

File tree

5 files changed

+120
-20
lines changed

5 files changed

+120
-20
lines changed

README.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,32 @@ Add to `settings.py`:
4545
AUTO_LOGOUT = {'IDLE_TIME': 600} # logout after 10 minutes of downtime
4646
```
4747

48+
or the same, but with `datetime.timedelta` (more semantically):
49+
50+
```python
51+
from datetime import timedelta
52+
53+
AUTO_LOGOUT = {'IDLE_TIME': timedelta(minutes=10)}
54+
```
55+
4856
## ⌛ Limit session time
4957

5058
Logout a user after 3600 seconds (hour) from the last login.
5159

5260
Add to `settings.py`:
5361

54-
5562
```python
5663
AUTO_LOGOUT = {'SESSION_TIME': 3600}
5764
```
5865

66+
or the same, but with `datetime.timedelta` (more semantically):
67+
68+
```python
69+
from datetime import timedelta
70+
71+
AUTO_LOGOUT = {'SESSION_TIME': timedelta(hours=1)}
72+
```
73+
5974
## ✉️ Show messages when logging out automatically
6075

6176
Set the message that will be displayed after the user automatically logs out of the system:
@@ -95,9 +110,11 @@ in case of downtime (5 minutes or more) and not allow working within one session
95110
for more than half an hour:
96111

97112
```python
113+
from datetime import timedelta
114+
98115
AUTO_LOGOUT = {
99-
'IDLE_TIME': 300, # 5 minutes
100-
'SESSION_TIME': 1800, # 30 minutes
116+
'IDLE_TIME': timedelta(minutes=5),
117+
'SESSION_TIME': timedelta(minutes=30),
101118
'MESSAGE': 'The session has expired. Please login again to continue.',
102119
}
103120
```

README.rst

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ Add to `settings`:
4444
4545
AUTO_LOGOUT = {'IDLE_TIME': 600} # logout after 10 minutes of downtime
4646
47+
or the same, but with `datetime.timedelta` (more semantically):
48+
49+
.. code:: python
50+
51+
AUTO_LOGOUT = {'IDLE_TIME': timedelta(minutes=10)}
4752
4853
Limit session time
4954
------------------
@@ -56,6 +61,12 @@ Add to `settings`:
5661
5762
AUTO_LOGOUT = {'SESSION_TIME': 3600}
5863
64+
or the same, but with `datetime.timedelta` (more semantically):
65+
66+
.. code:: python
67+
68+
AUTO_LOGOUT = {'SESSION_TIME': timedelta(hours=1)}
69+
5970
Show messages when logging out automatically
6071
--------------------------------------------
6172

@@ -95,8 +106,10 @@ for more than half an hour:
95106

96107
.. code:: python
97108
109+
from datetime import timedelta
110+
98111
AUTO_LOGOUT = {
99-
'IDLE_TIME': 300, # 5 minutes
100-
'SESSION_TIME': 1800, # 30 minutes
112+
'IDLE_TIME': timedelta(minutes=5),
113+
'SESSION_TIME': timedelta(minutes=30),
101114
'MESSAGE': 'The session has expired. Please login again to continue.',
102115
}

django_auto_logout/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.3.2'
1+
__version__ = '0.4.0'

django_auto_logout/middleware.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,41 @@ def _auto_logout(request: HttpRequest, options):
2121
now = datetime.now()
2222

2323
if options.get('SESSION_TIME') is not None:
24-
ttl = timedelta(seconds=options['SESSION_TIME'])
24+
if isinstance(options['SESSION_TIME'], timedelta):
25+
ttl = options['SESSION_TIME']
26+
elif isinstance(options['SESSION_TIME'], int):
27+
ttl = timedelta(seconds=options['SESSION_TIME'])
28+
else:
29+
raise TypeError(f"AUTO_LOGOUT['SESSION_TIME'] should be `int` or `timedelta`, "
30+
f"not `{type(options['SESSION_TIME']).__name__}`.")
31+
2532
time_expired = user.last_login < now - ttl
2633
should_logout |= time_expired
2734
logger.debug('Check SESSION_TIME: %s < %s (%s)', user.last_login, now, time_expired)
2835

2936
if options.get('IDLE_TIME') is not None:
30-
ttl = timedelta(seconds=options['IDLE_TIME'])
37+
if isinstance(options['IDLE_TIME'], timedelta):
38+
ttl = options['IDLE_TIME']
39+
elif isinstance(options['IDLE_TIME'], int):
40+
ttl = timedelta(seconds=options['IDLE_TIME'])
41+
else:
42+
raise TypeError(f"AUTO_LOGOUT['IDLE_TIME'] should be `int` or `timedelta`, "
43+
f"not `{type(options['IDLE_TIME']).__name__}`.")
3144

32-
if 'django_auto_logout_last_touch' in request.session:
33-
last_touch = datetime.fromisoformat(request.session['django_auto_logout_last_touch'])
45+
if 'django_auto_logout_last_request' in request.session:
46+
last_req = datetime.fromisoformat(request.session['django_auto_logout_last_request'])
3447
else:
35-
last_touch = now
36-
request.session['django_auto_logout_last_touch'] = last_touch.isoformat()
48+
last_req = now
49+
request.session['django_auto_logout_last_request'] = last_req.isoformat()
3750

38-
time_expired = last_touch < now - ttl
51+
time_expired = last_req < now - ttl
3952
should_logout |= time_expired
40-
logger.debug('Check IDLE_TIME: %s < %s (%s)', last_touch, now, time_expired)
53+
logger.debug('Check IDLE_TIME: %s < %s (%s)', last_req, now, time_expired)
4154

4255
if should_logout:
43-
del request.session['django_auto_logout_last_touch']
56+
del request.session['django_auto_logout_last_request']
4457
else:
45-
request.session['django_auto_logout_last_touch'] = now.isoformat()
58+
request.session['django_auto_logout_last_request'] = now.isoformat()
4659

4760
if should_logout:
4861
logger.debug('Logout user %s', user)

example/some_app_login_required/tests.py

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from time import sleep
2+
from datetime import timedelta
23
from django.test import TestCase
34
from django.conf import settings
45
from django.contrib.auth import get_user_model
@@ -24,7 +25,6 @@ def assertLoginRequiredRedirect(self):
2425
return resp
2526

2627
def _logout_session_time(self):
27-
settings.AUTO_LOGOUT = {'SESSION_TIME': 1}
2828
self.assertLoginRequiredRedirect()
2929

3030
self.client.force_login(self.user)
@@ -34,35 +34,68 @@ def _logout_session_time(self):
3434
self.assertLoginRequiredRedirect()
3535

3636
def test_logout_session_time(self):
37+
settings.AUTO_LOGOUT = {'SESSION_TIME': 1}
3738
settings.USE_TZ = False
3839
self._logout_session_time()
3940

4041
def test_logout_session_time_using_tz_utc(self):
42+
settings.AUTO_LOGOUT = {'SESSION_TIME': 1}
4143
settings.USE_TZ = True
4244
self._logout_session_time()
4345

4446
def test_logout_session_time_using_tz_non_utc(self):
47+
settings.AUTO_LOGOUT = {'SESSION_TIME': 1}
4548
settings.USE_TZ = True
4649
settings.TIME_ZONE = 'Asia/Yekaterinburg'
4750
self._logout_session_time()
4851

49-
def test_logout_idle_time_no_idle(self):
50-
settings.AUTO_LOGOUT = {'IDLE_TIME': 1}
52+
def test_logout_session_time_timedelta(self):
53+
settings.AUTO_LOGOUT = {'SESSION_TIME': timedelta(seconds=1)}
54+
settings.USE_TZ = False
55+
self._logout_session_time()
56+
57+
def test_logout_session_time_using_tz_utc_timedelta(self):
58+
settings.AUTO_LOGOUT = {'SESSION_TIME': timedelta(seconds=1)}
59+
settings.USE_TZ = True
60+
self._logout_session_time()
61+
62+
def test_logout_session_time_using_tz_non_utc_timedelta(self):
63+
settings.AUTO_LOGOUT = {'SESSION_TIME': timedelta(seconds=1)}
64+
settings.USE_TZ = True
65+
settings.TIME_ZONE = 'Asia/Yekaterinburg'
66+
self._logout_session_time()
67+
68+
def _test_logout_idle_time_no_idle(self):
5169
self.client.force_login(self.user)
5270
self.assertLoginRequiredIsOk()
5371

5472
for _ in range(10):
5573
sleep(0.5)
5674
self.assertLoginRequiredIsOk()
5775

58-
def test_logout_idle_time(self):
76+
def test_logout_idle_time_no_idle(self):
5977
settings.AUTO_LOGOUT = {'IDLE_TIME': 1}
78+
self._test_logout_idle_time_no_idle()
79+
80+
def test_logout_idle_time_no_idle_timedelta(self):
81+
settings.AUTO_LOGOUT = {'IDLE_TIME': timedelta(seconds=1)}
82+
self._test_logout_idle_time_no_idle()
83+
84+
def _test_logout_idle_time(self):
6085
self.client.force_login(self.user)
6186
self.assertLoginRequiredIsOk()
6287

6388
sleep(1.5)
6489
self.assertLoginRequiredRedirect()
6590

91+
def test_logout_idle_time(self):
92+
settings.AUTO_LOGOUT = {'IDLE_TIME': 1}
93+
self._test_logout_idle_time()
94+
95+
def test_logout_idle_time_timedelta(self):
96+
settings.AUTO_LOGOUT = {'IDLE_TIME': timedelta(seconds=1)}
97+
self._test_logout_idle_time()
98+
6699
def test_combine_idle_and_session_time(self):
67100
settings.AUTO_LOGOUT = {
68101
'IDLE_TIME': 1,
@@ -81,6 +114,30 @@ def test_combine_idle_and_session_time(self):
81114
sleep(0.5)
82115
self.assertLoginRequiredRedirect()
83116

117+
def test_session_time_config_time(self):
118+
settings.AUTO_LOGOUT = {
119+
'IDLE_TIME': 1,
120+
'SESSION_TIME': '2',
121+
}
122+
123+
self.client.force_login(self.user)
124+
125+
exc_message = "AUTO_LOGOUT['SESSION_TIME'] should be `int` or `timedelta`, not `str`."
126+
with self.assertRaisesMessage(TypeError, exc_message):
127+
self.client.get('/login-required/')
128+
129+
def test_idle_time_config_time(self):
130+
settings.AUTO_LOGOUT = {
131+
'IDLE_TIME': '1',
132+
'SESSION_TIME': 2,
133+
}
134+
135+
self.client.force_login(self.user)
136+
137+
exc_message = "AUTO_LOGOUT['IDLE_TIME'] should be `int` or `timedelta`, not `str`."
138+
with self.assertRaisesMessage(TypeError, exc_message):
139+
self.client.get('/login-required/')
140+
84141
def test_combine_idle_and_session_time_but_session_less_than_idle(self):
85142
settings.AUTO_LOGOUT = {
86143
'IDLE_TIME': 2,

0 commit comments

Comments
 (0)