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

requests-responses.mddocs/

0

# Requests & Responses

1

2

Django REST Framework provides enhanced Request and Response objects that extend Django's base HTTP handling with additional features for API development. The type stubs ensure complete type safety for request parsing, response rendering, and content negotiation.

3

4

## Request Object

5

6

### Request Class

7

8

```python { .api }

9

class Request(HttpRequest):

10

"""Enhanced request object with DRF-specific functionality."""

11

12

# Parser and authentication configuration

13

parsers: Sequence[BaseParser] | None

14

authenticators: Sequence[BaseAuthentication | ForcedAuthentication] | None

15

negotiator: BaseContentNegotiation | None

16

17

# Content negotiation results

18

accepted_renderer: BaseRenderer

19

accepted_media_type: str

20

21

# Authentication results

22

user: AbstractUser | AnonymousUser

23

auth: Any # Token, session, or custom auth object

24

25

# Parsed data properties

26

@property

27

def data(self) -> dict[str, Any]:

28

"""

29

Parsed request data from request body.

30

31

Returns:

32

dict[str, Any]: Parsed data (JSON, form data, etc.)

33

"""

34

...

35

36

@property

37

def query_params(self) -> QueryDict:

38

"""

39

Query parameters from URL (immutable).

40

41

Returns:

42

QueryDict: URL query parameters

43

"""

44

...

45

46

def __init__(

47

self,

48

request: HttpRequest,

49

parsers: Sequence[BaseParser] | None = None,

50

authenticators: Sequence[BaseAuthentication] | None = None,

51

negotiator: BaseContentNegotiation | None = None,

52

parser_context: dict[str, Any] | None = None

53

) -> None: ...

54

```

55

56

**Key Properties:**

57

- `data: dict[str, Any]` - Parsed request body (JSON, form data, files)

58

- `query_params: QueryDict` - URL query parameters (immutable)

59

- `user: AbstractUser | AnonymousUser` - Authenticated user or anonymous user

60

- `auth: Any` - Authentication object (token, session data, etc.)

61

62

### Request Utilities

63

64

```python { .api }

65

def is_form_media_type(media_type: str) -> bool:

66

"""

67

Check if media type is form-encoded.

68

69

Args:

70

media_type: Content-Type header value

71

72

Returns:

73

bool: True if form media type

74

"""

75

...

76

77

def clone_request(request: Request, method: str) -> Request:

78

"""

79

Clone a request with a different HTTP method.

80

81

Args:

82

request: Original request object

83

method: New HTTP method

84

85

Returns:

86

Request: Cloned request with new method

87

"""

88

...

89

```

90

91

### ForcedAuthentication

92

93

```python { .api }

94

class ForcedAuthentication:

95

"""Force authentication for testing purposes."""

96

97

def __init__(self, user: AbstractUser, token: Any = None) -> None: ...

98

```

99

100

**Parameters:**

101

- `user: AbstractUser` - User to authenticate as

102

- `token: Any` - Optional authentication token

103

104

### Empty Data Markers

105

106

```python { .api }

107

class Empty:

108

"""Marker class for empty request data."""

109

pass

110

111

# Singleton instance

112

empty: Empty

113

```

114

115

## Response Object

116

117

### Response Class

118

119

```python { .api }

120

class Response(SimpleTemplateResponse):

121

"""Enhanced response object with DRF functionality."""

122

123

# Response data and metadata

124

data: Any

125

exception: bool

126

content_type: str | None

127

128

# Content negotiation results

129

accepted_renderer: BaseRenderer

130

accepted_media_type: str

131

renderer_context: dict[str, Any]

132

133

def __init__(

134

self,

135

data: Any = None,

136

status: int | None = None,

137

template_name: str | list[str] | None = None,

138

headers: dict[str, str] | None = None,

139

exception: bool = False,

140

content_type: str | None = None

141

) -> None: ...

142

143

@property

144

def rendered_content(self) -> bytes:

145

"""Get rendered response content."""

146

...

147

148

@property

149

def status_text(self) -> str:

150

"""Get HTTP status text."""

151

...

152

153

def render(self) -> Response:

154

"""

155

Render the response content using the configured renderer.

156

157

Returns:

158

Response: Self after rendering

159

"""

160

...

161

```

162

163

**Parameters:**

164

- `data: Any` - Response data to serialize

165

- `status: int | None` - HTTP status code

166

- `template_name: str | list[str] | None` - Template for HTML responses

167

- `headers: dict[str, str] | None` - Additional HTTP headers

168

- `exception: bool` - Whether response represents an exception

169

- `content_type: str | None` - Override content type

170

171

## Request Processing Examples

172

173

### Accessing Request Data

174

175

```python { .api }

176

from rest_framework.views import APIView

177

from rest_framework.response import Response

178

from rest_framework import status

179

180

class BookCreateView(APIView):

181

"""Demonstrate request data access."""

182

183

def post(self, request: Request) -> Response:

184

"""Handle POST request with data parsing."""

185

186

# Access parsed JSON/form data

187

title = request.data.get('title')

188

author_id = request.data.get('author_id')

189

190

# Access query parameters

191

format_type = request.query_params.get('format', 'json')

192

include_meta = request.query_params.get('include_meta', 'false').lower() == 'true'

193

194

# Access authentication info

195

user = request.user

196

auth_token = request.auth

197

198

# Access request metadata

199

content_type = request.content_type

200

method = request.method

201

202

# Validate and process data

203

if not title or not author_id:

204

return Response(

205

{'error': 'Title and author_id are required'},

206

status=status.HTTP_400_BAD_REQUEST

207

)

208

209

# Create book instance

210

book = Book.objects.create(

211

title=title,

212

author_id=author_id,

213

created_by=user if user.is_authenticated else None

214

)

215

216

# Return response based on format preference

217

response_data = {

218

'id': book.id,

219

'title': book.title,

220

'author_id': book.author_id

221

}

222

223

if include_meta:

224

response_data['meta'] = {

225

'created_by': user.username if user.is_authenticated else None,

226

'created_at': book.created_at.isoformat(),

227

'format': format_type

228

}

229

230

return Response(response_data, status=status.HTTP_201_CREATED)

231

```

232

233

### File Upload Handling

234

235

```python { .api }

236

from django.core.files.storage import default_storage

237

238

class FileUploadView(APIView):

239

"""Handle file uploads with request data access."""

240

241

def post(self, request: Request) -> Response:

242

"""Process file upload request."""

243

244

# Access uploaded files

245

uploaded_file = request.data.get('file')

246

if not uploaded_file:

247

return Response(

248

{'error': 'No file provided'},

249

status=status.HTTP_400_BAD_REQUEST

250

)

251

252

# Access additional form fields

253

title = request.data.get('title', uploaded_file.name)

254

description = request.data.get('description', '')

255

256

# Validate file

257

if uploaded_file.size > 10 * 1024 * 1024: # 10MB limit

258

return Response(

259

{'error': 'File too large'},

260

status=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE

261

)

262

263

# Save file

264

file_path = default_storage.save(

265

f"uploads/{uploaded_file.name}",

266

uploaded_file

267

)

268

269

# Create database record

270

document = Document.objects.create(

271

title=title,

272

description=description,

273

file_path=file_path,

274

uploaded_by=request.user

275

)

276

277

return Response({

278

'id': document.id,

279

'title': document.title,

280

'file_url': default_storage.url(file_path)

281

}, status=status.HTTP_201_CREATED)

282

```

283

284

## Response Creation Examples

285

286

### Basic Response Patterns

287

288

```python { .api }

289

from rest_framework.response import Response

290

from rest_framework import status

291

292

class BookDetailView(APIView):

293

"""Demonstrate various response patterns."""

294

295

def get(self, request: Request, pk: int) -> Response:

296

"""Return different response types based on conditions."""

297

298

try:

299

book = Book.objects.get(pk=pk)

300

except Book.DoesNotExist:

301

# Error response with custom status

302

return Response(

303

{'error': 'Book not found'},

304

status=status.HTTP_404_NOT_FOUND

305

)

306

307

# Check permissions

308

if book.is_private and request.user != book.owner:

309

return Response(

310

{'error': 'Access denied'},

311

status=status.HTTP_403_FORBIDDEN

312

)

313

314

# Success response with data

315

serializer = BookSerializer(book, context={'request': request})

316

return Response(serializer.data, status=status.HTTP_200_OK)

317

318

def put(self, request: Request, pk: int) -> Response:

319

"""Update with validation response patterns."""

320

321

try:

322

book = Book.objects.get(pk=pk)

323

except Book.DoesNotExist:

324

return Response(

325

{'error': 'Book not found'},

326

status=status.HTTP_404_NOT_FOUND

327

)

328

329

# Validate and update

330

serializer = BookSerializer(book, data=request.data, partial=False)

331

if serializer.is_valid():

332

serializer.save()

333

return Response(serializer.data, status=status.HTTP_200_OK)

334

else:

335

# Validation error response

336

return Response(

337

serializer.errors,

338

status=status.HTTP_400_BAD_REQUEST

339

)

340

341

def delete(self, request: Request, pk: int) -> Response:

342

"""Delete with empty response."""

343

344

try:

345

book = Book.objects.get(pk=pk)

346

book.delete()

347

# Empty success response

348

return Response(status=status.HTTP_204_NO_CONTENT)

349

except Book.DoesNotExist:

350

return Response(

351

{'error': 'Book not found'},

352

status=status.HTTP_404_NOT_FOUND

353

)

354

```

355

356

### Custom Headers and Content Types

357

358

```python { .api }

359

class CustomResponseView(APIView):

360

"""Demonstrate custom response headers and content types."""

361

362

def get(self, request: Request) -> Response:

363

"""Response with custom headers."""

364

365

data = {'message': 'Custom response'}

366

headers = {

367

'X-Custom-Header': 'Custom Value',

368

'X-Request-ID': str(uuid.uuid4()),

369

'Cache-Control': 'no-cache, no-store',

370

}

371

372

return Response(

373

data,

374

status=status.HTTP_200_OK,

375

headers=headers

376

)

377

378

def post(self, request: Request) -> Response:

379

"""Response with custom content type."""

380

381

# Process request data

382

result = {'processed': True, 'timestamp': timezone.now().isoformat()}

383

384

return Response(

385

result,

386

status=status.HTTP_201_CREATED,

387

content_type='application/vnd.api+json'

388

)

389

```

390

391

### Conditional Responses

392

393

```python { .api }

394

class ConditionalResponseView(APIView):

395

"""Demonstrate conditional response handling."""

396

397

def get(self, request: Request) -> Response:

398

"""Return different responses based on request parameters."""

399

400

# Check for format preference

401

format_param = request.query_params.get('format', 'json')

402

403

if format_param == 'xml':

404

return Response(

405

{'message': 'XML format requested'},

406

content_type='application/xml'

407

)

408

elif format_param == 'csv':

409

# Return CSV data

410

csv_data = "id,name,value\n1,test,100"

411

return Response(

412

csv_data,

413

content_type='text/csv',

414

headers={'Content-Disposition': 'attachment; filename="data.csv"'}

415

)

416

else:

417

# Default JSON response

418

return Response({

419

'data': ['item1', 'item2', 'item3'],

420

'format': 'json'

421

})

422

```

423

424

## Advanced Request/Response Handling

425

426

### Request Method Override

427

428

```python { .api }

429

from rest_framework.request import override_method

430

431

class MethodOverrideView(APIView):

432

"""Handle method override for clients that don't support all HTTP methods."""

433

434

def post(self, request: Request) -> Response:

435

"""Handle POST with method override."""

436

437

# Check for method override header

438

override_method_header = request.META.get('HTTP_X_HTTP_METHOD_OVERRIDE')

439

440

if override_method_header:

441

with override_method(request, override_method_header):

442

if override_method_header.upper() == 'PUT':

443

return self.put(request)

444

elif override_method_header.upper() == 'PATCH':

445

return self.patch(request)

446

elif override_method_header.upper() == 'DELETE':

447

return self.delete(request)

448

449

# Regular POST handling

450

return Response({'method': 'POST'})

451

452

def put(self, request: Request) -> Response:

453

return Response({'method': 'PUT'})

454

455

def patch(self, request: Request) -> Response:

456

return Response({'method': 'PATCH'})

457

458

def delete(self, request: Request) -> Response:

459

return Response({'method': 'DELETE'})

460

```

461

462

### Streaming Responses

463

464

```python { .api }

465

import json

466

from django.http import StreamingHttpResponse

467

468

class StreamingResponseView(APIView):

469

"""Handle large dataset streaming."""

470

471

def get(self, request: Request) -> StreamingHttpResponse:

472

"""Stream large JSON response."""

473

474

def generate_data():

475

"""Generator function for streaming data."""

476

yield '{"items": ['

477

478

first = True

479

for book in Book.objects.iterator(chunk_size=1000):

480

if not first:

481

yield ','

482

first = False

483

484

book_data = {

485

'id': book.id,

486

'title': book.title,

487

'author': book.author.name if book.author else None

488

}

489

yield json.dumps(book_data)

490

491

yield ']}'

492

493

response = StreamingHttpResponse(

494

generate_data(),

495

content_type='application/json'

496

)

497

response['Content-Disposition'] = 'attachment; filename="books.json"'

498

return response

499

```

500

501

### Request Context and Metadata

502

503

```python { .api }

504

class RequestContextView(APIView):

505

"""Access comprehensive request context information."""

506

507

def get(self, request: Request) -> Response:

508

"""Return detailed request context."""

509

510

context = {

511

# HTTP Information

512

'method': request.method,

513

'path': request.path,

514

'full_path': request.get_full_path(),

515

'scheme': request.scheme,

516

'is_secure': request.is_secure(),

517

518

# Authentication

519

'user': {

520

'username': request.user.username if request.user.is_authenticated else None,

521

'is_authenticated': request.user.is_authenticated,

522

'is_staff': getattr(request.user, 'is_staff', False),

523

},

524

'auth_type': type(request.auth).__name__ if request.auth else None,

525

526

# Content Information

527

'content_type': request.content_type,

528

'accepted_media_type': getattr(request, 'accepted_media_type', None),

529

'accepted_renderer': type(getattr(request, 'accepted_renderer', None)).__name__,

530

531

# Client Information

532

'user_agent': request.META.get('HTTP_USER_AGENT'),

533

'remote_addr': request.META.get('REMOTE_ADDR'),

534

'host': request.get_host(),

535

536

# Data Summary

537

'has_data': bool(request.data),

538

'data_keys': list(request.data.keys()) if request.data else [],

539

'query_params': dict(request.query_params),

540

}

541

542

return Response(context)

543

```

544

545

## Error Response Patterns

546

547

### Standardized Error Responses

548

549

```python { .api }

550

class ErrorResponseView(APIView):

551

"""Demonstrate standardized error response patterns."""

552

553

def post(self, request: Request) -> Response:

554

"""Handle request with comprehensive error handling."""

555

556

try:

557

# Validate required fields

558

required_fields = ['name', 'email']

559

missing_fields = []

560

561

for field in required_fields:

562

if field not in request.data:

563

missing_fields.append(field)

564

565

if missing_fields:

566

return Response({

567

'error': 'Missing required fields',

568

'code': 'MISSING_FIELDS',

569

'details': {

570

'missing_fields': missing_fields

571

}

572

}, status=status.HTTP_400_BAD_REQUEST)

573

574

# Validate email format

575

email = request.data['email']

576

if '@' not in email:

577

return Response({

578

'error': 'Invalid email format',

579

'code': 'INVALID_EMAIL',

580

'details': {

581

'field': 'email',

582

'value': email

583

}

584

}, status=status.HTTP_400_BAD_REQUEST)

585

586

# Process successful request

587

return Response({

588

'message': 'Success',

589

'data': request.data

590

}, status=status.HTTP_201_CREATED)

591

592

except Exception as e:

593

# Handle unexpected errors

594

return Response({

595

'error': 'Internal server error',

596

'code': 'INTERNAL_ERROR',

597

'details': {

598

'message': str(e) if settings.DEBUG else 'An unexpected error occurred'

599

}

600

}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

601

```

602

603

### Response Formatting Middleware

604

605

```python { .api }

606

class ResponseFormatView(APIView):

607

"""Demonstrate consistent response formatting."""

608

609

def format_success_response(

610

self,

611

data: Any,

612

message: str = 'Success',

613

status_code: int = status.HTTP_200_OK

614

) -> Response:

615

"""Format successful response with consistent structure."""

616

617

response_data = {

618

'success': True,

619

'message': message,

620

'data': data,

621

'timestamp': timezone.now().isoformat()

622

}

623

624

return Response(response_data, status=status_code)

625

626

def format_error_response(

627

self,

628

error: str,

629

code: str | None = None,

630

details: dict[str, Any] | None = None,

631

status_code: int = status.HTTP_400_BAD_REQUEST

632

) -> Response:

633

"""Format error response with consistent structure."""

634

635

response_data = {

636

'success': False,

637

'error': error,

638

'code': code,

639

'details': details or {},

640

'timestamp': timezone.now().isoformat()

641

}

642

643

return Response(response_data, status=status_code)

644

645

def get(self, request: Request) -> Response:

646

"""Example using formatted responses."""

647

648

books = Book.objects.all()[:10]

649

serializer = BookSerializer(books, many=True)

650

651

return self.format_success_response(

652

data=serializer.data,

653

message='Books retrieved successfully'

654

)

655

```

656

657

This comprehensive request and response system provides type-safe HTTP handling with full mypy support, enabling confident API development with proper request parsing, response formatting, and error handling patterns.