Skip to content

Commit 71f87a5

Browse files
authored
Fix NamespaceVersioning ignoring DEFAULT_VERSION on non-None namespaces (#7278)
* Fix the case where if the namespace is not None and there's no match, NamespaceVersioning always raises NotFound even if DEFAULT_VERSION is set or None is in ALLOWED_VERSIONS * Add test cases
1 parent aed7761 commit 71f87a5

File tree

2 files changed

+102
-10
lines changed

2 files changed

+102
-10
lines changed

rest_framework/versioning.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -119,15 +119,16 @@ class NamespaceVersioning(BaseVersioning):
119119

120120
def determine_version(self, request, *args, **kwargs):
121121
resolver_match = getattr(request, 'resolver_match', None)
122-
if resolver_match is None or not resolver_match.namespace:
123-
return self.default_version
124-
125-
# Allow for possibly nested namespaces.
126-
possible_versions = resolver_match.namespace.split(':')
127-
for version in possible_versions:
128-
if self.is_allowed_version(version):
129-
return version
130-
raise exceptions.NotFound(self.invalid_version_message)
122+
if resolver_match is not None and resolver_match.namespace:
123+
# Allow for possibly nested namespaces.
124+
possible_versions = resolver_match.namespace.split(':')
125+
for version in possible_versions:
126+
if self.is_allowed_version(version):
127+
return version
128+
129+
if not self.is_allowed_version(self.default_version):
130+
raise exceptions.NotFound(self.invalid_version_message)
131+
return self.default_version
131132

132133
def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
133134
if request.version is not None:

tests/test_versioning.py

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ class FakeResolverMatch(ResolverMatch):
272272
assert response.status_code == status.HTTP_404_NOT_FOUND
273273

274274

275-
class TestAllowedAndDefaultVersion:
275+
class TestAcceptHeaderAllowedAndDefaultVersion:
276276
def test_missing_without_default(self):
277277
scheme = versioning.AcceptHeaderVersioning
278278
view = AllowedVersionsView.as_view(versioning_class=scheme)
@@ -318,6 +318,97 @@ def test_missing_with_default_and_none_allowed(self):
318318
assert response.data == {'version': 'v2'}
319319

320320

321+
class TestNamespaceAllowedAndDefaultVersion:
322+
def test_no_namespace_without_default(self):
323+
class FakeResolverMatch:
324+
namespace = None
325+
326+
scheme = versioning.NamespaceVersioning
327+
view = AllowedVersionsView.as_view(versioning_class=scheme)
328+
329+
request = factory.get('/endpoint/')
330+
request.resolver_match = FakeResolverMatch
331+
response = view(request)
332+
assert response.status_code == status.HTTP_404_NOT_FOUND
333+
334+
def test_no_namespace_with_default(self):
335+
class FakeResolverMatch:
336+
namespace = None
337+
338+
scheme = versioning.NamespaceVersioning
339+
view = AllowedAndDefaultVersionsView.as_view(versioning_class=scheme)
340+
341+
request = factory.get('/endpoint/')
342+
request.resolver_match = FakeResolverMatch
343+
response = view(request)
344+
assert response.status_code == status.HTTP_200_OK
345+
assert response.data == {'version': 'v2'}
346+
347+
def test_no_match_without_default(self):
348+
class FakeResolverMatch:
349+
namespace = 'no_match'
350+
351+
scheme = versioning.NamespaceVersioning
352+
view = AllowedVersionsView.as_view(versioning_class=scheme)
353+
354+
request = factory.get('/endpoint/')
355+
request.resolver_match = FakeResolverMatch
356+
response = view(request)
357+
assert response.status_code == status.HTTP_404_NOT_FOUND
358+
359+
def test_no_match_with_default(self):
360+
class FakeResolverMatch:
361+
namespace = 'no_match'
362+
363+
scheme = versioning.NamespaceVersioning
364+
view = AllowedAndDefaultVersionsView.as_view(versioning_class=scheme)
365+
366+
request = factory.get('/endpoint/')
367+
request.resolver_match = FakeResolverMatch
368+
response = view(request)
369+
assert response.status_code == status.HTTP_200_OK
370+
assert response.data == {'version': 'v2'}
371+
372+
def test_with_default(self):
373+
class FakeResolverMatch:
374+
namespace = 'v1'
375+
376+
scheme = versioning.NamespaceVersioning
377+
view = AllowedAndDefaultVersionsView.as_view(versioning_class=scheme)
378+
379+
request = factory.get('/endpoint/')
380+
request.resolver_match = FakeResolverMatch
381+
response = view(request)
382+
assert response.status_code == status.HTTP_200_OK
383+
assert response.data == {'version': 'v1'}
384+
385+
def test_no_match_without_default_but_none_allowed(self):
386+
class FakeResolverMatch:
387+
namespace = 'no_match'
388+
389+
scheme = versioning.NamespaceVersioning
390+
view = AllowedWithNoneVersionsView.as_view(versioning_class=scheme)
391+
392+
request = factory.get('/endpoint/')
393+
request.resolver_match = FakeResolverMatch
394+
response = view(request)
395+
assert response.status_code == status.HTTP_200_OK
396+
assert response.data == {'version': None}
397+
398+
def test_no_match_with_default_and_none_allowed(self):
399+
class FakeResolverMatch:
400+
namespace = 'no_match'
401+
402+
scheme = versioning.NamespaceVersioning
403+
view = AllowedWithNoneAndDefaultVersionsView.as_view(versioning_class=scheme)
404+
405+
request = factory.get('/endpoint/')
406+
request.resolver_match = FakeResolverMatch
407+
response = view(request)
408+
assert response.status_code == status.HTTP_200_OK
409+
assert response.data == {'version': 'v2'}
410+
411+
321412
class TestHyperlinkedRelatedField(URLPatternsTestCase, APITestCase):
322413
included = [
323414
path('namespaced/<int:pk>/', dummy_pk_view, name='namespaced'),

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy