0
# REST API
1
2
Django REST Framework integration providing serializers, viewsets, and permissions for API-based preference management. This enables programmatic access to preferences through a comprehensive REST API.
3
4
## Capabilities
5
6
### Preference Serializers
7
8
Serializers for converting preference objects to/from JSON representations with proper validation and type handling.
9
10
```python { .api }
11
class PreferenceValueField(serializers.Field):
12
"""
13
Custom DRF field for handling preference value serialization/deserialization.
14
15
Automatically handles type conversion based on the preference type
16
and validates values according to preference validation rules.
17
"""
18
19
def to_representation(self, value):
20
"""Convert preference value to API representation."""
21
22
def to_internal_value(self, data):
23
"""Convert API data to preference value."""
24
25
class PreferenceSerializer(serializers.Serializer):
26
"""
27
Base serializer for preference objects.
28
29
Fields:
30
- section (CharField, read-only): Preference section
31
- name (CharField, read-only): Preference name
32
- identifier (SerializerMethodField): Section__name identifier
33
- default (SerializerMethodField): Default value
34
- value (PreferenceValueField): Current preference value
35
- verbose_name (SerializerMethodField): Display name
36
- help_text (SerializerMethodField): Help text
37
- additional_data (SerializerMethodField): Additional metadata
38
- field (SerializerMethodField): Form field configuration
39
"""
40
section = serializers.CharField(read_only=True)
41
name = serializers.CharField(read_only=True)
42
identifier = serializers.SerializerMethodField()
43
default = serializers.SerializerMethodField()
44
value = PreferenceValueField()
45
verbose_name = serializers.SerializerMethodField()
46
help_text = serializers.SerializerMethodField()
47
additional_data = serializers.SerializerMethodField()
48
field = serializers.SerializerMethodField()
49
50
def get_identifier(self, obj) -> str:
51
"""Return section__name identifier."""
52
53
def get_default(self, obj):
54
"""Return default value."""
55
56
def get_verbose_name(self, obj) -> str:
57
"""Return verbose name."""
58
59
def get_help_text(self, obj) -> str:
60
"""Return help text."""
61
62
def get_additional_data(self, obj) -> dict:
63
"""Return additional preference metadata."""
64
65
def get_field(self, obj) -> dict:
66
"""Return form field configuration."""
67
68
def validate_value(self, value):
69
"""
70
Validate preference value using form field validation.
71
72
Args:
73
- value: Value to validate
74
75
Returns:
76
Validated value
77
78
Raises:
79
- ValidationError: If value is invalid
80
"""
81
82
def update(self, instance, validated_data):
83
"""
84
Update preference value and send signal.
85
86
Args:
87
- instance: Preference instance
88
- validated_data: Validated data
89
90
Returns:
91
Updated preference instance
92
"""
93
94
class GlobalPreferenceSerializer(PreferenceSerializer):
95
"""
96
Serializer for global preferences.
97
98
Inherits all functionality from PreferenceSerializer
99
with global preference specific configuration.
100
"""
101
```
102
103
### Preference ViewSets
104
105
ViewSets providing API endpoints for preference CRUD operations with proper permissions and bulk operations.
106
107
```python { .api }
108
class PreferenceViewSet(
109
mixins.UpdateModelMixin,
110
mixins.ListModelMixin,
111
mixins.RetrieveModelMixin,
112
viewsets.GenericViewSet
113
):
114
"""
115
Base API viewset for preference CRUD operations.
116
117
Provides:
118
- GET /preferences/ - List all preferences
119
- GET /preferences/{identifier}/ - Retrieve specific preference
120
- PUT/PATCH /preferences/{identifier}/ - Update preference value
121
- POST /preferences/bulk/ - Bulk update multiple preferences
122
"""
123
serializer_class = PreferenceSerializer
124
lookup_field = 'identifier'
125
126
def get_queryset(self):
127
"""Initialize and filter preferences."""
128
129
def get_manager(self) -> PreferencesManager:
130
"""Return preference manager for this viewset."""
131
132
def init_preferences(self):
133
"""Ensure preferences are populated in database."""
134
135
def get_object(self):
136
"""
137
Get preference by identifier (section__name format).
138
139
Returns:
140
Preference instance
141
142
Raises:
143
- Http404: If preference not found
144
"""
145
146
def get_section_and_name(self, identifier: str) -> tuple:
147
"""
148
Parse preference identifier into section and name.
149
150
Args:
151
- identifier: Preference identifier (section__name)
152
153
Returns:
154
Tuple of (section, name)
155
"""
156
157
@action(detail=False, methods=['post'])
158
def bulk(self, request):
159
"""
160
Bulk update multiple preferences atomically.
161
162
Request Body:
163
{
164
"preferences": {
165
"section__name": value,
166
"section__name2": value2,
167
...
168
}
169
}
170
171
Returns:
172
Updated preferences data
173
"""
174
175
class GlobalPreferencesViewSet(PreferenceViewSet):
176
"""
177
API viewset for global preferences.
178
179
Endpoints:
180
- GET /global-preferences/ - List global preferences
181
- GET /global-preferences/{identifier}/ - Get specific preference
182
- PUT /global-preferences/{identifier}/ - Update preference
183
- POST /global-preferences/bulk/ - Bulk update
184
"""
185
queryset = GlobalPreferenceModel.objects.all()
186
serializer_class = GlobalPreferenceSerializer
187
permission_classes = [GlobalPreferencePermission]
188
189
class PerInstancePreferenceViewSet(PreferenceViewSet):
190
"""
191
Base viewset for per-instance preferences.
192
193
Subclasses must implement get_related_instance() method
194
to return the instance for preference filtering.
195
"""
196
197
def get_manager(self, **kwargs) -> PreferencesManager:
198
"""Return manager with instance context."""
199
200
def get_queryset(self):
201
"""Filter preferences by related instance."""
202
203
def get_related_instance(self):
204
"""
205
Abstract method to get the related instance.
206
207
Must be implemented by subclasses.
208
209
Returns:
210
Related model instance
211
"""
212
```
213
214
### API Permissions
215
216
Permission classes for controlling access to preference API endpoints.
217
218
```python { .api }
219
class GlobalPreferencePermission(DjangoModelPermissions):
220
"""
221
Permission class for global preferences API.
222
223
Controls access to global preference endpoints based on
224
Django model permissions for GlobalPreferenceModel.
225
"""
226
227
def has_permission(self, request, view):
228
"""Check if user has permission for the action."""
229
230
def has_object_permission(self, request, view, obj):
231
"""Check if user has permission for specific preference."""
232
```
233
234
## Usage Examples
235
236
### API Client Usage
237
238
```python
239
import requests
240
from django.conf import settings
241
242
# Base API URL
243
api_base = f"{settings.BASE_URL}/api/preferences/"
244
245
# List all global preferences
246
response = requests.get(f"{api_base}global-preferences/")
247
preferences = response.json()
248
249
# Get specific preference
250
response = requests.get(f"{api_base}global-preferences/general__title/")
251
preference = response.json()
252
print(preference['value']) # Current value
253
print(preference['default']) # Default value
254
255
# Update preference
256
response = requests.patch(
257
f"{api_base}global-preferences/general__title/",
258
json={'value': 'New Site Title'},
259
headers={'Content-Type': 'application/json'}
260
)
261
262
# Bulk update multiple preferences
263
response = requests.post(
264
f"{api_base}global-preferences/bulk/",
265
json={
266
'preferences': {
267
'general__title': 'New Title',
268
'general__maintenance_mode': True,
269
'ui__theme': 'dark'
270
}
271
}
272
)
273
```
274
275
### JavaScript/Frontend Usage
276
277
```javascript
278
// Fetch preferences
279
async function getPreferences() {
280
const response = await fetch('/api/preferences/global-preferences/');
281
const preferences = await response.json();
282
return preferences.results;
283
}
284
285
// Update single preference
286
async function updatePreference(identifier, value) {
287
const response = await fetch(`/api/preferences/global-preferences/${identifier}/`, {
288
method: 'PATCH',
289
headers: {
290
'Content-Type': 'application/json',
291
'X-CSRFToken': getCsrfToken()
292
},
293
body: JSON.stringify({ value })
294
});
295
return response.json();
296
}
297
298
// Bulk update
299
async function bulkUpdatePreferences(preferences) {
300
const response = await fetch('/api/preferences/global-preferences/bulk/', {
301
method: 'POST',
302
headers: {
303
'Content-Type': 'application/json',
304
'X-CSRFToken': getCsrfToken()
305
},
306
body: JSON.stringify({ preferences })
307
});
308
return response.json();
309
}
310
311
// Usage example
312
async function updateSiteSettings() {
313
await bulkUpdatePreferences({
314
'general__title': 'My New Site',
315
'general__maintenance_mode': false,
316
'ui__theme': 'light'
317
});
318
}
319
```
320
321
### Custom ViewSet for User Preferences
322
323
```python
324
from dynamic_preferences.users.models import UserPreferenceModel
325
from dynamic_preferences.users.serializers import UserPreferenceSerializer
326
from dynamic_preferences.api.viewsets import PerInstancePreferenceViewSet
327
from rest_framework.permissions import IsAuthenticated
328
329
class UserPreferencesViewSet(PerInstancePreferenceViewSet):
330
"""
331
API viewset for user-specific preferences.
332
333
Automatically filters preferences to current user.
334
"""
335
queryset = UserPreferenceModel.objects.all()
336
serializer_class = UserPreferenceSerializer
337
permission_classes = [IsAuthenticated]
338
339
def get_related_instance(self):
340
"""Return current user as the related instance."""
341
return self.request.user
342
343
def get_queryset(self):
344
"""Filter preferences to current user only."""
345
return super().get_queryset().filter(instance=self.request.user)
346
347
# URL configuration
348
from rest_framework.routers import DefaultRouter
349
350
router = DefaultRouter()
351
router.register('global-preferences', GlobalPreferencesViewSet, basename='global-preferences')
352
router.register('user-preferences', UserPreferencesViewSet, basename='user-preferences')
353
354
urlpatterns = [
355
path('api/preferences/', include(router.urls)),
356
]
357
```
358
359
### Custom Permission Classes
360
361
```python
362
from rest_framework.permissions import BasePermission
363
364
class PreferencePermission(BasePermission):
365
"""Custom permission for preferences based on section."""
366
367
def has_permission(self, request, view):
368
"""Check general permission."""
369
if request.method in ['GET', 'HEAD', 'OPTIONS']:
370
return request.user.is_authenticated
371
return request.user.is_staff
372
373
def has_object_permission(self, request, view, obj):
374
"""Check permission for specific preference."""
375
# Allow read access to authenticated users
376
if request.method in ['GET', 'HEAD', 'OPTIONS']:
377
return request.user.is_authenticated
378
379
# Check section-specific permissions
380
if obj.section == 'security':
381
return request.user.is_superuser
382
elif obj.section == 'ui':
383
return request.user.is_staff
384
385
return request.user.is_staff
386
387
class SectionBasedPreferenceViewSet(GlobalPreferencesViewSet):
388
permission_classes = [PreferencePermission]
389
```
390
391
### API Response Format
392
393
```json
394
{
395
"count": 5,
396
"next": null,
397
"previous": null,
398
"results": [
399
{
400
"section": "general",
401
"name": "title",
402
"identifier": "general__title",
403
"value": "My Site",
404
"default": "Default Title",
405
"verbose_name": "Site Title",
406
"help_text": "The title displayed in the site header",
407
"additional_data": {},
408
"field": {
409
"class": "CharField",
410
"kwargs": {
411
"max_length": 255,
412
"required": true
413
}
414
}
415
}
416
]
417
}
418
```