Skip to content

Commit 8b051ea

Browse files
7503 do device validate-create in serial (#12222)
* 7503 do device validate-create in serial * 7503 fix single instance * 7503 atomic transaction * 7503 fix return data for bulk operations * 7503 add test * Move sequential creation logic to a mixin --------- Co-authored-by: jeremystretch <[email protected]>
1 parent bca9d0f commit 8b051ea

File tree

3 files changed

+69
-6
lines changed

3 files changed

+69
-6
lines changed

netbox/dcim/api/views.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
from django.http import Http404, HttpResponse
22
from django.shortcuts import get_object_or_404
3-
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter
43
from drf_spectacular.types import OpenApiTypes
4+
from drf_spectacular.utils import extend_schema, OpenApiParameter
55
from rest_framework.decorators import action
66
from rest_framework.renderers import JSONRenderer
77
from rest_framework.response import Response
8-
from rest_framework.status import HTTP_400_BAD_REQUEST
98
from rest_framework.routers import APIRootView
9+
from rest_framework.status import HTTP_400_BAD_REQUEST
1010
from rest_framework.viewsets import ViewSet
1111

1212
from circuits.models import Circuit
1313
from dcim import filtersets
1414
from dcim.constants import CABLE_TRACE_SVG_DEFAULT_WIDTH
1515
from dcim.models import *
1616
from dcim.svg import CableTraceSVG
17-
from extras.api.nested_serializers import NestedConfigTemplateSerializer
1817
from extras.api.mixins import ConfigContextQuerySetMixin, ConfigTemplateRenderMixin
1918
from ipam.models import Prefix, VLAN
2019
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
2120
from netbox.api.metadata import ContentTypeMetadata
2221
from netbox.api.pagination import StripCountAnnotationsPaginator
2322
from netbox.api.renderers import TextRenderer
2423
from netbox.api.viewsets import NetBoxModelViewSet
24+
from netbox.api.viewsets.mixins import SequentialBulkCreatesMixin
2525
from netbox.constants import NESTED_SERIALIZER_PREFIX
2626
from utilities.api import get_serializer_for_model
2727
from utilities.utils import count_related
@@ -386,7 +386,12 @@ class PlatformViewSet(NetBoxModelViewSet):
386386
# Devices/modules
387387
#
388388

389-
class DeviceViewSet(ConfigContextQuerySetMixin, ConfigTemplateRenderMixin, NetBoxModelViewSet):
389+
class DeviceViewSet(
390+
SequentialBulkCreatesMixin,
391+
ConfigContextQuerySetMixin,
392+
ConfigTemplateRenderMixin,
393+
NetBoxModelViewSet
394+
):
390395
queryset = Device.objects.prefetch_related(
391396
'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay',
392397
'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'config_template', 'tags',

netbox/dcim/tests/test_api.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1115,7 +1115,7 @@ def setUpTestData(cls):
11151115

11161116
device_types = (
11171117
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
1118-
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'),
1118+
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2', u_height=2),
11191119
)
11201120
DeviceType.objects.bulk_create(device_types)
11211121

@@ -1229,6 +1229,39 @@ def test_unique_name_per_site_constraint(self):
12291229

12301230
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
12311231

1232+
def test_rack_fit(self):
1233+
"""
1234+
Check that creating multiple devices with overlapping position fails.
1235+
"""
1236+
device = Device.objects.first()
1237+
device_type = DeviceType.objects.all()[1]
1238+
data = [
1239+
{
1240+
'device_type': device_type.pk,
1241+
'device_role': device.device_role.pk,
1242+
'site': device.site.pk,
1243+
'name': 'Test Device 7',
1244+
'rack': device.rack.pk,
1245+
'face': 'front',
1246+
'position': 1
1247+
},
1248+
{
1249+
'device_type': device_type.pk,
1250+
'device_role': device.device_role.pk,
1251+
'site': device.site.pk,
1252+
'name': 'Test Device 8',
1253+
'rack': device.rack.pk,
1254+
'face': 'front',
1255+
'position': 2
1256+
}
1257+
]
1258+
1259+
self.add_permissions('dcim.add_device')
1260+
url = reverse('dcim-api:device-list')
1261+
response = self.client.post(url, data, format='json', **self.header)
1262+
1263+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
1264+
12321265

12331266
class ModuleTest(APIViewTestCases.APIViewTestCase):
12341267
model = Module

netbox/netbox/api/viewsets/mixins.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@
1515

1616
__all__ = (
1717
'BriefModeMixin',
18+
'BulkDestroyModelMixin',
1819
'BulkUpdateModelMixin',
1920
'CustomFieldsMixin',
2021
'ExportTemplatesMixin',
21-
'BulkDestroyModelMixin',
2222
'ObjectValidationMixin',
23+
'SequentialBulkCreatesMixin',
2324
)
2425

2526

@@ -94,6 +95,30 @@ def list(self, request, *args, **kwargs):
9495
return super().list(request, *args, **kwargs)
9596

9697

98+
class SequentialBulkCreatesMixin:
99+
"""
100+
Perform bulk creation of new objects sequentially, rather than all at once. This ensures that any validation
101+
which depends on the evaluation of existing objects (such as checking for free space within a rack) functions
102+
appropriately.
103+
"""
104+
@transaction.atomic
105+
def create(self, request, *args, **kwargs):
106+
if not isinstance(request.data, list):
107+
# Creating a single object
108+
return super().create(request, *args, **kwargs)
109+
110+
return_data = []
111+
for data in request.data:
112+
serializer = self.get_serializer(data=data)
113+
serializer.is_valid(raise_exception=True)
114+
self.perform_create(serializer)
115+
return_data.append(serializer.data)
116+
117+
headers = self.get_success_headers(serializer.data)
118+
119+
return Response(return_data, status=status.HTTP_201_CREATED, headers=headers)
120+
121+
97122
class BulkUpdateModelMixin:
98123
"""
99124
Support bulk modification of objects using the list endpoint for a model. Accepts a PATCH action with a list of one

0 commit comments

Comments
 (0)