0
# Compatibility Utilities
1
2
Helper functions and classes for cross-version compatibility and Django integration, including user model handling and field utilities. These utilities ensure the JWT package works across different Django and Django REST Framework versions.
3
4
## Capabilities
5
6
### User Model Utilities
7
8
Functions for handling Django user model interactions across different configurations.
9
10
```python { .api }
11
def get_username_field():
12
"""
13
Returns the username field name from the configured User model.
14
15
Returns:
16
str: Username field name (e.g., 'username', 'email', 'phone')
17
18
Notes:
19
- Uses User.USERNAME_FIELD if available
20
- Falls back to 'username' if User model is not accessible
21
- Handles cases where User model is not properly configured
22
23
Examples:
24
- Default Django User: returns 'username'
25
- Custom User with email auth: returns 'email'
26
- Custom User with phone auth: returns 'phone'
27
"""
28
29
def get_username(user):
30
"""
31
Extracts username value from a user instance.
32
33
Args:
34
user: Django user model instance
35
36
Returns:
37
str: Username value from the user instance
38
39
Notes:
40
- Uses user.get_username() method if available (Django 1.5+)
41
- Falls back to user.username attribute for older versions
42
- Handles different User model implementations
43
44
Compatibility:
45
- Django 1.4: Uses user.username attribute
46
- Django 1.5+: Uses user.get_username() method
47
"""
48
```
49
50
### Serializer Compatibility
51
52
Compatibility layer for Django REST Framework serializer changes across versions.
53
54
```python { .api }
55
class Serializer(serializers.Serializer):
56
@property
57
def object(self):
58
"""
59
Backward compatibility property for accessing validated data.
60
61
Returns:
62
dict: Validated data from the serializer
63
64
Notes:
65
- In DRF 3.0+: Returns self.validated_data
66
- Provides compatibility for code expecting .object attribute
67
- Deprecated in newer DRF versions but maintained for compatibility
68
"""
69
```
70
71
### Form Field Utilities
72
73
Custom field classes that ensure consistent behavior across framework versions.
74
75
```python { .api }
76
class PasswordField(serializers.CharField):
77
def __init__(self, *args, **kwargs):
78
"""
79
Password input field with consistent styling across DRF versions.
80
81
Args:
82
*args: Positional arguments for CharField
83
**kwargs: Keyword arguments for CharField
84
85
Features:
86
- Automatically sets input_type to 'password'
87
- Preserves existing style configuration if provided
88
- Ensures password fields are rendered with proper input type
89
90
Usage:
91
password = PasswordField(write_only=True, required=True)
92
"""
93
```
94
95
## Usage Examples
96
97
### Custom User Model Integration
98
99
```python
100
from rest_framework_jwt.compat import get_username_field, get_username
101
from django.contrib.auth import get_user_model
102
103
User = get_user_model()
104
105
# Handle different username field configurations
106
username_field = get_username_field()
107
print(f"Authentication uses field: {username_field}")
108
109
# Example with email-based authentication
110
class EmailUser(AbstractUser):
111
email = models.EmailField(unique=True)
112
USERNAME_FIELD = 'email'
113
REQUIRED_FIELDS = ['username']
114
115
# get_username_field() returns 'email'
116
# get_username(user_instance) returns user.email value
117
```
118
119
### Serializer Backward Compatibility
120
121
```python
122
from rest_framework_jwt.compat import Serializer
123
from rest_framework import serializers
124
125
class CustomAuthSerializer(Serializer): # Using compat Serializer
126
username = serializers.CharField()
127
password = serializers.CharField()
128
129
def validate(self, attrs):
130
# Validation logic here
131
return attrs
132
133
# Usage that works across DRF versions
134
serializer = CustomAuthSerializer(data=request.data)
135
if serializer.is_valid():
136
# Both work due to compatibility layer:
137
data = serializer.object # Backward compatibility
138
data = serializer.validated_data # Modern approach
139
```
140
141
### Password Field Usage
142
143
```python
144
from rest_framework_jwt.compat import PasswordField
145
from rest_framework import serializers
146
147
class LoginSerializer(serializers.Serializer):
148
username = serializers.CharField()
149
password = PasswordField(write_only=True) # Ensures password input type
150
151
# Rendered as <input type="password"> in forms
152
# Ensures consistent behavior across DRF versions
153
```
154
155
### Cross-Version User Handling
156
157
```python
158
from rest_framework_jwt.compat import get_username, get_username_field
159
from django.contrib.auth import get_user_model
160
161
def create_jwt_payload(user):
162
"""Create JWT payload compatible with any User model configuration."""
163
164
# Get username regardless of field name or Django version
165
username = get_username(user)
166
username_field = get_username_field()
167
168
payload = {
169
'user_id': user.pk,
170
'username': username,
171
username_field: username, # Include both for flexibility
172
}
173
174
return payload
175
176
def authenticate_user(credentials):
177
"""Authenticate user with dynamic username field."""
178
User = get_user_model()
179
username_field = get_username_field()
180
181
# Build authentication kwargs dynamically
182
auth_kwargs = {
183
username_field: credentials.get(username_field),
184
'password': credentials.get('password'),
185
}
186
187
return authenticate(**auth_kwargs)
188
```
189
190
### Form Integration
191
192
```python
193
from django import forms
194
from rest_framework_jwt.compat import PasswordField
195
196
class JWTAuthForm(forms.Form):
197
"""Django form using JWT-compatible password field."""
198
199
username = forms.CharField(max_length=150)
200
password = forms.CharField(widget=forms.PasswordInput)
201
202
def __init__(self, *args, **kwargs):
203
super().__init__(*args, **kwargs)
204
205
# Dynamically set username field label
206
from rest_framework_jwt.compat import get_username_field
207
username_field = get_username_field()
208
209
if username_field == 'email':
210
self.fields['username'].label = 'Email Address'
211
self.fields['username'].widget.attrs['type'] = 'email'
212
elif username_field == 'phone':
213
self.fields['username'].label = 'Phone Number'
214
self.fields['username'].widget.attrs['type'] = 'tel'
215
```
216
217
## Django Version Compatibility
218
219
### User Model Evolution
220
221
```python
222
# Django 1.4 and earlier
223
class User(models.Model):
224
username = models.CharField(max_length=30, unique=True)
225
226
# get_username() method not available
227
# Must access username attribute directly
228
229
# Django 1.5+
230
class User(AbstractBaseUser):
231
USERNAME_FIELD = 'username' # Configurable username field
232
233
def get_username(self):
234
return getattr(self, self.USERNAME_FIELD)
235
236
# Custom implementations
237
class CustomUser(AbstractUser):
238
USERNAME_FIELD = 'email' # Use email instead of username
239
240
def get_username(self):
241
return self.email
242
```
243
244
### DRF Serializer Changes
245
246
```python
247
# DRF 2.x
248
class MySerializer(serializers.Serializer):
249
def restore_object(self, attrs, instance=None):
250
# Old validation method
251
pass
252
253
# Access via .object attribute
254
serializer.object
255
256
# DRF 3.0+
257
class MySerializer(serializers.Serializer):
258
def validate(self, attrs):
259
# New validation method
260
return attrs
261
262
# Access via .validated_data attribute
263
serializer.validated_data
264
265
# Compatibility layer bridges these differences
266
```
267
268
## Error Handling
269
270
### Graceful Fallbacks
271
272
```python
273
def get_username_field():
274
try:
275
# Try to get USERNAME_FIELD from User model
276
username_field = get_user_model().USERNAME_FIELD
277
except:
278
# Fallback to default if User model is not accessible
279
username_field = 'username'
280
281
return username_field
282
283
def get_username(user):
284
try:
285
# Try modern get_username() method
286
username = user.get_username()
287
except AttributeError:
288
# Fallback to direct attribute access
289
username = user.username
290
291
return username
292
```
293
294
### Common Compatibility Issues
295
296
```python
297
# Issue: Different User model configurations
298
# Solution: Use get_username_field() and get_username()
299
300
# Issue: DRF serializer API changes
301
# Solution: Use compat.Serializer with .object property
302
303
# Issue: Password field rendering inconsistencies
304
# Solution: Use compat.PasswordField with proper input type
305
306
# Issue: Import errors across Django versions
307
# Solution: Try/except blocks with fallback implementations
308
```
309
310
## Testing Compatibility
311
312
### Multi-Version Testing
313
314
```python
315
# Test with different User model configurations
316
@override_settings(AUTH_USER_MODEL='myapp.EmailUser')
317
def test_email_based_auth(self):
318
# Test JWT with email-based user model
319
pass
320
321
@override_settings(AUTH_USER_MODEL='myapp.PhoneUser')
322
def test_phone_based_auth(self):
323
# Test JWT with phone-based user model
324
pass
325
326
# Test serializer compatibility
327
def test_serializer_object_access(self):
328
serializer = CustomSerializer(data={'test': 'data'})
329
serializer.is_valid()
330
331
# Both should work
332
data1 = serializer.object
333
data2 = serializer.validated_data
334
assert data1 == data2
335
```
336
337
## Integration Patterns
338
339
### Flexible Authentication
340
341
```python
342
from rest_framework_jwt.compat import get_username_field
343
344
class FlexibleJWTSerializer(serializers.Serializer):
345
"""Serializer that adapts to any User model configuration."""
346
347
def __init__(self, *args, **kwargs):
348
super().__init__(*args, **kwargs)
349
350
# Add username field based on User model
351
username_field = get_username_field()
352
self.fields[username_field] = serializers.CharField()
353
self.fields['password'] = PasswordField(write_only=True)
354
355
def validate(self, attrs):
356
username_field = get_username_field()
357
username = attrs.get(username_field)
358
password = attrs.get('password')
359
360
if username and password:
361
user = authenticate(**{
362
username_field: username,
363
'password': password
364
})
365
366
if user and user.is_active:
367
return {'user': user}
368
369
raise serializers.ValidationError('Invalid credentials')
370
```
371
372
The compatibility utilities ensure that JWT authentication works reliably across different Django versions, user model configurations, and DRF versions while maintaining clean, maintainable code.