|
21 | 21 | from django.urls import NoReverseMatch
|
22 | 22 |
|
23 | 23 | from rest_framework import views
|
| 24 | +from rest_framework.compat import path |
24 | 25 | from rest_framework.response import Response
|
25 | 26 | from rest_framework.reverse import reverse
|
26 | 27 | from rest_framework.schemas import SchemaGenerator
|
@@ -79,50 +80,10 @@ def urls(self):
|
79 | 80 | return self._urls
|
80 | 81 |
|
81 | 82 |
|
82 |
| -class SimpleRouter(BaseRouter): |
83 |
| - |
84 |
| - routes = [ |
85 |
| - # List route. |
86 |
| - Route( |
87 |
| - url=r'^{prefix}{trailing_slash}$', |
88 |
| - mapping={ |
89 |
| - 'get': 'list', |
90 |
| - 'post': 'create' |
91 |
| - }, |
92 |
| - name='{basename}-list', |
93 |
| - detail=False, |
94 |
| - initkwargs={'suffix': 'List'} |
95 |
| - ), |
96 |
| - # Dynamically generated list routes. Generated using |
97 |
| - # @action(detail=False) decorator on methods of the viewset. |
98 |
| - DynamicRoute( |
99 |
| - url=r'^{prefix}/{url_path}{trailing_slash}$', |
100 |
| - name='{basename}-{url_name}', |
101 |
| - detail=False, |
102 |
| - initkwargs={} |
103 |
| - ), |
104 |
| - # Detail route. |
105 |
| - Route( |
106 |
| - url=r'^{prefix}/{lookup}{trailing_slash}$', |
107 |
| - mapping={ |
108 |
| - 'get': 'retrieve', |
109 |
| - 'put': 'update', |
110 |
| - 'patch': 'partial_update', |
111 |
| - 'delete': 'destroy' |
112 |
| - }, |
113 |
| - name='{basename}-detail', |
114 |
| - detail=True, |
115 |
| - initkwargs={'suffix': 'Instance'} |
116 |
| - ), |
117 |
| - # Dynamically generated detail routes. Generated using |
118 |
| - # @action(detail=True) decorator on methods of the viewset. |
119 |
| - DynamicRoute( |
120 |
| - url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$', |
121 |
| - name='{basename}-{url_name}', |
122 |
| - detail=True, |
123 |
| - initkwargs={} |
124 |
| - ), |
125 |
| - ] |
| 83 | +class AbstractSimpleRouter(BaseRouter): |
| 84 | + """ |
| 85 | + Base class for SimpleRouter and SimplePathRouter. |
| 86 | + """ |
126 | 87 |
|
127 | 88 | def __init__(self, trailing_slash=True):
|
128 | 89 | self.trailing_slash = '/' if trailing_slash else ''
|
@@ -203,6 +164,52 @@ def get_method_map(self, viewset, method_map):
|
203 | 164 | bound_methods[method] = action
|
204 | 165 | return bound_methods
|
205 | 166 |
|
| 167 | + |
| 168 | +class SimpleRouter(AbstractSimpleRouter): |
| 169 | + |
| 170 | + routes = [ |
| 171 | + # List route. |
| 172 | + Route( |
| 173 | + url=r'^{prefix}{trailing_slash}$', |
| 174 | + mapping={ |
| 175 | + 'get': 'list', |
| 176 | + 'post': 'create' |
| 177 | + }, |
| 178 | + name='{basename}-list', |
| 179 | + detail=False, |
| 180 | + initkwargs={'suffix': 'List'} |
| 181 | + ), |
| 182 | + # Dynamically generated list routes. Generated using |
| 183 | + # @action(detail=False) decorator on methods of the viewset. |
| 184 | + DynamicRoute( |
| 185 | + url=r'^{prefix}/{url_path}{trailing_slash}$', |
| 186 | + name='{basename}-{url_name}', |
| 187 | + detail=False, |
| 188 | + initkwargs={} |
| 189 | + ), |
| 190 | + # Detail route. |
| 191 | + Route( |
| 192 | + url=r'^{prefix}/{lookup}{trailing_slash}$', |
| 193 | + mapping={ |
| 194 | + 'get': 'retrieve', |
| 195 | + 'put': 'update', |
| 196 | + 'patch': 'partial_update', |
| 197 | + 'delete': 'destroy' |
| 198 | + }, |
| 199 | + name='{basename}-detail', |
| 200 | + detail=True, |
| 201 | + initkwargs={'suffix': 'Instance'} |
| 202 | + ), |
| 203 | + # Dynamically generated detail routes. Generated using |
| 204 | + # @action(detail=True) decorator on methods of the viewset. |
| 205 | + DynamicRoute( |
| 206 | + url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$', |
| 207 | + name='{basename}-{url_name}', |
| 208 | + detail=True, |
| 209 | + initkwargs={} |
| 210 | + ), |
| 211 | + ] |
| 212 | + |
206 | 213 | def get_lookup_regex(self, viewset, lookup_prefix=''):
|
207 | 214 | """
|
208 | 215 | Given a viewset, return the portion of URL regex that is used
|
@@ -270,6 +277,122 @@ def get_urls(self):
|
270 | 277 | return ret
|
271 | 278 |
|
272 | 279 |
|
| 280 | +class SimplePathRouter(AbstractSimpleRouter): |
| 281 | + """ |
| 282 | + Router which uses Django 2.x path to build urls |
| 283 | + """ |
| 284 | + |
| 285 | + routes = [ |
| 286 | + # List route. |
| 287 | + Route( |
| 288 | + url='{prefix}{trailing_slash}', |
| 289 | + mapping={ |
| 290 | + 'get': 'list', |
| 291 | + 'post': 'create' |
| 292 | + }, |
| 293 | + name='{basename}-list', |
| 294 | + detail=False, |
| 295 | + initkwargs={'suffix': 'List'} |
| 296 | + ), |
| 297 | + # Dynamically generated list routes. Generated using |
| 298 | + # @action(detail=False) decorator on methods of the viewset. |
| 299 | + DynamicRoute( |
| 300 | + url='{prefix}/{url_path}{trailing_slash}', |
| 301 | + name='{basename}-{url_name}', |
| 302 | + detail=False, |
| 303 | + initkwargs={} |
| 304 | + ), |
| 305 | + # Detail route. |
| 306 | + Route( |
| 307 | + url='{prefix}/{lookup}{trailing_slash}', |
| 308 | + mapping={ |
| 309 | + 'get': 'retrieve', |
| 310 | + 'put': 'update', |
| 311 | + 'patch': 'partial_update', |
| 312 | + 'delete': 'destroy' |
| 313 | + }, |
| 314 | + name='{basename}-detail', |
| 315 | + detail=True, |
| 316 | + initkwargs={'suffix': 'Instance'} |
| 317 | + ), |
| 318 | + # Dynamically generated detail routes. Generated using |
| 319 | + # @action(detail=True) decorator on methods of the viewset. |
| 320 | + DynamicRoute( |
| 321 | + url='{prefix}/{lookup}/{url_path}{trailing_slash}', |
| 322 | + name='{basename}-{url_name}', |
| 323 | + detail=True, |
| 324 | + initkwargs={} |
| 325 | + ), |
| 326 | + ] |
| 327 | + |
| 328 | + def get_lookup_path(self, viewset, lookup_prefix=''): |
| 329 | + """ |
| 330 | + Given a viewset, return the portion of URL path that is used |
| 331 | + to match against a single instance. |
| 332 | +
|
| 333 | + Note that lookup_prefix is not used directly inside REST rest_framework |
| 334 | + itself, but is required in order to nicely support nested router |
| 335 | + implementations, such as drf-nested-routers. |
| 336 | +
|
| 337 | + https://github.com/alanjds/drf-nested-routers |
| 338 | + """ |
| 339 | + base_converter = '<{lookup_converter}:{lookup_prefix}{lookup_url_kwarg}>' |
| 340 | + # Use `pk` as default field, unset set. Default regex should not |
| 341 | + # consume `.json` style suffixes and should break at '/' boundaries. |
| 342 | + lookup_field = getattr(viewset, 'lookup_field', 'pk') |
| 343 | + lookup_url_kwarg = getattr(viewset, 'lookup_url_kwarg', None) or lookup_field |
| 344 | + lookup_converter = getattr(viewset, 'lookup_converter', 'path') |
| 345 | + return base_converter.format( |
| 346 | + lookup_prefix=lookup_prefix, |
| 347 | + lookup_url_kwarg=lookup_url_kwarg, |
| 348 | + lookup_converter=lookup_converter |
| 349 | + ) |
| 350 | + |
| 351 | + def get_urls(self): |
| 352 | + """ |
| 353 | + Use the registered viewsets to generate a list of URL patterns. |
| 354 | + """ |
| 355 | + assert path is not None, 'SimplePathRouter requires Django 2.x path' |
| 356 | + ret = [] |
| 357 | + |
| 358 | + for prefix, viewset, basename in self.registry: |
| 359 | + lookup = self.get_lookup_path(viewset) |
| 360 | + routes = self.get_routes(viewset) |
| 361 | + |
| 362 | + for route in routes: |
| 363 | + |
| 364 | + # Only actions which actually exist on the viewset will be bound |
| 365 | + mapping = self.get_method_map(viewset, route.mapping) |
| 366 | + if not mapping: |
| 367 | + continue |
| 368 | + |
| 369 | + # Build the url pattern |
| 370 | + url_path = route.url.format( |
| 371 | + prefix=prefix, |
| 372 | + lookup=lookup, |
| 373 | + trailing_slash=self.trailing_slash |
| 374 | + ) |
| 375 | + |
| 376 | + # If there is no prefix, the first part of the url is probably |
| 377 | + # controlled by project's urls.py and the router is in an app, |
| 378 | + # so a slash in the beginning will (A) cause Django to give |
| 379 | + # warnings and (B) generate URLS that will require using '//'. |
| 380 | + if not prefix and url_path[0] == '/': |
| 381 | + url_path = url_path[1:] |
| 382 | + |
| 383 | + initkwargs = route.initkwargs.copy() |
| 384 | + initkwargs.update({ |
| 385 | + 'basename': basename, |
| 386 | + 'detail': route.detail, |
| 387 | + }) |
| 388 | + |
| 389 | + view = viewset.as_view(mapping, **initkwargs) |
| 390 | + name = route.name.format(basename=basename) |
| 391 | + ret.append(path(url_path, view, name=name)) |
| 392 | + |
| 393 | + return ret |
| 394 | + |
| 395 | + |
273 | 396 | class APIRootView(views.APIView):
|
274 | 397 | """
|
275 | 398 | The default basic root view for DefaultRouter
|
|
0 commit comments