|
6 | 6 |
|
7 | 7 | from django.http.response import Http404
|
8 | 8 | from django.conf import settings
|
9 |
| -from django.core.exceptions import MiddlewareNotUsed |
| 9 | +from django.core import exceptions as django_exceptions |
| 10 | + |
| 11 | +from rest_framework.versioning import BaseVersioning |
10 | 12 |
|
11 | 13 | from pulpcore.metrics import init_otel_meter
|
12 | 14 | from pulpcore.app.models import Domain
|
@@ -72,7 +74,7 @@ class APIRootRewriteMiddleware:
|
72 | 74 |
|
73 | 75 | def __init__(self, get_response):
|
74 | 76 | if not settings.API_ROOT_REWRITE_HEADER:
|
75 |
| - raise MiddlewareNotUsed() |
| 77 | + raise django_exceptions.MiddlewareNotUsed() |
76 | 78 | self.get_response = get_response
|
77 | 79 |
|
78 | 80 | def __call__(self, request):
|
@@ -195,3 +197,51 @@ def __call__(self, request):
|
195 | 197 | x_task_diagnostics_var.reset(ctx_token)
|
196 | 198 | else:
|
197 | 199 | return self.get_response(request)
|
| 200 | + |
| 201 | + |
| 202 | +class NamespaceVersioning(BaseVersioning): |
| 203 | + """ |
| 204 | + To the client this is the same style as `URLPathVersioning`. |
| 205 | + The difference is in the backend - this implementation uses |
| 206 | + Django's URL namespaces to determine the version. |
| 207 | +
|
| 208 | + An example URL conf that is namespaced into two separate versions |
| 209 | +
|
| 210 | + # users/urls.py |
| 211 | + urlpatterns = [ |
| 212 | + path('/users/', users_list, name='users-list'), |
| 213 | + path('/users/<int:pk>/', users_detail, name='users-detail') |
| 214 | + ] |
| 215 | +
|
| 216 | + # urls.py |
| 217 | + urlpatterns = [ |
| 218 | + path('v1/', include('users.urls', namespace='v1')), |
| 219 | + path('v2/', include('users.urls', namespace='v2')) |
| 220 | + ] |
| 221 | +
|
| 222 | + GET /1.0/something/ HTTP/1.1 |
| 223 | + Host: example.com |
| 224 | + Accept: application/json |
| 225 | + """ |
| 226 | + |
| 227 | + invalid_version_message = "Invalid version in URL path. Does not match any version namespace." |
| 228 | + |
| 229 | + def determine_version(self, request, *args, **kwargs): |
| 230 | + resolver_match = getattr(request, "resolver_match", None) |
| 231 | + if resolver_match is None or not resolver_match.namespace: |
| 232 | + return self.default_version |
| 233 | + |
| 234 | + # Allow for possibly nested namespaces. |
| 235 | + possible_versions = resolver_match.namespace.split(":") |
| 236 | + for version in possible_versions: |
| 237 | + if self.is_allowed_version(version): |
| 238 | + return version |
| 239 | + raise django_exceptions.NotFound(self.invalid_version_message) |
| 240 | + |
| 241 | + def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): |
| 242 | + if request.version is not None: |
| 243 | + viewname = self.get_versioned_viewname(viewname, request) |
| 244 | + return super().reverse(viewname, args, kwargs, request, format, **extra) |
| 245 | + |
| 246 | + def get_versioned_viewname(self, viewname, request): |
| 247 | + return request.version + ":" + viewname |
0 commit comments