Просмотр исходного кода

Fixes #20641: Handle viewsets with queryset=None in get_view_name() (#20642)

The get_view_name() utility function crashed with AttributeError when
called on viewsets that override get_queryset() without setting a
class-level queryset attribute (e.g., ObjectChangeViewSet).

This pattern became necessary in #20089 to force re-evaluation of
valid_models() on each request, ensuring ObjectChange querysets reflect
current ContentType state.

Added None check to fall back to DRF's default view naming when no
class-level queryset exists.
Jason Novinger 3 месяцев назад
Родитель
Сommit
fb8d41b527
2 измененных файлов с 19 добавлено и 2 удалено
  1. 1 1
      netbox/utilities/api.py
  2. 18 1
      netbox/utilities/tests/test_api.py

+ 1 - 1
netbox/utilities/api.py

@@ -72,7 +72,7 @@ def get_view_name(view):
     Derive the view name from its associated model, if it has one. Fall back to DRF's built-in `get_view_name()`.
     Derive the view name from its associated model, if it has one. Fall back to DRF's built-in `get_view_name()`.
     This function is provided to DRF as its VIEW_NAME_FUNCTION.
     This function is provided to DRF as its VIEW_NAME_FUNCTION.
     """
     """
-    if hasattr(view, 'queryset'):
+    if hasattr(view, 'queryset') and view.queryset is not None:
         # Derive the model name from the queryset.
         # Derive the model name from the queryset.
         name = title(view.queryset.model._meta.verbose_name)
         name = title(view.queryset.model._meta.verbose_name)
         if suffix := getattr(view, 'suffix', None):
         if suffix := getattr(view, 'suffix', None):

+ 18 - 1
netbox/utilities/tests/test_api.py

@@ -1,4 +1,4 @@
-from django.test import Client, TestCase, override_settings
+from django.test import Client, TestCase, override_settings, tag
 from django.urls import reverse
 from django.urls import reverse
 from drf_spectacular.drainage import GENERATOR_STATS
 from drf_spectacular.drainage import GENERATOR_STATS
 from rest_framework import status
 from rest_framework import status
@@ -9,6 +9,7 @@ from extras.choices import CustomFieldTypeChoices
 from extras.models import CustomField
 from extras.models import CustomField
 from ipam.models import VLAN
 from ipam.models import VLAN
 from netbox.config import get_config
 from netbox.config import get_config
+from utilities.api import get_view_name
 from utilities.testing import APITestCase, disable_warnings
 from utilities.testing import APITestCase, disable_warnings
 
 
 
 
@@ -267,3 +268,19 @@ class APIDocsTestCase(TestCase):
         with GENERATOR_STATS.silence():  # Suppress schema generator warnings
         with GENERATOR_STATS.silence():  # Suppress schema generator warnings
             response = self.client.get(url)
             response = self.client.get(url)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
+
+
+class GetViewNameTestCase(TestCase):
+
+    @tag('regression')
+    def test_get_view_name_with_none_queryset(self):
+        from rest_framework.viewsets import ReadOnlyModelViewSet
+
+        class MockViewSet(ReadOnlyModelViewSet):
+            queryset = None
+
+        view = MockViewSet()
+        view.suffix = 'List'
+
+        name = get_view_name(view)
+        self.assertEqual(name, 'Mock List')