0
# Serialization
1
2
Value serialization system for database storage with support for complex Python objects and custom serialization logic. This system handles the conversion between Python objects and database-storable string representations.
3
4
## Capabilities
5
6
### Base Serializer Classes
7
8
Foundation classes for implementing serialization logic with support for both class-based and instance-based serializers.
9
10
```python { .api }
11
class BaseSerializer:
12
"""
13
Base serializer for converting Python variables to database strings.
14
15
Provides the foundation for all serialization operations with
16
class-based methods for stateless serialization.
17
"""
18
19
@classmethod
20
def serialize(cls, value, **kwargs) -> str:
21
"""
22
Convert Python value to database string representation.
23
24
Args:
25
- value: Python value to serialize
26
- **kwargs: Additional serialization options
27
28
Returns:
29
String representation suitable for database storage
30
31
Raises:
32
- SerializationError: If serialization fails
33
"""
34
35
@classmethod
36
def deserialize(cls, value, **kwargs):
37
"""
38
Convert database string back to Python value.
39
40
Args:
41
- value: String value from database
42
- **kwargs: Additional deserialization options
43
44
Returns:
45
Python value
46
47
Raises:
48
- SerializationError: If deserialization fails
49
"""
50
51
@classmethod
52
def to_python(cls, value, **kwargs):
53
"""
54
Abstract method for converting string to Python value.
55
56
Must be implemented by subclasses.
57
58
Args:
59
- value: String value to convert
60
- **kwargs: Additional conversion options
61
62
Returns:
63
Python value
64
"""
65
66
@classmethod
67
def to_db(cls, value, **kwargs) -> str:
68
"""
69
Convert Python value to database string.
70
71
Args:
72
- value: Python value to convert
73
- **kwargs: Additional conversion options
74
75
Returns:
76
Database-safe string representation
77
"""
78
79
@classmethod
80
def clean_to_db_value(cls, value):
81
"""
82
Clean and validate value before database storage.
83
84
Args:
85
- value: Value to clean
86
87
Returns:
88
Cleaned value ready for serialization
89
"""
90
91
class InstanciatedSerializer(BaseSerializer):
92
"""
93
Instance-based serializer for cases requiring additional context.
94
95
Used when serialization requires additional data or state
96
that cannot be handled by class methods alone.
97
"""
98
99
def __init__(self, **kwargs):
100
"""
101
Initialize serializer with context data.
102
103
Args:
104
- **kwargs: Context data for serialization
105
"""
106
```
107
108
### Basic Type Serializers
109
110
Serializers for fundamental Python data types with proper type conversion and validation.
111
112
```python { .api }
113
class BooleanSerializer(BaseSerializer):
114
"""
115
Serialize boolean values to/from database strings.
116
117
Handles: True/False -> "True"/"False"
118
"""
119
120
@classmethod
121
def to_python(cls, value) -> bool:
122
"""Convert string to boolean."""
123
124
@classmethod
125
def to_db(cls, value) -> str:
126
"""Convert boolean to string."""
127
128
class IntegerSerializer(BaseSerializer):
129
"""
130
Serialize integer values to/from database strings.
131
132
Handles: 42 -> "42"
133
Alias: IntSerializer
134
"""
135
136
@classmethod
137
def to_python(cls, value) -> int:
138
"""Convert string to integer."""
139
140
@classmethod
141
def to_db(cls, value) -> str:
142
"""Convert integer to string."""
143
144
# Alias for backward compatibility
145
IntSerializer = IntegerSerializer
146
147
class DecimalSerializer(BaseSerializer):
148
"""
149
Serialize Decimal values to/from database strings.
150
151
Handles: Decimal('123.45') -> "123.45"
152
Preserves precision for financial/scientific calculations.
153
"""
154
155
@classmethod
156
def to_python(cls, value):
157
"""Convert string to Decimal."""
158
159
@classmethod
160
def to_db(cls, value) -> str:
161
"""Convert Decimal to string."""
162
163
class FloatSerializer(BaseSerializer):
164
"""
165
Serialize float values to/from database strings.
166
167
Handles: 3.14159 -> "3.14159"
168
"""
169
170
@classmethod
171
def to_python(cls, value) -> float:
172
"""Convert string to float."""
173
174
@classmethod
175
def to_db(cls, value) -> str:
176
"""Convert float to string."""
177
178
class StringSerializer(BaseSerializer):
179
"""
180
Serialize string values with optional HTML escaping.
181
182
Handles: "Hello World" -> "Hello World"
183
Supports HTML escaping for security.
184
"""
185
186
@classmethod
187
def to_python(cls, value) -> str:
188
"""Convert database string to Python string."""
189
190
@classmethod
191
def to_db(cls, value) -> str:
192
"""Convert Python string to database string."""
193
```
194
195
### Collection Serializers
196
197
Serializers for handling multiple values and complex data structures.
198
199
```python { .api }
200
class MultipleSerializer(BaseSerializer):
201
"""
202
Serialize multiple choice values as JSON arrays.
203
204
Handles: ['choice1', 'choice2'] -> '["choice1", "choice2"]'
205
"""
206
207
@classmethod
208
def to_python(cls, value) -> list:
209
"""Convert JSON string to Python list."""
210
211
@classmethod
212
def to_db(cls, value) -> str:
213
"""Convert Python list to JSON string."""
214
```
215
216
### Model-Related Serializers
217
218
Serializers for handling Django model instances and relationships.
219
220
```python { .api }
221
class ModelSerializer(InstanciatedSerializer):
222
"""
223
Serialize Django model instances by primary key.
224
225
Handles model instance -> PK string -> model instance
226
Requires model class context for deserialization.
227
"""
228
229
def __init__(self, model_class=None, **kwargs):
230
"""
231
Initialize with model class for lookups.
232
233
Args:
234
- model_class: Django model class
235
- **kwargs: Additional context
236
"""
237
self.model_class = model_class
238
super().__init__(**kwargs)
239
240
def to_python(self, value):
241
"""
242
Convert primary key to model instance.
243
244
Args:
245
- value: Primary key value
246
247
Returns:
248
Model instance or None
249
"""
250
251
def to_db(self, value) -> str:
252
"""
253
Convert model instance to primary key string.
254
255
Args:
256
- value: Model instance
257
258
Returns:
259
Primary key as string
260
"""
261
262
class ModelMultipleSerializer(ModelSerializer):
263
"""
264
Serialize multiple Django model instances.
265
266
Handles: [model1, model2] -> "[pk1, pk2]" -> [model1, model2]
267
"""
268
269
def to_python(self, value) -> list:
270
"""Convert PK list to model instance list."""
271
272
def to_db(self, value) -> str:
273
"""Convert model instance list to PK JSON string."""
274
```
275
276
### File Serializers
277
278
Serializers for handling file uploads and file references.
279
280
```python { .api }
281
class FileSerializer(InstanciatedSerializer):
282
"""
283
Serialize file uploads with proper file handling.
284
285
Handles file storage, path management, and cleanup.
286
"""
287
288
def __init__(self, upload_path=None, storage=None, **kwargs):
289
"""
290
Initialize with file handling configuration.
291
292
Args:
293
- upload_path: Path for file uploads
294
- storage: Django storage backend
295
- **kwargs: Additional context
296
"""
297
self.upload_path = upload_path
298
self.storage = storage
299
super().__init__(**kwargs)
300
301
def to_python(self, value):
302
"""
303
Convert file path to file object.
304
305
Args:
306
- value: File path string
307
308
Returns:
309
File object or None
310
"""
311
312
def to_db(self, value) -> str:
313
"""
314
Store file and return path string.
315
316
Args:
317
- value: File object
318
319
Returns:
320
File path as string
321
"""
322
323
class PreferenceFieldFile:
324
"""
325
FieldFile subclass for preference files.
326
327
Provides file-like interface for preference file handling
328
with proper integration with Django's file storage system.
329
"""
330
331
def __init__(self, instance, field, name):
332
"""
333
Initialize preference field file.
334
335
Args:
336
- instance: Preference model instance
337
- field: File field
338
- name: File name/path
339
"""
340
341
@property
342
def url(self) -> str:
343
"""Return URL for accessing the file."""
344
345
@property
346
def size(self) -> int:
347
"""Return file size in bytes."""
348
349
def delete(self, save=True):
350
"""Delete the file and optionally save the model."""
351
```
352
353
### Date/Time Serializers
354
355
Serializers for temporal data with timezone support and proper formatting.
356
357
```python { .api }
358
class DurationSerializer(BaseSerializer):
359
"""
360
Serialize timedelta objects to/from database strings.
361
362
Handles: timedelta(days=1, hours=2) -> "1 day, 2:00:00"
363
"""
364
365
@classmethod
366
def to_python(cls, value):
367
"""Convert string to timedelta."""
368
369
@classmethod
370
def to_db(cls, value) -> str:
371
"""Convert timedelta to string."""
372
373
class DateSerializer(BaseSerializer):
374
"""
375
Serialize date objects to/from ISO format strings.
376
377
Handles: date(2023, 12, 25) -> "2023-12-25"
378
"""
379
380
@classmethod
381
def to_python(cls, value):
382
"""Convert ISO string to date."""
383
384
@classmethod
385
def to_db(cls, value) -> str:
386
"""Convert date to ISO string."""
387
388
class DateTimeSerializer(BaseSerializer):
389
"""
390
Serialize datetime objects with timezone handling.
391
392
Handles: datetime(2023, 12, 25, 10, 30) -> "2023-12-25T10:30:00Z"
393
Supports timezone conversion and UTC normalization.
394
"""
395
396
@classmethod
397
def to_python(cls, value):
398
"""Convert ISO string to datetime with timezone."""
399
400
@classmethod
401
def to_db(cls, value) -> str:
402
"""Convert datetime to ISO string with timezone."""
403
404
class TimeSerializer(BaseSerializer):
405
"""
406
Serialize time objects to/from ISO format strings.
407
408
Handles: time(14, 30, 45) -> "14:30:45"
409
"""
410
411
@classmethod
412
def to_python(cls, value):
413
"""Convert ISO string to time."""
414
415
@classmethod
416
def to_db(cls, value) -> str:
417
"""Convert time to ISO string."""
418
```
419
420
### Special Values and Exceptions
421
422
Special markers and exception classes for serialization error handling.
423
424
```python { .api }
425
class UnsetValue:
426
"""
427
Marker class for unset preference values.
428
429
Used to distinguish between None values and truly unset preferences.
430
"""
431
432
def __bool__(self) -> bool:
433
"""Always evaluates to False."""
434
return False
435
436
# Global instance for unset values
437
UNSET: UnsetValue
438
439
class SerializationError(Exception):
440
"""
441
Exception raised when serialization/deserialization fails.
442
443
Provides context about serialization failures with
444
details about the value and operation that failed.
445
"""
446
447
def __init__(self, message, value=None, operation=None):
448
"""
449
Initialize serialization error.
450
451
Args:
452
- message: Error description
453
- value: Value that caused the error
454
- operation: Serialization operation (serialize/deserialize)
455
"""
456
self.value = value
457
self.operation = operation
458
super().__init__(message)
459
```
460
461
## Usage Examples
462
463
### Basic Serialization
464
465
```python
466
from dynamic_preferences.serializers import StringSerializer, BooleanSerializer
467
468
# String serialization
469
value = "Hello, World!"
470
serialized = StringSerializer.serialize(value)
471
print(serialized) # "Hello, World!"
472
deserialized = StringSerializer.deserialize(serialized)
473
print(deserialized) # "Hello, World!"
474
475
# Boolean serialization
476
bool_value = True
477
serialized = BooleanSerializer.serialize(bool_value)
478
print(serialized) # "True"
479
deserialized = BooleanSerializer.deserialize(serialized)
480
print(deserialized) # True
481
```
482
483
### Model Serialization
484
485
```python
486
from django.contrib.auth.models import User
487
from dynamic_preferences.serializers import ModelSerializer
488
489
# Create model serializer
490
user_serializer = ModelSerializer(model_class=User)
491
492
# Serialize user instance
493
user = User.objects.get(pk=1)
494
serialized = user_serializer.to_db(user)
495
print(serialized) # "1"
496
497
# Deserialize back to user instance
498
deserialized = user_serializer.to_python(serialized)
499
print(deserialized) # <User: username>
500
501
# Multiple users
502
from dynamic_preferences.serializers import ModelMultipleSerializer
503
504
multi_serializer = ModelMultipleSerializer(model_class=User)
505
users = User.objects.filter(is_active=True)[:3]
506
serialized = multi_serializer.to_db(list(users))
507
print(serialized) # "[1, 2, 3]"
508
deserialized = multi_serializer.to_python(serialized)
509
print(deserialized) # [<User: user1>, <User: user2>, <User: user3>]
510
```
511
512
### Date/Time Serialization
513
514
```python
515
from datetime import date, datetime, time, timedelta
516
from dynamic_preferences.serializers import (
517
DateSerializer, DateTimeSerializer, TimeSerializer, DurationSerializer
518
)
519
520
# Date serialization
521
today = date.today()
522
serialized = DateSerializer.serialize(today)
523
print(serialized) # "2023-12-25"
524
deserialized = DateSerializer.deserialize(serialized)
525
print(deserialized) # datetime.date(2023, 12, 25)
526
527
# DateTime with timezone
528
now = datetime.now()
529
serialized = DateTimeSerializer.serialize(now)
530
print(serialized) # "2023-12-25T10:30:00+00:00"
531
532
# Duration serialization
533
duration = timedelta(days=1, hours=2, minutes=30)
534
serialized = DurationSerializer.serialize(duration)
535
print(serialized) # "1 day, 2:30:00"
536
```
537
538
### File Serialization
539
540
```python
541
from dynamic_preferences.serializers import FileSerializer
542
from django.core.files.uploadedfile import SimpleUploadedFile
543
544
# Create file serializer with upload configuration
545
file_serializer = FileSerializer(
546
upload_path='preferences/files/',
547
storage=default_storage
548
)
549
550
# Handle file upload
551
uploaded_file = SimpleUploadedFile(
552
"test.txt",
553
b"file content",
554
content_type="text/plain"
555
)
556
557
# Serialize file (saves to storage)
558
file_path = file_serializer.to_db(uploaded_file)
559
print(file_path) # "preferences/files/test.txt"
560
561
# Deserialize back to file object
562
file_obj = file_serializer.to_python(file_path)
563
print(file_obj.url) # "/media/preferences/files/test.txt"
564
print(file_obj.size) # 12
565
```
566
567
### Custom Serializer
568
569
```python
570
import json
571
from dynamic_preferences.serializers import BaseSerializer, SerializationError
572
573
class JSONSerializer(BaseSerializer):
574
"""Custom serializer for JSON data."""
575
576
@classmethod
577
def to_python(cls, value):
578
try:
579
if value == '':
580
return {}
581
return json.loads(value)
582
except (json.JSONDecodeError, TypeError) as e:
583
raise SerializationError(f"Invalid JSON: {e}", value, 'deserialize')
584
585
@classmethod
586
def to_db(cls, value):
587
try:
588
return json.dumps(value, ensure_ascii=False)
589
except (TypeError, ValueError) as e:
590
raise SerializationError(f"Cannot serialize to JSON: {e}", value, 'serialize')
591
592
# Usage with custom preference type
593
from dynamic_preferences.types import BasePreferenceType
594
595
class JSONPreference(BasePreferenceType):
596
serializer = JSONSerializer()
597
field_class = forms.CharField
598
599
def setup_field(self, **kwargs):
600
field = super().setup_field(**kwargs)
601
field.widget = forms.Textarea(attrs={'rows': 4})
602
return field
603
604
def validate(self, value):
605
# Ensure it's valid JSON-serializable data
606
try:
607
json.dumps(value)
608
except (TypeError, ValueError):
609
raise ValidationError("Value must be JSON serializable")
610
611
# Register and use
612
@global_preferences_registry.register
613
class APIConfiguration(JSONPreference):
614
section = Section('api')
615
name = 'config'
616
default = {'timeout': 30, 'retries': 3}
617
verbose_name = 'API Configuration'
618
619
# Usage
620
global_preferences = global_preferences_registry.manager()
621
config = global_preferences['api__config']
622
print(config) # {'timeout': 30, 'retries': 3}
623
624
# Update configuration
625
global_preferences['api__config'] = {
626
'timeout': 60,
627
'retries': 5,
628
'base_url': 'https://api.example.com'
629
}
630
```
631
632
### Error Handling
633
634
```python
635
from dynamic_preferences.serializers import SerializationError
636
637
try:
638
# This will fail for invalid data
639
result = IntegerSerializer.deserialize("not_a_number")
640
except SerializationError as e:
641
print(f"Serialization failed: {e}")
642
print(f"Value: {e.value}")
643
print(f"Operation: {e.operation}")
644
645
# Custom error handling in serializers
646
class SafeIntegerSerializer(IntegerSerializer):
647
@classmethod
648
def to_python(cls, value, default=0):
649
try:
650
return super().to_python(value)
651
except SerializationError:
652
return default
653
654
# Usage with fallback
655
result = SafeIntegerSerializer.to_python("invalid", default=42)
656
print(result) # 42
657
```