|
3 | 3 |
|
4 | 4 | from django.http.response import Http404
|
5 | 5 | from django.conf import settings
|
6 |
| -from django.core.exceptions import MiddlewareNotUsed |
| 6 | +from django.core import exceptions as django_exceptions |
| 7 | + |
| 8 | +from rest_framework.versioning import BaseVersioning |
7 | 9 |
|
8 | 10 | from pulpcore.metrics import init_otel_meter
|
9 | 11 | from pulpcore.app.models import Domain
|
@@ -69,7 +71,7 @@ class APIRootRewriteMiddleware:
|
69 | 71 |
|
70 | 72 | def __init__(self, get_response):
|
71 | 73 | if not settings.API_ROOT_REWRITE_HEADER:
|
72 |
| - raise MiddlewareNotUsed() |
| 74 | + raise django_exceptions.MiddlewareNotUsed() |
73 | 75 | self.get_response = get_response
|
74 | 76 |
|
75 | 77 | def __call__(self, request):
|
@@ -168,3 +170,51 @@ def __call__(self, request):
|
168 | 170 | x_task_diagnostics_var.reset(ctx_token)
|
169 | 171 | else:
|
170 | 172 | return self.get_response(request)
|
| 173 | + |
| 174 | + |
| 175 | +class NamespaceVersioning(BaseVersioning): |
| 176 | + """ |
| 177 | + To the client this is the same style as `URLPathVersioning`. |
| 178 | + The difference is in the backend - this implementation uses |
| 179 | + Django's URL namespaces to determine the version. |
| 180 | +
|
| 181 | + An example URL conf that is namespaced into two separate versions |
| 182 | +
|
| 183 | + # users/urls.py |
| 184 | + urlpatterns = [ |
| 185 | + path('/users/', users_list, name='users-list'), |
| 186 | + path('/users/<int:pk>/', users_detail, name='users-detail') |
| 187 | + ] |
| 188 | +
|
| 189 | + # urls.py |
| 190 | + urlpatterns = [ |
| 191 | + path('v1/', include('users.urls', namespace='v1')), |
| 192 | + path('v2/', include('users.urls', namespace='v2')) |
| 193 | + ] |
| 194 | +
|
| 195 | + GET /1.0/something/ HTTP/1.1 |
| 196 | + Host: example.com |
| 197 | + Accept: application/json |
| 198 | + """ |
| 199 | + |
| 200 | + invalid_version_message = "Invalid version in URL path. Does not match any version namespace." |
| 201 | + |
| 202 | + def determine_version(self, request, *args, **kwargs): |
| 203 | + resolver_match = getattr(request, "resolver_match", None) |
| 204 | + if resolver_match is None or not resolver_match.namespace: |
| 205 | + return self.default_version |
| 206 | + |
| 207 | + # Allow for possibly nested namespaces. |
| 208 | + possible_versions = resolver_match.namespace.split(":") |
| 209 | + for version in possible_versions: |
| 210 | + if self.is_allowed_version(version): |
| 211 | + return version |
| 212 | + raise django_exceptions.NotFound(self.invalid_version_message) |
| 213 | + |
| 214 | + def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): |
| 215 | + if request.version is not None: |
| 216 | + viewname = self.get_versioned_viewname(viewname, request) |
| 217 | + return super().reverse(viewname, args, kwargs, request, format, **extra) |
| 218 | + |
| 219 | + def get_versioned_viewname(self, viewname, request): |
| 220 | + return request.version + ":" + viewname |
0 commit comments