Fixes #18991: AttributeError: NoneType object has not attribute model

`GenericArrayForeignKey`, which was added in #18826, advertised itself
as a many_to_many relation, which is somewhat inaccurate and triggered
DRF's introspection of many_to_many relations which looks for a
`remote_field.model` property chain on the relation. Since that doesn't
exist, DRF threw an AttributeError resulting in a 500 error.

This changes `GenericArrayForeignKey` to advertise itself to DRF as a
one_to_many relation, which is more accurate, and does not trigger the
property access in DRF that was causing the problem.

This also adds a regression test for both FrontPort and RearPort "path"
API view.
This commit is contained in:
Jason Novinger 2025-03-25 13:35:23 -05:00
parent 9a1d9365cd
commit 636148f965
2 changed files with 41 additions and 3 deletions

View File

@ -1,6 +1,6 @@
import json
from django.test import override_settings
from django.test import override_settings, tag
from django.urls import reverse
from django.utils.translation import gettext as _
from rest_framework import status
@ -1904,6 +1904,27 @@ class FrontPortTest(APIViewTestCases.APIViewTestCase):
},
]
@tag('regression') # Issue #18991
def test_front_port_paths(self):
device = Device.objects.first()
rear_port = RearPort.objects.create(
device=device, name='Rear Port 10', type=PortTypeChoices.TYPE_8P8C
)
interface1 = Interface.objects.create(device=device, name='Interface 1')
front_port = FrontPort.objects.create(
device=device,
name='Rear Port 10',
type=PortTypeChoices.TYPE_8P8C,
rear_port=rear_port,
)
Cable.objects.create(a_terminations=[interface1], b_terminations=[front_port])
self.add_permissions(f'dcim.view_{self.model._meta.model_name}')
url = reverse(f'dcim-api:{self.model._meta.model_name}-paths', kwargs={'pk': front_port.pk})
response = self.client.get(url, **self.header)
self.assertHttpStatus(response, status.HTTP_200_OK)
class RearPortTest(APIViewTestCases.APIViewTestCase):
model = RearPort
@ -1947,6 +1968,23 @@ class RearPortTest(APIViewTestCases.APIViewTestCase):
},
]
@tag('regression') # Issue #18991
def test_rear_port_paths(self):
device = Device.objects.first()
interface1 = Interface.objects.create(device=device, name='Interface 1')
rear_port = RearPort.objects.create(
device=device,
name='Rear Port 10',
type=PortTypeChoices.TYPE_8P8C,
)
Cable.objects.create(a_terminations=[interface1], b_terminations=[rear_port])
self.add_permissions(f'dcim.view_{self.model._meta.model_name}')
url = reverse(f'dcim-api:{self.model._meta.model_name}-paths', kwargs={'pk': rear_port.pk})
response = self.client.get(url, **self.header)
self.assertHttpStatus(response, status.HTTP_200_OK)
class ModuleBayTest(APIViewTestCases.APIViewTestCase):
model = ModuleBay

View File

@ -198,9 +198,9 @@ class GenericArrayForeignKey(FieldCacheMixin, models.Field):
Provide a generic many-to-many relation through an 2d array field
"""
many_to_many = True
many_to_many = False
many_to_one = False
one_to_many = False
one_to_many = True
one_to_one = False
def __init__(self, field, for_concrete_model=True):