Skip to content

Commit d4f7adc

Browse files
committed
Fix NamespaceVersioning ignoring DEFAULT_VERSION on non-None namespaces
* 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 8cba4f8 commit d4f7adc

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
@@ -117,15 +117,16 @@ class NamespaceVersioning(BaseVersioning):
117117

118118
def determine_version(self, request, *args, **kwargs):
119119
resolver_match = getattr(request, 'resolver_match', None)
120-
if resolver_match is None or not resolver_match.namespace:
121-
return self.default_version
122-
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-
raise exceptions.NotFound(self.invalid_version_message)
120+
if resolver_match is not None and resolver_match.namespace:
121+
# Allow for possibly nested namespaces.
122+
possible_versions = resolver_match.namespace.split(':')
123+
for version in possible_versions:
124+
if self.is_allowed_version(version):
125+
return version
126+
127+
if not self.is_allowed_version(self.default_version):
128+
raise exceptions.NotFound(self.invalid_version_message)
129+
return self.default_version
129130

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

tests/test_versioning.py

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ class FakeResolverMatch:
262262
assert response.status_code == status.HTTP_404_NOT_FOUND
263263

264264

265-
class TestAllowedAndDefaultVersion:
265+
class TestAcceptHeaderAllowedAndDefaultVersion:
266266
def test_missing_without_default(self):
267267
scheme = versioning.AcceptHeaderVersioning
268268
view = AllowedVersionsView.as_view(versioning_class=scheme)
@@ -308,6 +308,97 @@ def test_missing_with_default_and_none_allowed(self):
308308
assert response.data == {'version': 'v2'}
309309

310310

311+
class TestNamespaceAllowedAndDefaultVersion:
312+
def test_no_namespace_without_default(self):
313+
class FakeResolverMatch:
314+
namespace = None
315+
316+
scheme = versioning.NamespaceVersioning
317+
view = AllowedVersionsView.as_view(versioning_class=scheme)
318+
319+
request = factory.get('/endpoint/')
320+
request.resolver_match = FakeResolverMatch
321+
response = view(request)
322+
assert response.status_code == status.HTTP_404_NOT_FOUND
323+
324+
def test_no_namespace_with_default(self):
325+
class FakeResolverMatch:
326+
namespace = None
327+
328+
scheme = versioning.NamespaceVersioning
329+
view = AllowedAndDefaultVersionsView.as_view(versioning_class=scheme)
330+
331+
request = factory.get('/endpoint/')
332+
request.resolver_match = FakeResolverMatch
333+
response = view(request)
334+
assert response.status_code == status.HTTP_200_OK
335+
assert response.data == {'version': 'v2'}
336+
337+
def test_no_match_without_default(self):
338+
class FakeResolverMatch:
339+
namespace = 'no_match'
340+
341+
scheme = versioning.NamespaceVersioning
342+
view = AllowedVersionsView.as_view(versioning_class=scheme)
343+
344+
request = factory.get('/endpoint/')
345+
request.resolver_match = FakeResolverMatch
346+
response = view(request)
347+
assert response.status_code == status.HTTP_404_NOT_FOUND
348+
349+
def test_no_match_with_default(self):
350+
class FakeResolverMatch:
351+
namespace = 'no_match'
352+
353+
scheme = versioning.NamespaceVersioning
354+
view = AllowedAndDefaultVersionsView.as_view(versioning_class=scheme)
355+
356+
request = factory.get('/endpoint/')
357+
request.resolver_match = FakeResolverMatch
358+
response = view(request)
359+
assert response.status_code == status.HTTP_200_OK
360+
assert response.data == {'version': 'v2'}
361+
362+
def test_with_default(self):
363+
class FakeResolverMatch:
364+
namespace = 'v2'
365+
366+
scheme = versioning.NamespaceVersioning
367+
view = AllowedAndDefaultVersionsView.as_view(versioning_class=scheme)
368+
369+
request = factory.get('/endpoint/')
370+
request.resolver_match = FakeResolverMatch
371+
response = view(request)
372+
assert response.status_code == status.HTTP_200_OK
373+
assert response.data == {'version': 'v2'}
374+
375+
def test_no_match_without_default_but_none_allowed(self):
376+
class FakeResolverMatch:
377+
namespace = 'no_match'
378+
379+
scheme = versioning.NamespaceVersioning
380+
view = AllowedWithNoneVersionsView.as_view(versioning_class=scheme)
381+
382+
request = factory.get('/endpoint/')
383+
request.resolver_match = FakeResolverMatch
384+
response = view(request)
385+
assert response.status_code == status.HTTP_200_OK
386+
assert response.data == {'version': None}
387+
388+
def test_no_match_with_default_and_none_allowed(self):
389+
class FakeResolverMatch:
390+
namespace = 'no_match'
391+
392+
scheme = versioning.NamespaceVersioning
393+
view = AllowedWithNoneAndDefaultVersionsView.as_view(versioning_class=scheme)
394+
395+
request = factory.get('/endpoint/')
396+
request.resolver_match = FakeResolverMatch
397+
response = view(request)
398+
assert response.status_code == status.HTTP_200_OK
399+
assert response.data == {'version': 'v2'}
400+
401+
311402
class TestHyperlinkedRelatedField(URLPatternsTestCase, APITestCase):
312403
included = [
313404
url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_view, name='namespaced'),

0 commit comments

Comments
 (0)