瀏覽代碼

Fixed #32195 -- Added system check for invalid view in path() and improved error messages.

Angus Holder 4 年之前
父節點
當前提交
3e73c65ffc

+ 8 - 0
django/urls/conf.py

@@ -55,6 +55,8 @@ def include(arg, namespace=None):
 
 
 def _path(route, view, kwargs=None, name=None, Pattern=None):
+    from django.views import View
+
     if isinstance(view, (list, tuple)):
         # For include(...) processing.
         pattern = Pattern(route, is_endpoint=False)
@@ -69,6 +71,12 @@ def _path(route, view, kwargs=None, name=None, Pattern=None):
     elif callable(view):
         pattern = Pattern(route, name=name, is_endpoint=True)
         return URLPattern(pattern, view, kwargs, name)
+    elif isinstance(view, View):
+        view_cls_name = view.__class__.__name__
+        raise TypeError(
+            f'view must be a callable, pass {view_cls_name}.as_view(), not '
+            f'{view_cls_name}().'
+        )
     else:
         raise TypeError('view must be a callable or a list/tuple in the case of include().')
 

+ 17 - 0
django/urls/resolvers.py

@@ -345,6 +345,7 @@ class URLPattern:
     def check(self):
         warnings = self._check_pattern_name()
         warnings.extend(self.pattern.check())
+        warnings.extend(self._check_callback())
         return warnings
 
     def _check_pattern_name(self):
@@ -361,6 +362,22 @@ class URLPattern:
         else:
             return []
 
+    def _check_callback(self):
+        from django.views import View
+
+        view = self.callback
+        if inspect.isclass(view) and issubclass(view, View):
+            return [Error(
+                'Your URL pattern %s has an invalid view, pass %s.as_view() '
+                'instead of %s.' % (
+                    self.pattern.describe(),
+                    view.__name__,
+                    view.__name__,
+                ),
+                id='urls.E009',
+            )]
+        return []
+
     def resolve(self, path):
         match = self.pattern.match(path)
         if match:

+ 2 - 0
docs/ref/checks.txt

@@ -584,6 +584,8 @@ The following checks are performed on your URL configuration:
   take the correct number of arguments (…).
 * **urls.E008**: The custom ``handlerXXX`` view ``'path.to.view'`` could not be
   imported.
+* **urls.E009**: Your URL pattern ``<pattern>`` has an invalid view, pass
+  ``<view>.as_view()`` instead of ``<view>``.
 
 ``contrib`` app checks
 ======================

+ 10 - 0
tests/check_framework/test_urls.py

@@ -134,6 +134,16 @@ class CheckUrlConfigTests(SimpleTestCase):
         result = check_url_namespaces_unique(None)
         self.assertEqual(result, [])
 
+    @override_settings(ROOT_URLCONF='check_framework.urls.cbv_as_view')
+    def test_check_view_not_class(self):
+        self.assertEqual(check_url_config(None), [
+            Error(
+                "Your URL pattern 'missing_as_view' has an invalid view, pass "
+                "EmptyCBV.as_view() instead of EmptyCBV.",
+                id='urls.E009',
+            ),
+        ])
+
 
 class UpdatedToPathTests(SimpleTestCase):
 

+ 19 - 0
tests/check_framework/urls/cbv_as_view.py

@@ -0,0 +1,19 @@
+from django.http import HttpResponse
+from django.urls import path
+from django.views import View
+
+
+class EmptyCBV(View):
+    pass
+
+
+class EmptyCallableView:
+    def __call__(self, request, *args, **kwargs):
+        return HttpResponse()
+
+
+urlpatterns = [
+    path('missing_as_view', EmptyCBV),
+    path('has_as_view', EmptyCBV.as_view()),
+    path('callable_class', EmptyCallableView()),
+]

+ 9 - 0
tests/urlpatterns/tests.py

@@ -5,6 +5,7 @@ from django.core.exceptions import ImproperlyConfigured
 from django.test import SimpleTestCase
 from django.test.utils import override_settings
 from django.urls import NoReverseMatch, Resolver404, path, resolve, reverse
+from django.views import View
 
 from .converters import DynamicConverter
 from .views import empty_view
@@ -146,6 +147,14 @@ class SimplifiedURLTests(SimpleTestCase):
         with self.assertRaisesMessage(TypeError, msg):
             path('articles/', 'invalid_view')
 
+    def test_invalid_view_instance(self):
+        class EmptyCBV(View):
+            pass
+
+        msg = 'view must be a callable, pass EmptyCBV.as_view(), not EmptyCBV().'
+        with self.assertRaisesMessage(TypeError, msg):
+            path('foo', EmptyCBV())
+
     def test_whitespace_in_route(self):
         msg = (
             "URL route 'space/<int:num>/extra/<str:%stest>' cannot contain "