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
This commit is contained in:
Konstantin Kuchkov 2023-06-14 06:24:09 -07:00 committed by GitHub
parent aed7761a8d
commit 71f87a5864
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 101 additions and 9 deletions

View File

@ -119,15 +119,16 @@ class NamespaceVersioning(BaseVersioning):
def determine_version(self, request, *args, **kwargs): def determine_version(self, request, *args, **kwargs):
resolver_match = getattr(request, 'resolver_match', None) resolver_match = getattr(request, 'resolver_match', None)
if resolver_match is None or not resolver_match.namespace: if resolver_match is not None and resolver_match.namespace:
return self.default_version # Allow for possibly nested namespaces.
possible_versions = resolver_match.namespace.split(':')
for version in possible_versions:
if self.is_allowed_version(version):
return version
# Allow for possibly nested namespaces. if not self.is_allowed_version(self.default_version):
possible_versions = resolver_match.namespace.split(':') raise exceptions.NotFound(self.invalid_version_message)
for version in possible_versions: return self.default_version
if self.is_allowed_version(version):
return version
raise exceptions.NotFound(self.invalid_version_message)
def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
if request.version is not None: if request.version is not None:

View File

@ -272,7 +272,7 @@ class TestInvalidVersion:
assert response.status_code == status.HTTP_404_NOT_FOUND assert response.status_code == status.HTTP_404_NOT_FOUND
class TestAllowedAndDefaultVersion: class TestAcceptHeaderAllowedAndDefaultVersion:
def test_missing_without_default(self): def test_missing_without_default(self):
scheme = versioning.AcceptHeaderVersioning scheme = versioning.AcceptHeaderVersioning
view = AllowedVersionsView.as_view(versioning_class=scheme) view = AllowedVersionsView.as_view(versioning_class=scheme)
@ -318,6 +318,97 @@ class TestAllowedAndDefaultVersion:
assert response.data == {'version': 'v2'} assert response.data == {'version': 'v2'}
class TestNamespaceAllowedAndDefaultVersion:
def test_no_namespace_without_default(self):
class FakeResolverMatch:
namespace = None
scheme = versioning.NamespaceVersioning
view = AllowedVersionsView.as_view(versioning_class=scheme)
request = factory.get('/endpoint/')
request.resolver_match = FakeResolverMatch
response = view(request)
assert response.status_code == status.HTTP_404_NOT_FOUND
def test_no_namespace_with_default(self):
class FakeResolverMatch:
namespace = None
scheme = versioning.NamespaceVersioning
view = AllowedAndDefaultVersionsView.as_view(versioning_class=scheme)
request = factory.get('/endpoint/')
request.resolver_match = FakeResolverMatch
response = view(request)
assert response.status_code == status.HTTP_200_OK
assert response.data == {'version': 'v2'}
def test_no_match_without_default(self):
class FakeResolverMatch:
namespace = 'no_match'
scheme = versioning.NamespaceVersioning
view = AllowedVersionsView.as_view(versioning_class=scheme)
request = factory.get('/endpoint/')
request.resolver_match = FakeResolverMatch
response = view(request)
assert response.status_code == status.HTTP_404_NOT_FOUND
def test_no_match_with_default(self):
class FakeResolverMatch:
namespace = 'no_match'
scheme = versioning.NamespaceVersioning
view = AllowedAndDefaultVersionsView.as_view(versioning_class=scheme)
request = factory.get('/endpoint/')
request.resolver_match = FakeResolverMatch
response = view(request)
assert response.status_code == status.HTTP_200_OK
assert response.data == {'version': 'v2'}
def test_with_default(self):
class FakeResolverMatch:
namespace = 'v1'
scheme = versioning.NamespaceVersioning
view = AllowedAndDefaultVersionsView.as_view(versioning_class=scheme)
request = factory.get('/endpoint/')
request.resolver_match = FakeResolverMatch
response = view(request)
assert response.status_code == status.HTTP_200_OK
assert response.data == {'version': 'v1'}
def test_no_match_without_default_but_none_allowed(self):
class FakeResolverMatch:
namespace = 'no_match'
scheme = versioning.NamespaceVersioning
view = AllowedWithNoneVersionsView.as_view(versioning_class=scheme)
request = factory.get('/endpoint/')
request.resolver_match = FakeResolverMatch
response = view(request)
assert response.status_code == status.HTTP_200_OK
assert response.data == {'version': None}
def test_no_match_with_default_and_none_allowed(self):
class FakeResolverMatch:
namespace = 'no_match'
scheme = versioning.NamespaceVersioning
view = AllowedWithNoneAndDefaultVersionsView.as_view(versioning_class=scheme)
request = factory.get('/endpoint/')
request.resolver_match = FakeResolverMatch
response = view(request)
assert response.status_code == status.HTTP_200_OK
assert response.data == {'version': 'v2'}
class TestHyperlinkedRelatedField(URLPatternsTestCase, APITestCase): class TestHyperlinkedRelatedField(URLPatternsTestCase, APITestCase):
included = [ included = [
path('namespaced/<int:pk>/', dummy_pk_view, name='namespaced'), path('namespaced/<int:pk>/', dummy_pk_view, name='namespaced'),