Repository: encode/django-rest-framework

Issue:
3.15 regression: Unset default namespace version suddenly raises 404
- The intention of #7278 seems to be to deal with situations where `DEFAULT_VERSION` is ignored. The description of the fix relates to non-`None` values of `DEFAULT_VERSION`.
- When `DEFAULT_VERSION` is `None` and `ALLOWED_VERSIONS` is non-empty, [the new code now raises 404](https://github.com/Kostia-K/django-rest-framework/blob/7050b4d36ecc3ce395bc097798af6bf9c99a28f0/rest_framework/versioning.py#L130).
- In 3.14, `None` would be returned in this case. This suddenly raises 404 where previously the view was properly run.

This is a regression that significantly changes the API. The change should be reverted.


Discussion:

Resolving patch:
diff --git a/rest_framework/versioning.py b/rest_framework/versioning.py
--- a/rest_framework/versioning.py
+++ b/rest_framework/versioning.py
@@ -119,16 +119,15 @@ class NamespaceVersioning(BaseVersioning):
 
     def determine_version(self, request, *args, **kwargs):
         resolver_match = getattr(request, 'resolver_match', None)
-        if resolver_match is not None and resolver_match.namespace:
-            # Allow for possibly nested namespaces.
-            possible_versions = resolver_match.namespace.split(':')
-            for version in possible_versions:
-                if self.is_allowed_version(version):
-                    return version
-
-        if not self.is_allowed_version(self.default_version):
-            raise exceptions.NotFound(self.invalid_version_message)
-        return self.default_version
+        if resolver_match is None or not 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
+        raise exceptions.NotFound(self.invalid_version_message)
 
     def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
         if request.version is not None:


Test patch:
diff --git a/tests/test_versioning.py b/tests/test_versioning.py
--- a/tests/test_versioning.py
+++ b/tests/test_versioning.py
@@ -272,7 +272,7 @@ class FakeResolverMatch(ResolverMatch):
         assert response.status_code == status.HTTP_404_NOT_FOUND
 
 
-class TestAcceptHeaderAllowedAndDefaultVersion:
+class TestAllowedAndDefaultVersion:
     def test_missing_without_default(self):
         scheme = versioning.AcceptHeaderVersioning
         view = AllowedVersionsView.as_view(versioning_class=scheme)
@@ -318,97 +318,6 @@ def test_missing_with_default_and_none_allowed(self):
         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):
     included = [
         path('namespaced/<int:pk>/', dummy_pk_view, name='namespaced'),