|
23 | 23 | from django.utils.deprecation import RenameMethodsBase
|
24 | 24 |
|
25 | 25 | from rest_framework import RemovedInDRF311Warning, views
|
| 26 | +from rest_framework.compat import path |
26 | 27 | from rest_framework.response import Response
|
27 | 28 | from rest_framework.reverse import reverse
|
28 | 29 | from rest_framework.schemas import SchemaGenerator
|
@@ -99,50 +100,10 @@ def urls(self):
|
99 | 100 | return self._urls
|
100 | 101 |
|
101 | 102 |
|
102 |
| -class SimpleRouter(BaseRouter): |
103 |
| - |
104 |
| - routes = [ |
105 |
| - # List route. |
106 |
| - Route( |
107 |
| - url=r'^{prefix}{trailing_slash}$', |
108 |
| - mapping={ |
109 |
| - 'get': 'list', |
110 |
| - 'post': 'create' |
111 |
| - }, |
112 |
| - name='{basename}-list', |
113 |
| - detail=False, |
114 |
| - initkwargs={'suffix': 'List'} |
115 |
| - ), |
116 |
| - # Dynamically generated list routes. Generated using |
117 |
| - # @action(detail=False) decorator on methods of the viewset. |
118 |
| - DynamicRoute( |
119 |
| - url=r'^{prefix}/{url_path}{trailing_slash}$', |
120 |
| - name='{basename}-{url_name}', |
121 |
| - detail=False, |
122 |
| - initkwargs={} |
123 |
| - ), |
124 |
| - # Detail route. |
125 |
| - Route( |
126 |
| - url=r'^{prefix}/{lookup}{trailing_slash}$', |
127 |
| - mapping={ |
128 |
| - 'get': 'retrieve', |
129 |
| - 'put': 'update', |
130 |
| - 'patch': 'partial_update', |
131 |
| - 'delete': 'destroy' |
132 |
| - }, |
133 |
| - name='{basename}-detail', |
134 |
| - detail=True, |
135 |
| - initkwargs={'suffix': 'Instance'} |
136 |
| - ), |
137 |
| - # Dynamically generated detail routes. Generated using |
138 |
| - # @action(detail=True) decorator on methods of the viewset. |
139 |
| - DynamicRoute( |
140 |
| - url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$', |
141 |
| - name='{basename}-{url_name}', |
142 |
| - detail=True, |
143 |
| - initkwargs={} |
144 |
| - ), |
145 |
| - ] |
| 103 | +class AbstractSimpleRouter(BaseRouter): |
| 104 | + """ |
| 105 | + Base class for SimpleRouter and SimplePathRouter. |
| 106 | + """ |
146 | 107 |
|
147 | 108 | def __init__(self, trailing_slash=True):
|
148 | 109 | self.trailing_slash = '/' if trailing_slash else ''
|
@@ -223,6 +184,52 @@ def get_method_map(self, viewset, method_map):
|
223 | 184 | bound_methods[method] = action
|
224 | 185 | return bound_methods
|
225 | 186 |
|
| 187 | + |
| 188 | +class SimpleRouter(AbstractSimpleRouter): |
| 189 | + |
| 190 | + routes = [ |
| 191 | + # List route. |
| 192 | + Route( |
| 193 | + url=r'^{prefix}{trailing_slash}$', |
| 194 | + mapping={ |
| 195 | + 'get': 'list', |
| 196 | + 'post': 'create' |
| 197 | + }, |
| 198 | + name='{basename}-list', |
| 199 | + detail=False, |
| 200 | + initkwargs={'suffix': 'List'} |
| 201 | + ), |
| 202 | + # Dynamically generated list routes. Generated using |
| 203 | + # @action(detail=False) decorator on methods of the viewset. |
| 204 | + DynamicRoute( |
| 205 | + url=r'^{prefix}/{url_path}{trailing_slash}$', |
| 206 | + name='{basename}-{url_name}', |
| 207 | + detail=False, |
| 208 | + initkwargs={} |
| 209 | + ), |
| 210 | + # Detail route. |
| 211 | + Route( |
| 212 | + url=r'^{prefix}/{lookup}{trailing_slash}$', |
| 213 | + mapping={ |
| 214 | + 'get': 'retrieve', |
| 215 | + 'put': 'update', |
| 216 | + 'patch': 'partial_update', |
| 217 | + 'delete': 'destroy' |
| 218 | + }, |
| 219 | + name='{basename}-detail', |
| 220 | + detail=True, |
| 221 | + initkwargs={'suffix': 'Instance'} |
| 222 | + ), |
| 223 | + # Dynamically generated detail routes. Generated using |
| 224 | + # @action(detail=True) decorator on methods of the viewset. |
| 225 | + DynamicRoute( |
| 226 | + url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$', |
| 227 | + name='{basename}-{url_name}', |
| 228 | + detail=True, |
| 229 | + initkwargs={} |
| 230 | + ), |
| 231 | + ] |
| 232 | + |
226 | 233 | def get_lookup_regex(self, viewset, lookup_prefix=''):
|
227 | 234 | """
|
228 | 235 | Given a viewset, return the portion of URL regex that is used
|
@@ -290,6 +297,124 @@ def get_urls(self):
|
290 | 297 | return ret
|
291 | 298 |
|
292 | 299 |
|
| 300 | +if path is not None: |
| 301 | + class SimplePathRouter(AbstractSimpleRouter): |
| 302 | + """ |
| 303 | + Router which uses Django 2.x path to build urls |
| 304 | + """ |
| 305 | + |
| 306 | + routes = [ |
| 307 | + # List route. |
| 308 | + Route( |
| 309 | + url='{prefix}{trailing_slash}', |
| 310 | + mapping={ |
| 311 | + 'get': 'list', |
| 312 | + 'post': 'create' |
| 313 | + }, |
| 314 | + name='{basename}-list', |
| 315 | + detail=False, |
| 316 | + initkwargs={'suffix': 'List'} |
| 317 | + ), |
| 318 | + # Dynamically generated list routes. Generated using |
| 319 | + # @action(detail=False) decorator on methods of the viewset. |
| 320 | + DynamicRoute( |
| 321 | + url='{prefix}/{url_path}{trailing_slash}', |
| 322 | + name='{basename}-{url_name}', |
| 323 | + detail=False, |
| 324 | + initkwargs={} |
| 325 | + ), |
| 326 | + # Detail route. |
| 327 | + Route( |
| 328 | + url='{prefix}/{lookup}{trailing_slash}', |
| 329 | + mapping={ |
| 330 | + 'get': 'retrieve', |
| 331 | + 'put': 'update', |
| 332 | + 'patch': 'partial_update', |
| 333 | + 'delete': 'destroy' |
| 334 | + }, |
| 335 | + name='{basename}-detail', |
| 336 | + detail=True, |
| 337 | + initkwargs={'suffix': 'Instance'} |
| 338 | + ), |
| 339 | + # Dynamically generated detail routes. Generated using |
| 340 | + # @action(detail=True) decorator on methods of the viewset. |
| 341 | + DynamicRoute( |
| 342 | + url='{prefix}/{lookup}/{url_path}{trailing_slash}', |
| 343 | + name='{basename}-{url_name}', |
| 344 | + detail=True, |
| 345 | + initkwargs={} |
| 346 | + ), |
| 347 | + ] |
| 348 | + |
| 349 | + def get_lookup_path(self, viewset, lookup_prefix=''): |
| 350 | + """ |
| 351 | + Given a viewset, return the portion of URL path that is used |
| 352 | + to match against a single instance. |
| 353 | +
|
| 354 | + Note that lookup_prefix is not used directly inside REST rest_framework |
| 355 | + itself, but is required in order to nicely support nested router |
| 356 | + implementations, such as drf-nested-routers. |
| 357 | +
|
| 358 | + https://github.com/alanjds/drf-nested-routers |
| 359 | + """ |
| 360 | + base_regex = '<{lookup_converter}:{lookup_prefix}{lookup_url_kwarg}>' |
| 361 | + # Use `pk` as default field, unset set. Default regex should not |
| 362 | + # consume `.json` style suffixes and should break at '/' boundaries. |
| 363 | + lookup_field = getattr(viewset, 'lookup_field', 'pk') |
| 364 | + lookup_url_kwarg = getattr(viewset, 'lookup_url_kwarg', None) or lookup_field |
| 365 | + lookup_converter = getattr(viewset, 'lookup_converter', 'path') |
| 366 | + return base_regex.format( |
| 367 | + lookup_prefix=lookup_prefix, |
| 368 | + lookup_url_kwarg=lookup_url_kwarg, |
| 369 | + lookup_converter=lookup_converter |
| 370 | + ) |
| 371 | + |
| 372 | + def get_urls(self): |
| 373 | + """ |
| 374 | + Use the registered viewsets to generate a list of URL patterns. |
| 375 | + """ |
| 376 | + ret = [] |
| 377 | + |
| 378 | + for prefix, viewset, basename in self.registry: |
| 379 | + lookup = self.get_lookup_path(viewset) |
| 380 | + routes = self.get_routes(viewset) |
| 381 | + |
| 382 | + for route in routes: |
| 383 | + |
| 384 | + # Only actions which actually exist on the viewset will be bound |
| 385 | + mapping = self.get_method_map(viewset, route.mapping) |
| 386 | + if not mapping: |
| 387 | + continue |
| 388 | + |
| 389 | + # Build the url pattern |
| 390 | + url_path = route.url.format( |
| 391 | + prefix=prefix, |
| 392 | + lookup=lookup, |
| 393 | + trailing_slash=self.trailing_slash |
| 394 | + ) |
| 395 | + |
| 396 | + # If there is no prefix, the first part of the url is probably |
| 397 | + # controlled by project's urls.py and the router is in an app, |
| 398 | + # so a slash in the beginning will (A) cause Django to give |
| 399 | + # warnings and (B) generate URLS that will require using '//'. |
| 400 | + if not prefix and url_path[0] == '/': |
| 401 | + url_path = url_path[1:] |
| 402 | + |
| 403 | + initkwargs = route.initkwargs.copy() |
| 404 | + initkwargs.update({ |
| 405 | + 'basename': basename, |
| 406 | + 'detail': route.detail, |
| 407 | + }) |
| 408 | + |
| 409 | + view = viewset.as_view(mapping, **initkwargs) |
| 410 | + name = route.name.format(basename=basename) |
| 411 | + ret.append(path(url_path, view, name=name)) |
| 412 | + |
| 413 | + return ret |
| 414 | +else: |
| 415 | + SimplePathRouter = None |
| 416 | + |
| 417 | + |
293 | 418 | class APIRootView(views.APIView):
|
294 | 419 | """
|
295 | 420 | The default basic root view for DefaultRouter
|
|
0 commit comments