Skip to content

Commit fb8d41b

Browse files
authored
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.
1 parent ae5d791 commit fb8d41b

File tree

2 files changed

+19
-2
lines changed

2 files changed

+19
-2
lines changed

netbox/utilities/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def get_view_name(view):
7272
Derive the view name from its associated model, if it has one. Fall back to DRF's built-in `get_view_name()`.
7373
This function is provided to DRF as its VIEW_NAME_FUNCTION.
7474
"""
75-
if hasattr(view, 'queryset'):
75+
if hasattr(view, 'queryset') and view.queryset is not None:
7676
# Derive the model name from the queryset.
7777
name = title(view.queryset.model._meta.verbose_name)
7878
if suffix := getattr(view, 'suffix', None):

netbox/utilities/tests/test_api.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from django.test import Client, TestCase, override_settings
1+
from django.test import Client, TestCase, override_settings, tag
22
from django.urls import reverse
33
from drf_spectacular.drainage import GENERATOR_STATS
44
from rest_framework import status
@@ -9,6 +9,7 @@
99
from extras.models import CustomField
1010
from ipam.models import VLAN
1111
from netbox.config import get_config
12+
from utilities.api import get_view_name
1213
from utilities.testing import APITestCase, disable_warnings
1314

1415

@@ -267,3 +268,19 @@ def test_api_docs(self):
267268
with GENERATOR_STATS.silence(): # Suppress schema generator warnings
268269
response = self.client.get(url)
269270
self.assertEqual(response.status_code, 200)
271+
272+
273+
class GetViewNameTestCase(TestCase):
274+
275+
@tag('regression')
276+
def test_get_view_name_with_none_queryset(self):
277+
from rest_framework.viewsets import ReadOnlyModelViewSet
278+
279+
class MockViewSet(ReadOnlyModelViewSet):
280+
queryset = None
281+
282+
view = MockViewSet()
283+
view.suffix = 'List'
284+
285+
name = get_view_name(view)
286+
self.assertEqual(name, 'Mock List')

0 commit comments

Comments
 (0)