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)