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.