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

views-viewsets.mddocs/

0

# Views & ViewSets

1

2

Django REST Framework views and viewsets provide the core infrastructure for handling HTTP requests and returning responses. The type stubs enable full type safety for view classes, mixins, and viewset operations with comprehensive generic type support.

3

4

## Core Imports

5

6

```python { .api }

7

from typing import Any, NoReturn, Sequence, Callable

8

from django.http import HttpRequest, HttpResponseBase

9

from django.db.models import QuerySet, Manager

10

from rest_framework.views import APIView

11

from rest_framework.generics import GenericAPIView, UsesQuerySet

12

from rest_framework.mixins import (

13

CreateModelMixin, ListModelMixin, RetrieveModelMixin,

14

UpdateModelMixin, DestroyModelMixin

15

)

16

from rest_framework.viewsets import ViewSet, GenericViewSet, ModelViewSet

17

from rest_framework.request import Request

18

from rest_framework.response import Response

19

from rest_framework.authentication import BaseAuthentication

20

from rest_framework.permissions import _PermissionClass, _SupportsHasPermission

21

from rest_framework.renderers import BaseRenderer

22

from rest_framework.parsers import BaseParser

23

from rest_framework.throttling import BaseThrottle

24

from rest_framework.negotiation import BaseContentNegotiation

25

from rest_framework.metadata import BaseMetadata

26

from rest_framework.versioning import BaseVersioning

27

from rest_framework.serializers import BaseSerializer

28

from rest_framework.filters import BaseFilterBackend

29

from rest_framework.pagination import BasePagination

30

from rest_framework.decorators import ViewSetAction

31

32

# Type variables

33

_MT_co = TypeVar("_MT_co", bound=Model, covariant=True)

34

_ViewFunc = Callable[..., HttpResponseBase]

35

```

36

37

## Base View Classes

38

39

### APIView

40

41

```python { .api }

42

class APIView(View):

43

"""Base class for all DRF views with comprehensive type safety."""

44

45

# Class-level configuration

46

authentication_classes: Sequence[type[BaseAuthentication]]

47

permission_classes: Sequence[_PermissionClass]

48

renderer_classes: Sequence[type[BaseRenderer]]

49

parser_classes: Sequence[type[BaseParser]]

50

throttle_classes: Sequence[type[BaseThrottle]]

51

content_negotiation_class: type[BaseContentNegotiation]

52

metadata_class: str | BaseMetadata | None

53

versioning_class: type[BaseVersioning] | None

54

55

# Request processing

56

def dispatch(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

57

def handle_exception(self, exc: Exception) -> Response: ...

58

def permission_denied(self, request: Request, message: str | None = None, code: str | None = None) -> NoReturn: ...

59

def throttled(self, request: Request, wait: float) -> NoReturn: ...

60

61

# Configuration methods

62

def get_authenticators(self) -> list[BaseAuthentication]: ...

63

def get_permissions(self) -> Sequence[_SupportsHasPermission]: ...

64

def get_renderers(self) -> list[BaseRenderer]: ...

65

def get_parsers(self) -> list[BaseParser]: ...

66

def get_throttles(self) -> list[BaseThrottle]: ...

67

def get_content_negotiator(self) -> BaseContentNegotiation: ...

68

def get_exception_handler(self) -> Callable: ...

69

70

# HTTP method handlers

71

def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

72

def post(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

73

def put(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

74

def patch(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

75

def delete(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

76

def head(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

77

def options(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

78

def trace(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

79

```

80

81

### GenericAPIView

82

83

```python { .api }

84

class GenericAPIView(APIView, UsesQuerySet[_MT_co]):

85

"""Generic view with model type support and common functionality."""

86

87

# Model and serializer configuration

88

queryset: QuerySet[_MT_co] | Manager[_MT_co] | None

89

serializer_class: type[BaseSerializer[_MT_co]] | None

90

91

# Lookup configuration

92

lookup_field: str # Default: 'pk'

93

lookup_url_kwarg: str | None

94

95

# Filtering and pagination

96

filter_backends: Sequence[type[BaseFilterBackend | BaseFilterProtocol[_MT_co]]]

97

pagination_class: type[BasePagination] | None

98

99

# Object retrieval

100

def get_object(self) -> _MT_co: ...

101

def get_queryset(self) -> QuerySet[_MT_co]: ...

102

def filter_queryset(self, queryset: QuerySet[_MT_co]) -> QuerySet[_MT_co]: ...

103

def paginate_queryset(self, queryset: QuerySet[_MT_co] | Sequence[Any]) -> Sequence[Any] | None: ...

104

def get_paginated_response(self, data: Any) -> Response: ...

105

106

# Serializer methods

107

def get_serializer(self, *args: Any, **kwargs: Any) -> BaseSerializer[_MT_co]: ...

108

def get_serializer_class(self) -> type[BaseSerializer[_MT_co]]: ...

109

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

110

```

111

112

**Parameters:**

113

- `queryset: QuerySet[_MT_co] | Manager[_MT_co] | None` - Base queryset for the view

114

- `serializer_class: type[BaseSerializer[_MT_co]] | None` - Serializer class for the model

115

- `lookup_field: str` - Model field for object lookup (default: 'pk')

116

- `lookup_url_kwarg: str | None` - URL kwarg name for lookup (defaults to lookup_field)

117

118

## View Mixins

119

120

### CreateModelMixin

121

122

```python { .api }

123

class CreateModelMixin:

124

"""Mixin for creating model instances."""

125

126

def create(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

127

def perform_create(self: UsesQuerySet[_MT], serializer: BaseSerializer[_MT]) -> None: ...

128

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

129

```

130

131

### ListModelMixin

132

133

```python { .api }

134

class ListModelMixin:

135

"""Mixin for listing model instances."""

136

137

def list(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

138

```

139

140

### RetrieveModelMixin

141

142

```python { .api }

143

class RetrieveModelMixin:

144

"""Mixin for retrieving a model instance."""

145

146

def retrieve(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

147

```

148

149

### UpdateModelMixin

150

151

```python { .api }

152

class UpdateModelMixin:

153

"""Mixin for updating model instances."""

154

155

def update(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

156

def partial_update(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

157

def perform_update(self: UsesQuerySet[_MT], serializer: BaseSerializer[_MT]) -> None: ...

158

```

159

160

### DestroyModelMixin

161

162

```python { .api }

163

class DestroyModelMixin:

164

"""Mixin for deleting model instances."""

165

166

def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

167

def perform_destroy(self: UsesQuerySet[_MT], instance: _MT) -> None: ...

168

```

169

170

## Concrete Generic Views

171

172

### Create Views

173

174

```python { .api }

175

class CreateAPIView(CreateModelMixin, GenericAPIView[_MT_co]):

176

"""Concrete view for creating model instances."""

177

178

def post(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

179

```

180

181

### List Views

182

183

```python { .api }

184

class ListAPIView(ListModelMixin, GenericAPIView[_MT_co]):

185

"""Concrete view for listing model instances."""

186

187

def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

188

```

189

190

### Retrieve Views

191

192

```python { .api }

193

class RetrieveAPIView(RetrieveModelMixin, GenericAPIView[_MT_co]):

194

"""Concrete view for retrieving a model instance."""

195

196

def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

197

```

198

199

### Update Views

200

201

```python { .api }

202

class UpdateAPIView(UpdateModelMixin, GenericAPIView[_MT_co]):

203

"""Concrete view for updating model instances."""

204

205

def put(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

206

def patch(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

207

```

208

209

### Delete Views

210

211

```python { .api }

212

class DestroyAPIView(DestroyModelMixin, GenericAPIView[_MT_co]):

213

"""Concrete view for deleting model instances."""

214

215

def delete(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

216

```

217

218

### Combined Views

219

220

```python { .api }

221

class ListCreateAPIView(ListModelMixin, CreateModelMixin, GenericAPIView[_MT_co]):

222

"""List and create model instances."""

223

224

def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

225

def post(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

226

227

class RetrieveUpdateAPIView(RetrieveModelMixin, UpdateModelMixin, GenericAPIView[_MT_co]):

228

"""Retrieve and update model instances."""

229

230

def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

231

def put(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

232

def patch(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

233

234

class RetrieveDestroyAPIView(RetrieveModelMixin, DestroyModelMixin, GenericAPIView[_MT_co]):

235

"""Retrieve and delete model instances."""

236

237

def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

238

def delete(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

239

240

class RetrieveUpdateDestroyAPIView(

241

RetrieveModelMixin,

242

UpdateModelMixin,

243

DestroyModelMixin,

244

GenericAPIView[_MT_co]

245

):

246

"""Full CRUD operations for model instances."""

247

248

def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

249

def put(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

250

def patch(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

251

def delete(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

252

```

253

254

## ViewSet Classes

255

256

### ViewSetMixin

257

258

```python { .api }

259

class ViewSetMixin:

260

"""Base mixin that converts a ViewSet class into a concrete view."""

261

262

# Class variables assigned in as_view()

263

name: str | None

264

description: str | None

265

suffix: str | None

266

detail: bool

267

basename: str

268

# Instance attributes assigned in view wrapper

269

action_map: dict[str, str]

270

args: tuple[Any, ...]

271

kwargs: dict[str, Any]

272

# Assigned in initialize_request()

273

action: str

274

275

@classmethod

276

def as_view(

277

cls,

278

actions: dict[str, str | ViewSetAction] | None = None,

279

**initkwargs: Any

280

) -> AsView[GenericView]: ...

281

282

def initialize_request(self, request: HttpRequest, *args: Any, **kwargs: Any) -> Request: ...

283

def reverse_action(self, url_name: str, *args: Any, **kwargs: Any) -> str: ...

284

@classmethod

285

def get_extra_actions(cls) -> list[_ViewFunc]: ...

286

def get_extra_action_url_map(self) -> dict[str, str]: ...

287

```

288

289

### ViewSet

290

291

```python { .api }

292

class ViewSet(ViewSetMixin, APIView):

293

"""Base ViewSet class providing the interface for routing multiple actions."""

294

pass

295

```

296

297

### GenericViewSet

298

299

```python { .api }

300

class GenericViewSet(ViewSetMixin, GenericAPIView[_MT_co]):

301

"""Generic ViewSet with model type support."""

302

pass

303

```

304

305

### Model ViewSets

306

307

```python { .api }

308

class ReadOnlyModelViewSet(

309

RetrieveModelMixin,

310

ListModelMixin,

311

GenericViewSet[_MT_co]

312

):

313

"""ViewSet that provides read-only actions."""

314

pass

315

316

class ModelViewSet(

317

CreateModelMixin,

318

RetrieveModelMixin,

319

UpdateModelMixin,

320

DestroyModelMixin,

321

ListModelMixin,

322

GenericViewSet[_MT_co]

323

):

324

"""ViewSet that provides full CRUD actions."""

325

pass

326

```

327

328

## Usage Examples

329

330

### Custom API View

331

332

```python { .api }

333

from rest_framework.views import APIView

334

from rest_framework.response import Response

335

from rest_framework import status

336

from rest_framework.permissions import IsAuthenticated

337

338

class BookSearchView(APIView):

339

"""Custom view for searching books."""

340

341

permission_classes = [IsAuthenticated]

342

343

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

344

query = request.query_params.get('q', '')

345

if not query:

346

return Response(

347

{'error': 'Query parameter q is required'},

348

status=status.HTTP_400_BAD_REQUEST

349

)

350

351

books = Book.objects.filter(title__icontains=query)

352

serializer = BookSerializer(books, many=True)

353

return Response(serializer.data)

354

355

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

356

"""Advanced search with filters."""

357

serializer = BookSearchSerializer(data=request.data)

358

if serializer.is_valid():

359

# Perform complex search logic

360

results = self.perform_search(serializer.validated_data)

361

return Response(results)

362

return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

363

364

def perform_search(self, filters: dict[str, Any]) -> list[dict[str, Any]]:

365

"""Perform the actual search operation."""

366

queryset = Book.objects.all()

367

368

if 'genre' in filters:

369

queryset = queryset.filter(genre=filters['genre'])

370

if 'author' in filters:

371

queryset = queryset.filter(author__name__icontains=filters['author'])

372

if 'published_after' in filters:

373

queryset = queryset.filter(published_date__gte=filters['published_after'])

374

375

serializer = BookSerializer(queryset, many=True)

376

return serializer.data

377

```

378

379

### Generic List Create View

380

381

```python { .api }

382

from rest_framework import generics

383

from rest_framework.permissions import IsAuthenticated

384

385

class BookListCreateView(generics.ListCreateAPIView[Book]):

386

"""List books and create new books."""

387

388

queryset = Book.objects.all()

389

serializer_class = BookSerializer

390

permission_classes = [IsAuthenticated]

391

392

def get_queryset(self) -> QuerySet[Book]:

393

"""Filter books by current user if requested."""

394

queryset = super().get_queryset()

395

user_only = self.request.query_params.get('user_only', 'false')

396

397

if user_only.lower() == 'true':

398

return queryset.filter(created_by=self.request.user)

399

return queryset

400

401

def perform_create(self, serializer: BookSerializer) -> None:

402

"""Set the created_by field to current user."""

403

serializer.save(created_by=self.request.user)

404

```

405

406

### Model ViewSet

407

408

```python { .api }

409

from rest_framework import viewsets, status

410

from rest_framework.decorators import action

411

from rest_framework.response import Response

412

413

class BookViewSet(viewsets.ModelViewSet[Book]):

414

"""Full CRUD operations for books."""

415

416

queryset = Book.objects.all()

417

serializer_class = BookSerializer

418

419

def get_permissions(self) -> list[BasePermission]:

420

"""Different permissions for different actions."""

421

if self.action in ['create', 'update', 'partial_update', 'destroy']:

422

permission_classes = [IsAuthenticated]

423

else:

424

permission_classes = []

425

return [permission() for permission in permission_classes]

426

427

def get_serializer_class(self) -> type[BaseSerializer[Book]]:

428

"""Different serializers for different actions."""

429

if self.action == 'list':

430

return BookListSerializer

431

elif self.action in ['create', 'update', 'partial_update']:

432

return BookWriteSerializer

433

return BookDetailSerializer

434

435

@action(detail=True, methods=['post'])

436

def favorite(self, request: Request, pk: str | None = None) -> Response:

437

"""Add book to user's favorites."""

438

book = self.get_object()

439

user = request.user

440

441

if user in book.favorited_by.all():

442

return Response(

443

{'detail': 'Already favorited'},

444

status=status.HTTP_400_BAD_REQUEST

445

)

446

447

book.favorited_by.add(user)

448

return Response({'detail': 'Added to favorites'})

449

450

@action(detail=True, methods=['delete'])

451

def unfavorite(self, request: Request, pk: str | None = None) -> Response:

452

"""Remove book from user's favorites."""

453

book = self.get_object()

454

user = request.user

455

456

if user not in book.favorited_by.all():

457

return Response(

458

{'detail': 'Not in favorites'},

459

status=status.HTTP_400_BAD_REQUEST

460

)

461

462

book.favorited_by.remove(user)

463

return Response({'detail': 'Removed from favorites'})

464

465

@action(detail=False, methods=['get'])

466

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

467

"""Get popular books based on favorites count."""

468

popular_books = Book.objects.annotate(

469

favorites_count=Count('favorited_by')

470

).order_by('-favorites_count')[:10]

471

472

serializer = self.get_serializer(popular_books, many=True)

473

return Response(serializer.data)

474

```

475

476

### Custom ViewSet Actions

477

478

```python { .api }

479

from rest_framework.decorators import action

480

481

class AuthorViewSet(viewsets.ModelViewSet[Author]):

482

queryset = Author.objects.all()

483

serializer_class = AuthorSerializer

484

485

@action(detail=True, methods=['get'])

486

def books(self, request: Request, pk: str | None = None) -> Response:

487

"""Get all books by this author."""

488

author = self.get_object()

489

books = author.book_set.all()

490

serializer = BookSerializer(books, many=True)

491

return Response(serializer.data)

492

493

@action(detail=True, methods=['get'])

494

def statistics(self, request: Request, pk: str | None = None) -> Response:

495

"""Get author statistics."""

496

author = self.get_object()

497

stats = {

498

'total_books': author.book_set.count(),

499

'average_rating': author.book_set.aggregate(

500

avg_rating=Avg('rating')

501

)['avg_rating'] or 0,

502

'latest_book': author.book_set.order_by('-published_date').first().title

503

}

504

return Response(stats)

505

506

@action(detail=False, methods=['get'], url_path='by-genre/(?P<genre>[^/.]+)')

507

def by_genre(self, request: Request, genre: str | None = None) -> Response:

508

"""Get authors who have written books in a specific genre."""

509

authors = Author.objects.filter(book__genre=genre).distinct()

510

serializer = self.get_serializer(authors, many=True)

511

return Response(serializer.data)

512

```

513

514

## View Configuration

515

516

### Permissions and Authentication

517

518

```python { .api }

519

class ProtectedBookView(generics.RetrieveUpdateDestroyAPIView[Book]):

520

queryset = Book.objects.all()

521

serializer_class = BookSerializer

522

authentication_classes = [TokenAuthentication, SessionAuthentication]

523

permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]

524

525

def get_permissions(self) -> list[BasePermission]:

526

"""Custom permission logic based on action."""

527

if self.request.method in ['PUT', 'PATCH', 'DELETE']:

528

permission_classes = [IsAuthenticated, IsOwner]

529

else:

530

permission_classes = [AllowAny]

531

return [permission() for permission in permission_classes]

532

```

533

534

### Filtering and Pagination

535

536

```python { .api }

537

from rest_framework.filters import SearchFilter, OrderingFilter

538

from django_filters.rest_framework import DjangoFilterBackend

539

540

class BookListView(generics.ListAPIView[Book]):

541

queryset = Book.objects.all()

542

serializer_class = BookSerializer

543

filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]

544

filterset_fields = ['genre', 'author', 'published_date']

545

search_fields = ['title', 'description', 'author__name']

546

ordering_fields = ['title', 'published_date', 'rating']

547

ordering = ['-published_date']

548

pagination_class = PageNumberPagination

549

```

550

551

## Utility Functions

552

553

### View Helper Functions

554

555

```python { .api }

556

def get_view_name(view: APIView) -> str:

557

"""Get a human-readable name for the view."""

558

...

559

560

def get_view_description(view: APIView, html: bool = False) -> str:

561

"""Get a human-readable description for the view."""

562

...

563

564

def set_rollback() -> None:

565

"""Mark the current transaction as rollback-only."""

566

...

567

568

def exception_handler(exc: Exception, context: dict[str, Any]) -> Response | None:

569

"""Default exception handler for DRF views."""

570

...

571

```

572

573

## Performance Optimization

574

575

### Query Optimization

576

577

```python { .api }

578

class OptimizedBookViewSet(viewsets.ModelViewSet[Book]):

579

serializer_class = BookSerializer

580

581

def get_queryset(self) -> QuerySet[Book]:

582

"""Optimize queries with select_related and prefetch_related."""

583

return Book.objects.select_related('author', 'publisher') \

584

.prefetch_related('genres', 'reviews')

585

586

def list(self, request: Request, *args: Any, **kwargs: Any) -> Response:

587

"""Custom list method with query optimization."""

588

queryset = self.get_queryset()

589

590

# Add any additional filtering

591

search = request.query_params.get('search')

592

if search:

593

queryset = queryset.filter(

594

Q(title__icontains=search) |

595

Q(description__icontains=search)

596

)

597

598

page = self.paginate_queryset(queryset)

599

if page is not None:

600

serializer = self.get_serializer(page, many=True)

601

return self.get_paginated_response(serializer.data)

602

603

serializer = self.get_serializer(queryset, many=True)

604

return Response(serializer.data)

605

```

606

607

### Caching

608

609

```python { .api }

610

from django.core.cache import cache

611

from django.utils.decorators import method_decorator

612

from django.views.decorators.cache import cache_page

613

614

class CachedBookListView(generics.ListAPIView[Book]):

615

queryset = Book.objects.all()

616

serializer_class = BookSerializer

617

618

@method_decorator(cache_page(60 * 15)) # Cache for 15 minutes

619

def dispatch(self, *args: Any, **kwargs: Any) -> Response:

620

return super().dispatch(*args, **kwargs)

621

622

def list(self, request: Request, *args: Any, **kwargs: Any) -> Response:

623

cache_key = f"book_list_{request.GET.urlencode()}"

624

cached_response = cache.get(cache_key)

625

626

if cached_response is not None:

627

return Response(cached_response)

628

629

response = super().list(request, *args, **kwargs)

630

cache.set(cache_key, response.data, timeout=60 * 15)

631

return response

632

```

633

634

This comprehensive view and viewset system provides type-safe HTTP request handling with full generic type support, enabling confident API development with complete mypy integration.