Skip to content

Commit 4e87188

Browse files
authored
Merge pull request #180 from loicgasser/issue_178_bis
Add setting for expiry format, add helper methods in login view
2 parents dfca07c + e9f6775 commit 4e87188

File tree

7 files changed

+99
-19
lines changed

7 files changed

+99
-19
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 4.0.1
2+
3+
- Fix for tox config to build Django 2.2 on python 3.6
4+
15
## 4.0.0
26

37
**BREAKING** This is a major release version because it

docs/settings.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ Example `settings.py`
99
#...snip...
1010
# These are the default values if none are set
1111
from datetime import timedelta
12+
from rest_framework.settings import api_settings
1213
REST_KNOX = {
1314
'SECURE_HASH_ALGORITHM': 'cryptography.hazmat.primitives.hashes.SHA512',
1415
'AUTH_TOKEN_CHARACTER_LENGTH': 64,
1516
'TOKEN_TTL': timedelta(hours=10),
1617
'USER_SERIALIZER': 'knox.serializers.UserSerializer',
1718
'TOKEN_LIMIT_PER_USER': None,
1819
'AUTO_REFRESH': False,
20+
'EXPIRY_DATETIME_FORMAT': api_settings.DATETME_FORMAT,
1921
}
2022
#...snip...
2123
```
@@ -74,6 +76,14 @@ in the database.
7476
## AUTH_HEADER_PREFIX
7577
This is the Authorization header value prefix. The default is `Token`
7678

79+
## EXPIRY_DATETIME_FORMAT
80+
This is the expiry datetime format returned in the login view. The default is the
81+
[DATETIME_FORMAT][DATETIME_FORMAT] of Django REST framework. May be any of `None`, `iso-8601`
82+
or a Python [strftime format][strftime format] string.
83+
84+
[DATETIME_FORMAT]: https://www.django-rest-framework.org/api-guide/settings/#date-and-time-formatting
85+
[strftime format]: https://docs.python.org/3/library/time.html#time.strftime
86+
7787
# Constants `knox.settings`
7888
Knox also provides some constants for information. These must not be changed in
7989
external code; they are used in the model definitions in knox and an error will

docs/views.md

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,36 @@ default, you can extend this class to provide your own value for
1515

1616
It is possible to customize LoginView behaviour by overriding the following
1717
helper methods:
18-
- `get_context`, to change the context passed to the `UserSerializer`
19-
- `get_token_ttl`, to change the token ttl
20-
- `get_token_limit_per_user`, to change the number of tokens available for a user
21-
- `get_user_serializer_class`, to change the class used for serializing the user
18+
- `get_context(self)`, to change the context passed to the `UserSerializer`
19+
- `get_token_ttl(self)`, to change the token ttl
20+
- `get_token_limit_per_user(self)`, to change the number of tokens available for a user
21+
- `get_user_serializer_class(self)`, to change the class used for serializing the user
22+
- `get_expiry_datetime_format(self)`, to change the datetime format used for expiry
23+
- `format_expiry_datetime(self, expiry)`, to format the expiry `datetime` object at your convinience
24+
25+
Finally, if none of these helper methods are sufficient, you can also override `get_post_response_data`
26+
to return a fully customized payload.
27+
28+
```python
29+
...snip...
30+
def get_post_response_data(self, request, token, instance):
31+
UserSerializer = self.get_user_serializer_class()
32+
33+
data = {
34+
'expiry': self.format_expiry_datetime(instance.expiry),
35+
'token': token
36+
}
37+
if UserSerializer is not None:
38+
data["user"] = UserSerializer(
39+
request.user,
40+
context=self.get_context()
41+
).data
42+
return data
43+
...snip...
44+
```
2245

2346
---
24-
When the endpoint authenticates a request, a json object will be returned
47+
When the endpoint authenticates a request, a json object will be returned
2548
containing the `token` key along with the actual value for the key by default.
2649
The success response also includes a `expiry` key with a timestamp for when
2750
the token expires.

knox/settings.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from django.conf import settings
44
from django.test.signals import setting_changed
5-
from rest_framework.settings import APISettings
5+
from rest_framework.settings import APISettings, api_settings
66

77
USER_SETTINGS = getattr(settings, 'REST_KNOX', None)
88

@@ -15,6 +15,7 @@
1515
'AUTO_REFRESH': False,
1616
'MIN_REFRESH_INTERVAL': 60,
1717
'AUTH_HEADER_PREFIX': 'Token',
18+
'EXPIRY_DATETIME_FORMAT': api_settings.DATETIME_FORMAT,
1819
}
1920

2021
IMPORT_STRINGS = {

knox/views.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from rest_framework import status
44
from rest_framework.permissions import IsAuthenticated
55
from rest_framework.response import Response
6+
from rest_framework.serializers import DateTimeField
67
from rest_framework.settings import api_settings
78
from rest_framework.views import APIView
89

@@ -27,6 +28,27 @@ def get_token_limit_per_user(self):
2728
def get_user_serializer_class(self):
2829
return knox_settings.USER_SERIALIZER
2930

31+
def get_expiry_datetime_format(self):
32+
return knox_settings.EXPIRY_DATETIME_FORMAT
33+
34+
def format_expiry_datetime(self, expiry):
35+
datetime_format = self.get_expiry_datetime_format()
36+
return DateTimeField(format=datetime_format).to_representation(expiry)
37+
38+
def get_post_response_data(self, request, token, instance):
39+
UserSerializer = self.get_user_serializer_class()
40+
41+
data = {
42+
'expiry': self.format_expiry_datetime(instance.expiry),
43+
'token': token
44+
}
45+
if UserSerializer is not None:
46+
data["user"] = UserSerializer(
47+
request.user,
48+
context=self.get_context()
49+
).data
50+
return data
51+
3052
def post(self, request, format=None):
3153
token_limit_per_user = self.get_token_limit_per_user()
3254
if token_limit_per_user is not None:
@@ -41,17 +63,7 @@ def post(self, request, format=None):
4163
instance, token = AuthToken.objects.create(request.user, token_ttl)
4264
user_logged_in.send(sender=request.user.__class__,
4365
request=request, user=request.user)
44-
UserSerializer = self.get_user_serializer_class()
45-
46-
data = {
47-
'expiry': instance.expiry,
48-
'token': token
49-
}
50-
if UserSerializer is not None:
51-
data["user"] = UserSerializer(
52-
request.user,
53-
context=self.get_context()
54-
).data
66+
data = self.get_post_response_data(request, token, instance)
5567
return Response(data)
5668

5769

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
# Versions should comply with PEP440. For a discussion on single-sourcing
1818
# the version across setup.py and the project code, see
1919
# https://packaging.python.org/en/latest/single_source_version.html
20-
version='4.0.0',
20+
version='4.0.1',
2121
description='Authentication for django rest framework',
2222
long_description=long_description,
2323
long_description_content_type='text/markdown',

tests/tests.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from django.test import override_settings
66
from django.utils.six.moves import reload_module
77
from freezegun import freeze_time
8+
from rest_framework.serializers import DateTimeField
89
from rest_framework.test import APIRequestFactory, APITestCase as TestCase
910

1011
from knox import auth, views
@@ -45,6 +46,10 @@ def get_basic_auth_header(username, password):
4546
token_no_expiration_knox = knox_settings.defaults.copy()
4647
token_no_expiration_knox["TOKEN_TTL"] = None
4748

49+
EXPIRY_DATETIME_FORMAT = '%H:%M %d/%m/%y'
50+
expiry_datetime_format_knox = knox_settings.defaults.copy()
51+
expiry_datetime_format_knox["EXPIRY_DATETIME_FORMAT"] = EXPIRY_DATETIME_FORMAT
52+
4853

4954
class AuthTestCase(TestCase):
5055

@@ -101,6 +106,31 @@ def test_login_returns_serialized_token_and_username_field(self):
101106
self.assertIn('user', response.data)
102107
self.assertIn(username_field, response.data['user'])
103108

109+
def test_login_returns_configured_expiry_datetime_format(self):
110+
111+
with override_settings(REST_KNOX=expiry_datetime_format_knox):
112+
reload_module(views)
113+
self.assertEqual(AuthToken.objects.count(), 0)
114+
url = reverse('knox_login')
115+
self.client.credentials(
116+
HTTP_AUTHORIZATION=get_basic_auth_header(self.username, self.password)
117+
)
118+
response = self.client.post(url, {}, format='json')
119+
self.assertEqual(
120+
expiry_datetime_format_knox["EXPIRY_DATETIME_FORMAT"],
121+
EXPIRY_DATETIME_FORMAT
122+
)
123+
reload_module(views)
124+
self.assertEqual(response.status_code, 200)
125+
self.assertIn('token', response.data)
126+
self.assertNotIn('user', response.data)
127+
self.assertEqual(
128+
response.data['expiry'],
129+
DateTimeField(format=EXPIRY_DATETIME_FORMAT).to_representation(
130+
AuthToken.objects.first().expiry
131+
)
132+
)
133+
104134
def test_logout_deletes_keys(self):
105135
self.assertEqual(AuthToken.objects.count(), 0)
106136
for _ in range(2):
@@ -364,5 +394,5 @@ def test_expiry_is_present(self):
364394
self.assertIn('expiry', response.data)
365395
self.assertEqual(
366396
response.data['expiry'],
367-
AuthToken.objects.first().expiry
397+
DateTimeField().to_representation(AuthToken.objects.first().expiry)
368398
)

0 commit comments

Comments
 (0)