0
# Fields & Relations
1
2
Django REST Framework provides a comprehensive field system for data validation, serialization, and deserialization. The type stubs enable full type safety for field definitions, relational fields, and custom field implementations with precise generic type support.
3
4
## Base Field Classes
5
6
### Field
7
8
```python { .api }
9
class Field(Generic[_VT, _DT, _RP, _IN]):
10
"""
11
Base class for all serializer fields with generic type support.
12
13
Type Parameters:
14
_VT: Value Type (internal Python type)
15
_DT: Data Type (input data type)
16
_RP: Representation Type (output serialization type)
17
_IN: Instance Type (model instance type)
18
"""
19
20
# Core configuration
21
read_only: bool
22
write_only: bool
23
required: bool
24
default: Any
25
initial: Any
26
allow_null: bool
27
28
# Validation configuration
29
validators: list[Callable[[Any], Any]]
30
error_messages: dict[str, str]
31
32
# Display configuration
33
label: str | None
34
help_text: str | None
35
style: dict[str, Any]
36
37
def __init__(
38
self,
39
read_only: bool = False,
40
write_only: bool = False,
41
required: bool | None = None,
42
default: Any = empty,
43
initial: Any = empty,
44
source: str | None = None,
45
label: str | None = None,
46
help_text: str | None = None,
47
style: dict[str, Any] | None = None,
48
error_messages: dict[str, str] | None = None,
49
validators: list[Callable[[Any], Any]] | None = None,
50
allow_null: bool = False
51
) -> None: ...
52
53
def bind(self, field_name: str, parent: Any) -> None:
54
"""Bind field to parent serializer."""
55
...
56
57
def get_value(self, dictionary: dict[str, Any]) -> Any:
58
"""Extract value from input data."""
59
...
60
61
def get_attribute(self, instance: _IN) -> Any:
62
"""Get attribute value from object instance."""
63
...
64
65
def get_default(self) -> Any:
66
"""Get default value for field."""
67
...
68
69
def validate_empty_values(self, data: _DT) -> tuple[bool, Any]:
70
"""Validate empty/null values."""
71
...
72
73
def run_validation(self, data: _DT) -> _VT:
74
"""Run full validation pipeline."""
75
...
76
77
def run_validators(self, value: _VT) -> None:
78
"""Run field validators."""
79
...
80
81
def to_internal_value(self, data: _DT) -> _VT:
82
"""Convert input data to internal value."""
83
...
84
85
def to_representation(self, value: _VT) -> _RP:
86
"""Convert internal value to serialized representation."""
87
...
88
89
def fail(self, key: str, **kwargs: Any) -> NoReturn:
90
"""Raise validation error with message key."""
91
...
92
93
@property
94
def validators(self) -> list[Validator[_VT]]: ...
95
@validators.setter
96
def validators(self, validators: list[Validator[_VT]]) -> None: ...
97
98
def get_validators(self) -> list[Validator[_VT]]: ...
99
def get_initial(self) -> _VT | None: ...
100
101
@property
102
def root(self) -> BaseSerializer: ...
103
@property
104
def context(self) -> dict[str, Any]: ...
105
```
106
107
**Parameters:**
108
- `read_only: bool` - Field is excluded from input validation
109
- `write_only: bool` - Field is excluded from serialized output
110
- `required: bool | None` - Field is required in input (default: True unless read_only or has default)
111
- `default: Any` - Default value when not provided
112
- `source: str | None` - Source attribute path (defaults to field name)
113
- `allow_null: bool` - Allow None values
114
115
## Primitive Field Types
116
117
### Boolean Fields
118
119
```python { .api }
120
class BooleanField(Field[bool, str | bool | int, bool, Any]):
121
"""Boolean field that accepts various truthy/falsy inputs."""
122
123
TRUE_VALUES: set[str | int | bool]
124
FALSE_VALUES: set[str | int | bool]
125
126
def __init__(self, **kwargs: Any) -> None: ...
127
128
class NullBooleanField(Field[bool | None, str | bool | int | None, bool, Any]):
129
"""Boolean field that also accepts None."""
130
pass
131
```
132
133
### String Fields
134
135
```python { .api }
136
class CharField(Field[str, str, str, Any]):
137
"""Character field with length and whitespace validation."""
138
139
def __init__(
140
self,
141
*,
142
max_length: int | None = None,
143
min_length: int | None = None,
144
allow_blank: bool = False,
145
trim_whitespace: bool = True,
146
**kwargs: Any
147
) -> None: ...
148
149
class EmailField(CharField):
150
"""Email field with email validation."""
151
pass
152
153
class RegexField(CharField):
154
"""Field that validates against a regular expression."""
155
156
def __init__(
157
self,
158
regex: str | Pattern[str],
159
max_length: int | None = None,
160
min_length: int | None = None,
161
**kwargs: Any
162
) -> None: ...
163
164
class SlugField(CharField):
165
"""Field for URL slugs (alphanumeric + hyphens/underscores)."""
166
pass
167
168
class URLField(CharField):
169
"""URL field with URL validation."""
170
pass
171
172
class UUIDField(Field[uuid.UUID, uuid.UUID | str | int, str, Any]):
173
"""UUID field that accepts UUID objects or strings."""
174
175
def __init__(self, *, format: str = 'hex_verbose', **kwargs: Any) -> None: ...
176
177
class IPAddressField(CharField):
178
"""IP address field supporting IPv4 and IPv6."""
179
180
def __init__(self, *, protocol: str = 'both', **kwargs: Any) -> None: ...
181
```
182
183
**String Field Parameters:**
184
- `max_length: int | None` - Maximum string length
185
- `min_length: int | None` - Minimum string length
186
- `allow_blank: bool` - Allow empty strings (default: False)
187
- `trim_whitespace: bool` - Trim leading/trailing whitespace (default: True)
188
189
### Numeric Fields
190
191
```python { .api }
192
class IntegerField(Field[int, float | int | str, int, Any]):
193
"""Integer field with range validation."""
194
195
def __init__(
196
self,
197
*,
198
max_value: int | None = None,
199
min_value: int | None = None,
200
**kwargs: Any
201
) -> None: ...
202
203
class FloatField(Field[float, float | int | str, str, Any]):
204
"""Float field with range validation."""
205
206
def __init__(
207
self,
208
*,
209
max_value: float | None = None,
210
min_value: float | None = None,
211
**kwargs: Any
212
) -> None: ...
213
214
class DecimalField(Field[Decimal, int | float | str | Decimal, str, Any]):
215
"""Decimal field with precision control."""
216
217
def __init__(
218
self,
219
max_digits: int | None = None,
220
decimal_places: int | None = None,
221
coerce_to_string: bool | None = None,
222
max_value: int | float | Decimal | None = None,
223
min_value: int | float | Decimal | None = None,
224
localize: bool = False,
225
rounding: str | None = None,
226
**kwargs: Any
227
) -> None: ...
228
```
229
230
**Numeric Field Parameters:**
231
- `max_value: int | float | None` - Maximum allowed value
232
- `min_value: int | float | None` - Minimum allowed value
233
- `max_digits: int | None` - Maximum total digits (DecimalField)
234
- `decimal_places: int | None` - Maximum decimal places (DecimalField)
235
236
### Date/Time Fields
237
238
```python { .api }
239
class DateTimeField(Field[datetime.datetime, datetime.datetime | str, str, Any]):
240
"""DateTime field with format support."""
241
242
def __init__(
243
self,
244
*,
245
format: str | None = None,
246
input_formats: list[str] | None = None,
247
default_timezone: timezone | None = None,
248
**kwargs: Any
249
) -> None: ...
250
251
class DateField(Field[datetime.date, datetime.date | str, str, Any]):
252
"""Date field with format support."""
253
254
def __init__(
255
self,
256
*,
257
format: str | None = None,
258
input_formats: list[str] | None = None,
259
**kwargs: Any
260
) -> None: ...
261
262
class TimeField(Field[datetime.time, datetime.time | str, str, Any]):
263
"""Time field with format support."""
264
265
def __init__(
266
self,
267
*,
268
format: str | None = None,
269
input_formats: list[str] | None = None,
270
**kwargs: Any
271
) -> None: ...
272
273
class DurationField(Field[datetime.timedelta, datetime.timedelta | str, str, Any]):
274
"""Duration field for time intervals."""
275
pass
276
```
277
278
**Date/Time Field Parameters:**
279
- `format: str | None` - Output format string (default uses settings)
280
- `input_formats: list[str] | None` - Accepted input formats
281
- `default_timezone: timezone | None` - Default timezone for naive datetimes
282
283
## Choice and Selection Fields
284
285
### ChoiceField
286
287
```python { .api }
288
class ChoiceField(Field[str, str | int | tuple, str, Any]):
289
"""Field that validates against a set of choices."""
290
291
def __init__(
292
self,
293
choices: Iterable[Any],
294
*,
295
allow_blank: bool = False,
296
html_cutoff: int | None = None,
297
html_cutoff_text: str | None = None,
298
**kwargs: Any
299
) -> None: ...
300
301
@property
302
def choices(self) -> dict[Any, str]: ...
303
304
@choices.setter
305
def choices(self, value: Iterable[Any]) -> None: ...
306
307
class MultipleChoiceField(ChoiceField, Field[list[Any], list[Any], list[Any], Any]):
308
"""Field that accepts multiple choices from a set."""
309
310
def __init__(
311
self,
312
*,
313
allow_empty: bool = True,
314
**kwargs: Any
315
) -> None: ...
316
317
class FilePathField(ChoiceField):
318
"""Field that provides choices from filesystem paths."""
319
320
def __init__(
321
self,
322
path: str,
323
*,
324
match: str | None = None,
325
recursive: bool = False,
326
allow_files: bool = True,
327
allow_folders: bool = False,
328
**kwargs: Any
329
) -> None: ...
330
```
331
332
**Choice Field Parameters:**
333
- `choices: Iterable[Any]` - Available choices (list, tuple, or dict)
334
- `allow_blank: bool` - Allow empty string selection
335
- `allow_empty: bool` - Allow empty list (MultipleChoiceField)
336
337
## File Fields
338
339
### FileField
340
341
```python { .api }
342
class FileField(Field[File, File, str | None, Any]):
343
"""Field for file uploads."""
344
345
def __init__(
346
self,
347
*,
348
max_length: int | None = None,
349
allow_empty_file: bool = False,
350
use_url: bool = True,
351
**kwargs: Any
352
) -> None: ...
353
354
class ImageField(FileField):
355
"""Field for image uploads with validation."""
356
357
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
358
```
359
360
**File Field Parameters:**
361
- `max_length: int | None` - Maximum filename length
362
- `allow_empty_file: bool` - Allow zero-byte files
363
- `use_url: bool` - Return file URL in representation
364
365
## Container Fields
366
367
### ListField
368
369
```python { .api }
370
class ListField(Field[list[Any], list[Any], list[Any], Any]):
371
"""Field for lists of items with child field validation."""
372
373
child: Field | None
374
allow_empty: bool
375
max_length: int | None
376
min_length: int | None
377
378
def __init__(
379
self,
380
*,
381
child: Field | None = None,
382
allow_empty: bool = True,
383
max_length: int | None = None,
384
min_length: int | None = None,
385
**kwargs: Any
386
) -> None: ...
387
388
class DictField(Field[dict[Any, Any], dict[Any, Any], dict[Any, Any], Any]):
389
"""Field for dictionaries with child field validation."""
390
391
child: Field | None
392
allow_empty: bool
393
394
def __init__(
395
self,
396
*,
397
child: Field | None = None,
398
allow_empty: bool = True,
399
**kwargs: Any
400
) -> None: ...
401
402
class HStoreField(DictField):
403
"""Field for PostgreSQL HStore data."""
404
pass
405
406
class JSONField(Field[dict[str, Any] | list[dict[str, Any]], Any, str, Any]):
407
"""Field for JSON data with optional binary encoding."""
408
409
def __init__(
410
self,
411
*,
412
binary: bool = False,
413
encoder: type[json.JSONEncoder] | None = None,
414
**kwargs: Any
415
) -> None: ...
416
```
417
418
**Container Field Parameters:**
419
- `child: Field | None` - Field type for list/dict values
420
- `allow_empty: bool` - Allow empty containers
421
- `max_length/min_length: int | None` - Size constraints
422
423
## Special Fields
424
425
### ReadOnlyField
426
427
```python { .api }
428
class ReadOnlyField(Field[Any, None, Any, Any]):
429
"""Field that returns attribute value without validation."""
430
431
def __init__(self, **kwargs: Any) -> None: ...
432
```
433
434
### HiddenField
435
436
```python { .api }
437
class HiddenField(Field[Any, None, None, Any]):
438
"""Field with a value that's not part of user input."""
439
440
def __init__(self, *, default: Any = empty, **kwargs: Any) -> None: ...
441
```
442
443
### SerializerMethodField
444
445
```python { .api }
446
class SerializerMethodField(Field[Any, None, Any, Any]):
447
"""Field that gets its value by calling a method on the serializer."""
448
449
def __init__(
450
self,
451
method_name: str | None = None,
452
**kwargs: Any
453
) -> None: ...
454
455
def bind(self, field_name: str, parent: Any) -> None: ...
456
def to_representation(self, value: Any) -> Any: ...
457
```
458
459
### ModelField
460
461
```python { .api }
462
class ModelField(Field[Any, Any, Any, Any]):
463
"""Field that wraps a Django model field."""
464
465
def __init__(self, model_field: models.Field, **kwargs: Any) -> None: ...
466
```
467
468
## Relational Fields
469
470
### RelatedField Base
471
472
```python { .api }
473
class RelatedField(Field[_MT, _DT, _PT, Any]):
474
"""Base class for fields that represent model relationships."""
475
476
queryset: QuerySet[_MT] | Manager[_MT] | None
477
html_cutoff: int | None
478
html_cutoff_text: str | None
479
480
def __init__(
481
self,
482
*,
483
queryset: QuerySet[_MT] | Manager[_MT] | None = None,
484
many: bool = False,
485
allow_empty: bool = True,
486
**kwargs: Any
487
) -> None: ...
488
489
def get_queryset(self) -> QuerySet[_MT]: ...
490
def get_choices(self, cutoff: int | None = None) -> dict[Any, str]: ...
491
def display_value(self, instance: _MT) -> str: ...
492
```
493
494
### String Related Field
495
496
```python { .api }
497
class StringRelatedField(RelatedField[_MT, _MT, str]):
498
"""Field that represents relationships using string representation."""
499
500
def __init__(self, **kwargs: Any) -> None: ...
501
def to_representation(self, value: _MT) -> str: ...
502
```
503
504
### Primary Key Related Field
505
506
```python { .api }
507
class PrimaryKeyRelatedField(RelatedField[_MT, _MT, Any]):
508
"""Field that represents relationships using primary key."""
509
510
pk_field: str | None
511
512
def __init__(
513
self,
514
*,
515
pk_field: str | None = None,
516
**kwargs: Any
517
) -> None: ...
518
519
def to_internal_value(self, data: Any) -> _MT: ...
520
def to_representation(self, value: _MT) -> Any: ...
521
```
522
523
### Hyperlinked Fields
524
525
```python { .api }
526
class Hyperlink(str):
527
"""String subclass for hyperlinked representations."""
528
529
@property
530
def name(self) -> str: ...
531
532
obj: Any
533
is_hyperlink: bool
534
535
class HyperlinkedRelatedField(RelatedField[_MT, str, Hyperlink]):
536
"""Field that represents relationships as hyperlinks."""
537
538
view_name: str | None
539
lookup_field: str
540
lookup_url_kwarg: str
541
format: str | None
542
543
def __init__(
544
self,
545
*,
546
view_name: str | None = None,
547
lookup_field: str = 'pk',
548
lookup_url_kwarg: str | None = None,
549
format: str | None = None,
550
**kwargs: Any
551
) -> None: ...
552
553
def get_object(
554
self,
555
view_name: str,
556
view_args: list[Any],
557
view_kwargs: dict[str, Any]
558
) -> _MT: ...
559
560
def get_url(
561
self,
562
obj: Model,
563
view_name: str,
564
request: Request,
565
format: str | None
566
) -> str | None: ...
567
568
class HyperlinkedIdentityField(HyperlinkedRelatedField[_MT, str, Hyperlink]):
569
"""Hyperlinked field that represents the identity URL of an object."""
570
571
def __init__(
572
self,
573
*,
574
view_name: str | None = None,
575
**kwargs: Any
576
) -> None: ...
577
```
578
579
### Slug Related Field
580
581
```python { .api }
582
class SlugRelatedField(RelatedField[_MT, str, str]):
583
"""Field that represents relationships using a slug attribute."""
584
585
slug_field: str | None
586
587
def __init__(
588
self,
589
*,
590
slug_field: str | None = None,
591
**kwargs: Any
592
) -> None: ...
593
594
def to_internal_value(self, data: str) -> _MT: ...
595
def to_representation(self, obj: _MT) -> str: ...
596
```
597
598
### Many Related Field
599
600
```python { .api }
601
class ManyRelatedField(Field[Sequence[Any], Sequence[Any], list[Any], Any]):
602
"""Field for many-to-many relationships."""
603
604
child_relation: RelatedField
605
allow_empty: bool
606
607
def __init__(
608
self,
609
child_relation: RelatedField | None = None,
610
*,
611
allow_empty: bool = True,
612
**kwargs: Any
613
) -> None: ...
614
```
615
616
## Field Usage Examples
617
618
### Basic Field Configuration
619
620
```python { .api }
621
from rest_framework import serializers
622
623
class BookSerializer(serializers.Serializer):
624
"""Demonstrate various field types and configurations."""
625
626
# String fields
627
title = serializers.CharField(max_length=200, min_length=1)
628
isbn = serializers.RegexField(
629
regex=r'^(?:\d{10}|\d{13})$',
630
help_text='10 or 13 digit ISBN'
631
)
632
slug = serializers.SlugField(allow_blank=True)
633
634
# Numeric fields
635
pages = serializers.IntegerField(min_value=1, max_value=10000)
636
price = serializers.DecimalField(
637
max_digits=8,
638
decimal_places=2,
639
min_value=0.01
640
)
641
rating = serializers.FloatField(min_value=0.0, max_value=5.0)
642
643
# Date fields
644
published_date = serializers.DateField()
645
created_at = serializers.DateTimeField(read_only=True)
646
647
# Boolean and choice fields
648
is_available = serializers.BooleanField(default=True)
649
genre = serializers.ChoiceField(choices=[
650
('fiction', 'Fiction'),
651
('non-fiction', 'Non-Fiction'),
652
('mystery', 'Mystery'),
653
('sci-fi', 'Science Fiction'),
654
])
655
656
# Container fields
657
tags = serializers.ListField(
658
child=serializers.CharField(max_length=50),
659
allow_empty=True,
660
max_length=10
661
)
662
metadata = serializers.DictField(
663
child=serializers.CharField(),
664
allow_empty=True
665
)
666
```
667
668
### Validation and Custom Fields
669
670
```python { .api }
671
class CustomValidationSerializer(serializers.Serializer):
672
"""Demonstrate field validation and custom fields."""
673
674
# Field with custom validators
675
username = serializers.CharField(
676
max_length=30,
677
validators=[
678
validators.RegexValidator(
679
regex=r'^[a-zA-Z0-9_]+$',
680
message='Username can only contain letters, numbers and underscores'
681
)
682
]
683
)
684
685
# Email field with custom validation
686
email = serializers.EmailField()
687
688
# Password field (write-only)
689
password = serializers.CharField(
690
write_only=True,
691
min_length=8,
692
style={'input_type': 'password'}
693
)
694
695
# Confirm password (write-only, not stored)
696
confirm_password = serializers.CharField(
697
write_only=True,
698
style={'input_type': 'password'}
699
)
700
701
# Read-only calculated field
702
full_name = serializers.SerializerMethodField()
703
704
# File upload field
705
avatar = serializers.ImageField(
706
allow_empty_file=False,
707
use_url=True
708
)
709
710
def get_full_name(self, obj: User) -> str:
711
"""Calculate full name from first and last name."""
712
return f"{obj.first_name} {obj.last_name}".strip()
713
714
def validate_email(self, value: str) -> str:
715
"""Custom email validation."""
716
if User.objects.filter(email__iexact=value).exists():
717
raise serializers.ValidationError('Email already registered')
718
return value.lower()
719
720
def validate(self, data: dict[str, Any]) -> dict[str, Any]:
721
"""Cross-field validation."""
722
if data.get('password') != data.get('confirm_password'):
723
raise serializers.ValidationError('Passwords do not match')
724
725
# Remove confirm_password from validated data
726
data.pop('confirm_password', None)
727
return data
728
```
729
730
### Relational Field Examples
731
732
```python { .api }
733
class AuthorSerializer(serializers.ModelSerializer):
734
"""Author serializer with relational fields."""
735
736
class Meta:
737
model = Author
738
fields = ['id', 'name', 'bio', 'country']
739
740
class BookRelationalSerializer(serializers.ModelSerializer):
741
"""Book serializer demonstrating various relational field types."""
742
743
# Foreign key as primary key
744
author_id = serializers.PrimaryKeyRelatedField(
745
queryset=Author.objects.all(),
746
source='author'
747
)
748
749
# Foreign key as string representation
750
author_name = serializers.StringRelatedField(source='author')
751
752
# Foreign key as nested serializer (read-only)
753
author_detail = AuthorSerializer(source='author', read_only=True)
754
755
# Foreign key as hyperlink
756
author_url = serializers.HyperlinkedRelatedField(
757
source='author',
758
view_name='author-detail',
759
read_only=True
760
)
761
762
# Foreign key using slug field
763
publisher_slug = serializers.SlugRelatedField(
764
queryset=Publisher.objects.all(),
765
slug_field='slug',
766
source='publisher'
767
)
768
769
# Many-to-many as list of primary keys
770
category_ids = serializers.PrimaryKeyRelatedField(
771
queryset=Category.objects.all(),
772
many=True,
773
source='categories'
774
)
775
776
# Many-to-many as nested serializers (read-only)
777
categories = CategorySerializer(many=True, read_only=True)
778
779
class Meta:
780
model = Book
781
fields = [
782
'id', 'title', 'author_id', 'author_name', 'author_detail',
783
'author_url', 'publisher_slug', 'category_ids', 'categories'
784
]
785
```
786
787
### Dynamic and Conditional Fields
788
789
```python { .api }
790
class DynamicFieldsSerializer(serializers.ModelSerializer):
791
"""Serializer with dynamic field inclusion based on context."""
792
793
# Conditional fields based on user permissions
794
price = serializers.DecimalField(
795
max_digits=8,
796
decimal_places=2,
797
read_only=True
798
)
799
800
# Admin-only fields
801
internal_notes = serializers.CharField(read_only=True)
802
cost = serializers.DecimalField(max_digits=8, decimal_places=2, read_only=True)
803
804
class Meta:
805
model = Book
806
fields = ['id', 'title', 'author', 'price', 'internal_notes', 'cost']
807
808
def __init__(self, *args: Any, **kwargs: Any) -> None:
809
# Get user from context
810
request = self.context.get('request')
811
user = getattr(request, 'user', None)
812
813
super().__init__(*args, **kwargs)
814
815
# Remove price field for anonymous users
816
if not user or not user.is_authenticated:
817
self.fields.pop('price', None)
818
819
# Remove admin fields for non-staff users
820
if not user or not user.is_staff:
821
self.fields.pop('internal_notes', None)
822
self.fields.pop('cost', None)
823
824
# Support dynamic field selection
825
fields = self.context.get('fields')
826
if fields is not None:
827
# Only include specified fields
828
allowed = set(fields)
829
existing = set(self.fields)
830
for field_name in existing - allowed:
831
self.fields.pop(field_name)
832
```
833
834
## Custom Field Implementation
835
836
### Custom Field Class
837
838
```python { .api }
839
class ColorField(serializers.CharField):
840
"""Custom field for hex color validation."""
841
842
def __init__(self, **kwargs: Any) -> None:
843
kwargs.setdefault('max_length', 7) # #FFFFFF
844
super().__init__(**kwargs)
845
846
def to_internal_value(self, data: str) -> str:
847
"""Validate and normalize hex color."""
848
value = super().to_internal_value(data)
849
850
# Remove # if present
851
if value.startswith('#'):
852
value = value[1:]
853
854
# Validate hex format
855
if not re.match(r'^[0-9A-Fa-f]{6}$', value):
856
raise serializers.ValidationError('Invalid hex color format')
857
858
# Return with # prefix
859
return f'#{value.upper()}'
860
861
def to_representation(self, value: str) -> str:
862
"""Return hex color with # prefix."""
863
if value and not value.startswith('#'):
864
return f'#{value}'
865
return value
866
867
class PhoneNumberField(serializers.CharField):
868
"""Custom field for phone number validation and formatting."""
869
870
def __init__(self, **kwargs: Any) -> None:
871
kwargs.setdefault('max_length', 20)
872
super().__init__(**kwargs)
873
874
def to_internal_value(self, data: str) -> str:
875
"""Clean and validate phone number."""
876
value = super().to_internal_value(data)
877
878
# Remove all non-digit characters
879
digits_only = re.sub(r'[^\d]', '', value)
880
881
# Validate US phone number (10 digits)
882
if len(digits_only) == 10:
883
return f'({digits_only[:3]}) {digits_only[3:6]}-{digits_only[6:]}'
884
elif len(digits_only) == 11 and digits_only[0] == '1':
885
# Handle +1 country code
886
return f'+1 ({digits_only[1:4]}) {digits_only[4:7]}-{digits_only[7:]}'
887
else:
888
raise serializers.ValidationError('Invalid phone number format')
889
890
def to_representation(self, value: str) -> str:
891
"""Return formatted phone number."""
892
return value
893
894
class Base64ImageField(serializers.ImageField):
895
"""Custom field that accepts base64 encoded images."""
896
897
def to_internal_value(self, data: str) -> File:
898
"""Convert base64 string to image file."""
899
if isinstance(data, str) and data.startswith('data:image'):
900
# Parse data URL: data:image/jpeg;base64,/9j/4AAQ...
901
try:
902
format_match = re.match(r'data:image/(\w+);base64,(.+)', data)
903
if not format_match:
904
raise serializers.ValidationError('Invalid image data format')
905
906
image_format = format_match.group(1).lower()
907
image_data = format_match.group(2)
908
909
# Decode base64
910
decoded_data = base64.b64decode(image_data)
911
912
# Create file-like object
913
image_file = ContentFile(decoded_data, name=f'image.{image_format}')
914
915
return super().to_internal_value(image_file)
916
917
except (ValueError, TypeError) as e:
918
raise serializers.ValidationError(f'Invalid base64 image: {e}')
919
920
# Fall back to regular image field handling
921
return super().to_internal_value(data)
922
```
923
924
## Field Utility Functions
925
926
### Helper Functions
927
928
```python { .api }
929
def is_simple_callable(obj: Callable) -> bool:
930
"""Check if object is a simple callable (not a class)."""
931
...
932
933
def get_attribute(instance: Any, attrs: list[str] | None) -> Any:
934
"""
935
Get nested attribute from instance using dot notation.
936
937
Args:
938
instance: Object to get attribute from
939
attrs: List of attribute names for nested access
940
941
Returns:
942
Any: Attribute value
943
"""
944
...
945
946
def to_choices_dict(choices: Iterable[Any]) -> dict[Any, str]:
947
"""Convert choices iterable to dictionary format."""
948
...
949
950
def flatten_choices_dict(choices: dict[Any, Any]) -> dict[Any, str]:
951
"""Flatten nested choices dictionary."""
952
...
953
954
def iter_options(
955
grouped_choices: dict[Any, Any],
956
cutoff: int | None = None,
957
cutoff_text: str | None = None
958
) -> Generator[Any, None, None]:
959
"""Iterate over grouped choices with optional cutoff."""
960
...
961
962
def get_error_detail(exc_info: Any) -> Any:
963
"""Extract error detail from exception info."""
964
...
965
```
966
967
### Default Value Classes
968
969
```python { .api }
970
class CreateOnlyDefault:
971
"""Default value that only applies during creation."""
972
973
def __init__(self, default: Any) -> None: ...
974
def set_context(self, serializer_field: Field) -> None: ...
975
def __call__(self) -> Any: ...
976
977
class CurrentUserDefault:
978
"""Default value that returns the current user."""
979
980
requires_context: bool # True
981
982
def set_context(self, serializer_field: Field) -> None: ...
983
def __call__(self) -> Any: ...
984
```
985
986
### Constants
987
988
```python { .api }
989
# Empty value sentinel
990
empty: Final[Any]
991
992
# Regular expression type
993
REGEX_TYPE: type[Pattern[str]]
994
995
# Error messages
996
MISSING_ERROR_MESSAGE: str
997
INVALID_ERROR_MESSAGE: str
998
REQUIRED_ERROR_MESSAGE: str
999
ALLOW_NULL_ERROR_MESSAGE: str
1000
NOT_A_DICT_ERROR_MESSAGE: str
1001
NOT_A_LIST_ERROR_MESSAGE: str
1002
# ... and many more error message constants
1003
```
1004
1005
This comprehensive field and relations system provides type-safe data validation and serialization with full mypy support, enabling confident implementation of robust data handling patterns in Django REST Framework applications.