or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

authentication-permissions.mdexceptions-status.mdfields-relations.mdindex.mdpagination-filtering.mdrequests-responses.mdrouters-urls.mdserializers.mdviews-viewsets.md

fields-relations.mddocs/

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.