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

routers-urls.mddocs/

0

# Routers & URLs

1

2

Django REST Framework routers provide automatic URL routing for ViewSets, enabling RESTful API endpoints with minimal configuration. The type stubs ensure type-safe URL pattern generation and ViewSet registration.

3

4

## Router Classes

5

6

### BaseRouter

7

8

```python { .api }

9

class BaseRouter:

10

"""Base class for all DRF routers."""

11

12

registry: list[tuple[str, type[ViewSetMixin], str]]

13

14

def __init__(self) -> None: ...

15

16

def register(

17

self,

18

prefix: str,

19

viewset: type[ViewSetMixin],

20

basename: str | None = None

21

) -> None:

22

"""

23

Register a viewset with the router.

24

25

Args:

26

prefix: URL prefix for the viewset routes

27

viewset: ViewSet class to register

28

basename: Base name for URL patterns (auto-generated if None)

29

"""

30

...

31

32

def get_urls(self) -> list[URLPattern]:

33

"""

34

Generate URL patterns for all registered viewsets.

35

36

Returns:

37

list[URLPattern]: Django URL patterns

38

"""

39

...

40

41

def get_api_root_view(self, api_urls: list[URLPattern] | None = None) -> Callable: ...

42

```

43

44

**Parameters:**

45

- `prefix: str` - URL prefix (e.g., 'books', 'api/v1/authors')

46

- `viewset: type[ViewSetMixin]` - ViewSet class to register

47

- `basename: str | None` - Base name for URL reversing (auto-detected if None)

48

49

### SimpleRouter

50

51

```python { .api }

52

class SimpleRouter(BaseRouter):

53

"""Simple router that generates standard RESTful routes."""

54

55

routes: list[Route]

56

57

def __init__(self, trailing_slash: bool = True) -> None: ...

58

59

def get_default_basename(self, viewset: type[ViewSetMixin]) -> str: ...

60

def get_lookup_regex(

61

self,

62

viewset: type[ViewSetMixin],

63

lookup_prefix: str = ''

64

) -> str: ...

65

```

66

67

**Parameters:**

68

- `trailing_slash: bool` - Add trailing slash to URLs (default: True)

69

70

### DefaultRouter

71

72

```python { .api }

73

class DefaultRouter(SimpleRouter):

74

"""Router that includes an API root view and browsable API."""

75

76

include_root_view: bool

77

include_format_suffixes: bool

78

root_view_name: str

79

80

def __init__(

81

self,

82

trailing_slash: bool = True,

83

include_root_view: bool = True,

84

include_format_suffixes: bool = True

85

) -> None: ...

86

87

def get_api_root_view(self, api_urls: list[URLPattern] | None = None) -> type[APIRootView]: ...

88

```

89

90

**Parameters:**

91

- `include_root_view: bool` - Include API root endpoint (default: True)

92

- `include_format_suffixes: bool` - Support format suffixes like .json (default: True)

93

- `root_view_name: str` - Name for the root view

94

95

## Route Configuration

96

97

### Route

98

99

```python { .api }

100

class Route(NamedTuple):

101

"""Configuration for a standard route."""

102

103

url: str

104

mapping: dict[str, str]

105

name: str

106

detail: bool

107

initkwargs: dict[str, Any]

108

```

109

110

**Fields:**

111

- `url: str` - URL pattern with format placeholders

112

- `mapping: dict[str, str]` - HTTP methods to ViewSet actions mapping

113

- `name: str` - URL name pattern

114

- `detail: bool` - Whether this is a detail route (requires object lookup)

115

- `initkwargs: dict[str, Any]` - Additional ViewSet initialization kwargs

116

117

### DynamicRoute

118

119

```python { .api }

120

class DynamicRoute(NamedTuple):

121

"""Configuration for custom @action routes."""

122

123

url: str

124

name: str

125

detail: bool

126

initkwargs: dict[str, Any]

127

```

128

129

### APIRootView

130

131

```python { .api }

132

class APIRootView(APIView):

133

"""Default API root view that lists available endpoints."""

134

135

_ignore_model_permissions: bool

136

schema: None

137

api_root_dict: dict[str, str] | None

138

139

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

140

```

141

142

## Router Usage Examples

143

144

### Basic Router Setup

145

146

```python { .api }

147

from rest_framework.routers import DefaultRouter

148

from rest_framework import viewsets

149

150

# Create router instance

151

router = DefaultRouter()

152

153

# Register viewsets

154

router.register(r'books', BookViewSet)

155

router.register(r'authors', AuthorViewSet)

156

router.register(r'categories', CategoryViewSet, basename='category')

157

158

# Get URL patterns

159

urlpatterns = router.urls

160

161

# Or include in main URLconf

162

from django.urls import path, include

163

164

urlpatterns = [

165

path('api/', include(router.urls)),

166

path('admin/', admin.site.urls),

167

]

168

```

169

170

### Generated URL Patterns

171

172

```python { .api }

173

# For BookViewSet registration, router generates:

174

# GET /books/ -> BookViewSet.list()

175

# POST /books/ -> BookViewSet.create()

176

# GET /books/{id}/ -> BookViewSet.retrieve()

177

# PUT /books/{id}/ -> BookViewSet.update()

178

# PATCH /books/{id}/ -> BookViewSet.partial_update()

179

# DELETE /books/{id}/ -> BookViewSet.destroy()

180

181

# URL names for reversing:

182

# book-list

183

# book-detail

184

```

185

186

### Custom Basename

187

188

```python { .api }

189

from django.contrib.auth.models import User

190

191

class UserViewSet(viewsets.ModelViewSet[User]):

192

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

193

# Custom queryset logic

194

return User.objects.filter(is_active=True)

195

196

# Register with custom basename since queryset is dynamic

197

router.register(r'users', UserViewSet, basename='user')

198

199

# Generated URL names: user-list, user-detail

200

```

201

202

### Multiple Router Configuration

203

204

```python { .api }

205

# API v1 router

206

v1_router = DefaultRouter()

207

v1_router.register(r'books', v1.BookViewSet)

208

v1_router.register(r'authors', v1.AuthorViewSet)

209

210

# API v2 router

211

v2_router = DefaultRouter()

212

v2_router.register(r'books', v2.BookViewSet)

213

v2_router.register(r'authors', v2.AuthorViewSet)

214

v2_router.register(r'reviews', v2.ReviewViewSet)

215

216

# URL configuration

217

urlpatterns = [

218

path('api/v1/', include((v1_router.urls, 'v1'), namespace='v1')),

219

path('api/v2/', include((v2_router.urls, 'v2'), namespace='v2')),

220

]

221

```

222

223

## ViewSet Actions & Routing

224

225

### Standard Actions

226

227

```python { .api }

228

class BookViewSet(viewsets.ModelViewSet[Book]):

229

"""Standard ModelViewSet with default actions."""

230

231

queryset = Book.objects.all()

232

serializer_class = BookSerializer

233

234

# These methods are automatically routed:

235

# list() -> GET /books/

236

# create() -> POST /books/

237

# retrieve() -> GET /books/{id}/

238

# update() -> PUT /books/{id}/

239

# partial_update() -> PATCH /books/{id}/

240

# destroy() -> DELETE /books/{id}/

241

```

242

243

### Custom Actions

244

245

```python { .api }

246

from rest_framework.decorators import action

247

from rest_framework.response import Response

248

249

class BookViewSet(viewsets.ModelViewSet[Book]):

250

queryset = Book.objects.all()

251

serializer_class = BookSerializer

252

253

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

254

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

255

"""List popular books - generates GET /books/popular/"""

256

popular_books = Book.objects.filter(rating__gte=4.0)

257

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

258

return Response(serializer.data)

259

260

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

261

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

262

"""Add/remove favorite - generates POST/DELETE /books/{id}/favorite/"""

263

book = self.get_object()

264

265

if request.method == 'POST':

266

request.user.favorite_books.add(book)

267

return Response({'status': 'favorited'})

268

else: # DELETE

269

request.user.favorite_books.remove(book)

270

return Response({'status': 'unfavorited'})

271

272

@action(

273

detail=False,

274

methods=['get'],

275

url_path='by-author/(?P<author_id>[^/.]+)',

276

url_name='by_author'

277

)

278

def books_by_author(self, request: Request, author_id: str | None = None) -> Response:

279

"""Custom URL pattern - generates GET /books/by-author/{author_id}/"""

280

books = Book.objects.filter(author_id=author_id)

281

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

282

return Response(serializer.data)

283

```

284

285

**Action Parameters:**

286

- `detail: bool` - Whether action requires object lookup (True) or collection-level (False)

287

- `methods: list[str]` - HTTP methods supported by the action

288

- `url_path: str` - Custom URL path segment (defaults to method name)

289

- `url_name: str` - Custom name for URL reversing (defaults to method name)

290

291

## URL Utilities

292

293

### Helper Functions

294

295

```python { .api }

296

def escape_curly_brackets(url_path: str) -> str:

297

"""Escape curly brackets in URL paths for regex compilation."""

298

...

299

300

def flatten(list_of_lists: Iterable[Iterable[Any]]) -> Iterable[Any]:

301

"""Flatten nested iterables."""

302

...

303

```

304

305

### URL Reversing

306

307

```python { .api }

308

from django.urls import reverse

309

310

# Reverse standard ViewSet URLs

311

book_list_url = reverse('book-list') # /api/books/

312

book_detail_url = reverse('book-detail', kwargs={'pk': 1}) # /api/books/1/

313

314

# Reverse custom action URLs

315

popular_books_url = reverse('book-popular') # /api/books/popular/

316

favorite_url = reverse('book-favorite', kwargs={'pk': 1}) # /api/books/1/favorite/

317

by_author_url = reverse('book-by-author', kwargs={'author_id': 5}) # /api/books/by-author/5/

318

319

# With namespaces

320

v1_books_url = reverse('v1:book-list')

321

v2_books_url = reverse('v2:book-list')

322

```

323

324

## Advanced Router Configuration

325

326

### Custom Route Patterns

327

328

```python { .api }

329

from rest_framework.routers import Route, DynamicRoute

330

331

class CustomRouter(SimpleRouter):

332

"""Router with custom route patterns."""

333

334

routes = [

335

# List route

336

Route(

337

url=r'^{prefix}/$',

338

mapping={'get': 'list', 'post': 'create'},

339

name='{basename}-list',

340

detail=False,

341

initkwargs={'suffix': 'List'}

342

),

343

# Detail route

344

Route(

345

url=r'^{prefix}/{lookup}/$',

346

mapping={

347

'get': 'retrieve',

348

'put': 'update',

349

'patch': 'partial_update',

350

'delete': 'destroy'

351

},

352

name='{basename}-detail',

353

detail=True,

354

initkwargs={'suffix': 'Instance'}

355

),

356

# Custom action route

357

DynamicRoute(

358

url=r'^{prefix}/{lookup}/{url_path}/$',

359

name='{basename}-{url_name}',

360

detail=True,

361

initkwargs={}

362

),

363

]

364

```

365

366

### Nested Routes

367

368

```python { .api }

369

from rest_framework_nested import routers

370

371

# Parent router

372

router = routers.SimpleRouter()

373

router.register(r'authors', AuthorViewSet, basename='author')

374

375

# Nested router

376

authors_router = routers.NestedSimpleRouter(router, r'authors', lookup='author')

377

authors_router.register(r'books', BookViewSet, basename='author-books')

378

379

# Generated URLs:

380

# /authors/ -> AuthorViewSet.list()

381

# /authors/{id}/ -> AuthorViewSet.retrieve()

382

# /authors/{id}/books/ -> BookViewSet.list() (filtered by author)

383

# /authors/{id}/books/{id}/ -> BookViewSet.retrieve()

384

```

385

386

### Router with Custom Lookup

387

388

```python { .api }

389

class BookViewSet(viewsets.ModelViewSet[Book]):

390

"""ViewSet with custom lookup field."""

391

392

queryset = Book.objects.all()

393

serializer_class = BookSerializer

394

lookup_field = 'isbn' # Use ISBN instead of primary key

395

lookup_url_kwarg = 'isbn'

396

397

# Router generates URLs like: /books/978-0123456789/

398

router.register(r'books', BookViewSet)

399

```

400

401

### Conditional Registration

402

403

```python { .api }

404

from django.conf import settings

405

406

router = DefaultRouter()

407

408

# Always register these

409

router.register(r'books', BookViewSet)

410

router.register(r'authors', AuthorViewSet)

411

412

# Conditionally register admin endpoints

413

if settings.DEBUG or settings.ENABLE_ADMIN_API:

414

router.register(r'admin/users', AdminUserViewSet, basename='admin-user')

415

router.register(r'admin/stats', AdminStatsViewSet, basename='admin-stats')

416

```

417

418

## Testing Router URLs

419

420

### URL Pattern Testing

421

422

```python { .api }

423

from django.test import TestCase

424

from django.urls import reverse

425

from rest_framework.test import APITestCase

426

427

class RouterURLTests(APITestCase):

428

"""Test router-generated URL patterns."""

429

430

def test_book_list_url(self) -> None:

431

"""Test book list URL resolution."""

432

url = reverse('book-list')

433

self.assertEqual(url, '/api/books/')

434

435

def test_book_detail_url(self) -> None:

436

"""Test book detail URL resolution."""

437

url = reverse('book-detail', kwargs={'pk': 1})

438

self.assertEqual(url, '/api/books/1/')

439

440

def test_custom_action_url(self) -> None:

441

"""Test custom action URL resolution."""

442

url = reverse('book-popular')

443

self.assertEqual(url, '/api/books/popular/')

444

445

def test_nested_action_url(self) -> None:

446

"""Test nested action URL resolution."""

447

url = reverse('book-favorite', kwargs={'pk': 1})

448

self.assertEqual(url, '/api/books/1/favorite/')

449

```

450

451

### ViewSet Action Testing

452

453

```python { .api }

454

class BookViewSetTests(APITestCase):

455

"""Test ViewSet actions through router URLs."""

456

457

def setUp(self) -> None:

458

self.book = Book.objects.create(title='Test Book', author='Test Author')

459

460

def test_list_action(self) -> None:

461

"""Test list action via router URL."""

462

url = reverse('book-list')

463

response = self.client.get(url)

464

self.assertEqual(response.status_code, 200)

465

466

def test_detail_action(self) -> None:

467

"""Test retrieve action via router URL."""

468

url = reverse('book-detail', kwargs={'pk': self.book.pk})

469

response = self.client.get(url)

470

self.assertEqual(response.status_code, 200)

471

472

def test_custom_action(self) -> None:

473

"""Test custom action via router URL."""

474

url = reverse('book-popular')

475

response = self.client.get(url)

476

self.assertEqual(response.status_code, 200)

477

```

478

479

## Performance Considerations

480

481

### Route Optimization

482

483

```python { .api }

484

# Use SimpleRouter for better performance when root view not needed

485

router = SimpleRouter(trailing_slash=False) # Also saves URL matching

486

487

# Minimize registered viewsets

488

router.register(r'books', BookViewSet)

489

# Don't register unused viewsets

490

491

# Use basename when queryset is dynamic to avoid inspection

492

router.register(r'user-books', UserBookViewSet, basename='user-book')

493

```

494

495

### URL Caching

496

497

```python { .api }

498

from django.core.cache import cache

499

500

class CachedRouter(DefaultRouter):

501

"""Router with URL caching."""

502

503

def get_urls(self) -> list[URLPattern]:

504

cache_key = f"router_urls_{len(self.registry)}"

505

urls = cache.get(cache_key)

506

507

if urls is None:

508

urls = super().get_urls()

509

cache.set(cache_key, urls, timeout=3600) # Cache for 1 hour

510

511

return urls

512

```

513

514

This comprehensive routing system provides type-safe URL pattern generation and ViewSet registration, enabling confident API endpoint configuration with full mypy integration and automatic RESTful URL structure.