From 927c012fc9f8b625642aeecabd5989bb909f6c3c Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Thu, 4 Jun 2020 09:34:22 -0500 Subject: [PATCH 01/19] #4674 - Fix available-ips and available-prefixes swagger definitions --- netbox/ipam/api/views.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index dd3652b1f..70065fd96 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -74,12 +74,8 @@ class PrefixViewSet(CustomFieldModelViewSet): serializer_class = serializers.PrefixSerializer filterset_class = filters.PrefixFilterSet - @swagger_auto_schema( - methods=['get', 'post'], - responses={ - 200: serializers.AvailablePrefixSerializer(many=True), - } - ) + @swagger_auto_schema(method='get', responses={200: serializers.AvailablePrefixSerializer(many=True)}) + @swagger_auto_schema(method='post', responses={201: serializers.AvailablePrefixSerializer(many=False)}) @action(detail=True, url_path='available-prefixes', methods=['get', 'post']) @advisory_lock(ADVISORY_LOCK_KEYS['available-prefixes']) def available_prefixes(self, request, pk=None): @@ -158,12 +154,9 @@ class PrefixViewSet(CustomFieldModelViewSet): return Response(serializer.data) - @swagger_auto_schema( - methods=['get', 'post'], - responses={ - 200: serializers.AvailableIPSerializer(many=True), - } - ) + @swagger_auto_schema(method='get', responses={200: serializers.AvailableIPSerializer(many=True)}) + @swagger_auto_schema(method='post', responses={201: serializers.AvailableIPSerializer(many=False)}, + request_body=serializers.AvailableIPSerializer(many=False)) @action(detail=True, url_path='available-ips', methods=['get', 'post']) @advisory_lock(ADVISORY_LOCK_KEYS['available-ips']) def available_ips(self, request, pk=None): From 5330914431a0367ab6e65205b6edcf0daa0c6549 Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Thu, 4 Jun 2020 09:42:00 -0500 Subject: [PATCH 02/19] #4674 - Correct many=False to many=True on the response serializers --- netbox/ipam/api/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 70065fd96..076e1f86f 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -75,7 +75,7 @@ class PrefixViewSet(CustomFieldModelViewSet): filterset_class = filters.PrefixFilterSet @swagger_auto_schema(method='get', responses={200: serializers.AvailablePrefixSerializer(many=True)}) - @swagger_auto_schema(method='post', responses={201: serializers.AvailablePrefixSerializer(many=False)}) + @swagger_auto_schema(method='post', responses={201: serializers.AvailablePrefixSerializer(many=True)}) @action(detail=True, url_path='available-prefixes', methods=['get', 'post']) @advisory_lock(ADVISORY_LOCK_KEYS['available-prefixes']) def available_prefixes(self, request, pk=None): @@ -155,7 +155,7 @@ class PrefixViewSet(CustomFieldModelViewSet): return Response(serializer.data) @swagger_auto_schema(method='get', responses={200: serializers.AvailableIPSerializer(many=True)}) - @swagger_auto_schema(method='post', responses={201: serializers.AvailableIPSerializer(many=False)}, + @swagger_auto_schema(method='post', responses={201: serializers.AvailableIPSerializer(many=True)}, request_body=serializers.AvailableIPSerializer(many=False)) @action(detail=True, url_path='available-ips', methods=['get', 'post']) @advisory_lock(ADVISORY_LOCK_KEYS['available-ips']) From 20ec700045b9a1a4c8d67c011a183d2d73e218e2 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 8 Jun 2020 17:00:47 -0400 Subject: [PATCH 03/19] Changelog for #4674 --- docs/release-notes/version-2.8.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-2.8.md b/docs/release-notes/version-2.8.md index f9b02db8d..a1756be7b 100644 --- a/docs/release-notes/version-2.8.md +++ b/docs/release-notes/version-2.8.md @@ -8,6 +8,7 @@ ### Bug Fixes +* [#4674](https://github.com/netbox-community/netbox/issues/4674) - Fix API definition for available prefix and IP address endpoints * [#4702](https://github.com/netbox-community/netbox/issues/4702) - Catch IntegrityError exception when adding a non-unique secret * [#4707](https://github.com/netbox-community/netbox/issues/4707) - Fix `prefix_count` population on VLAN API serializer * [#4725](https://github.com/netbox-community/netbox/issues/4725) - Fix "brief" rendering of various REST API endpoints From ed9ca270a7b708f7ab7b312d9f39af482f9c66c8 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 9 Jun 2020 13:24:07 -0400 Subject: [PATCH 04/19] Add missing API tests for pass-through port templates --- netbox/dcim/tests/test_api.py | 109 +++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 6c6b75c7f..39bc181fe 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -8,9 +8,9 @@ from dcim.choices import * from dcim.constants import * from dcim.models import ( Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, - DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, Interface, InterfaceTemplate, Manufacturer, + DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerFeed, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, PowerPanel, - Rack, RackGroup, RackReservation, RackRole, RearPort, Region, Site, VirtualChassis, + Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis, ) from ipam.models import VLAN from extras.models import Graph @@ -578,6 +578,111 @@ class InterfaceTemplateTest(APIViewTestCases.APIViewTestCase): ] +class FrontPortTemplateTest(APIViewTestCases.APIViewTestCase): + model = FrontPortTemplate + brief_fields = ['id', 'name', 'url'] + + @classmethod + def setUpTestData(cls): + manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1') + devicetype = DeviceType.objects.create( + manufacturer=manufacturer, model='Device Type 1', slug='device-type-1' + ) + + rear_port_templates = ( + RearPortTemplate(device_type=devicetype, name='Rear Port Template 1', type=PortTypeChoices.TYPE_8P8C), + RearPortTemplate(device_type=devicetype, name='Rear Port Template 2', type=PortTypeChoices.TYPE_8P8C), + RearPortTemplate(device_type=devicetype, name='Rear Port Template 3', type=PortTypeChoices.TYPE_8P8C), + RearPortTemplate(device_type=devicetype, name='Rear Port Template 4', type=PortTypeChoices.TYPE_8P8C), + RearPortTemplate(device_type=devicetype, name='Rear Port Template 5', type=PortTypeChoices.TYPE_8P8C), + RearPortTemplate(device_type=devicetype, name='Rear Port Template 6', type=PortTypeChoices.TYPE_8P8C), + ) + RearPortTemplate.objects.bulk_create(rear_port_templates) + + front_port_templates = ( + FrontPortTemplate( + device_type=devicetype, + name='Front Port Template 1', + type=PortTypeChoices.TYPE_8P8C, + rear_port=rear_port_templates[0] + ), + FrontPortTemplate( + device_type=devicetype, + name='Front Port Template 2', + type=PortTypeChoices.TYPE_8P8C, + rear_port=rear_port_templates[1] + ), + FrontPortTemplate( + device_type=devicetype, + name='Front Port Template 3', + type=PortTypeChoices.TYPE_8P8C, + rear_port=rear_port_templates[2] + ), + ) + FrontPortTemplate.objects.bulk_create(front_port_templates) + + cls.create_data = [ + { + 'device_type': devicetype.pk, + 'name': 'Front Port Template 4', + 'type': PortTypeChoices.TYPE_8P8C, + 'rear_port': rear_port_templates[3].pk, + 'position': 1, + }, + { + 'device_type': devicetype.pk, + 'name': 'Front Port Template 5', + 'type': PortTypeChoices.TYPE_8P8C, + 'rear_port': rear_port_templates[4].pk, + 'position': 1, + }, + { + 'device_type': devicetype.pk, + 'name': 'Front Port Template 6', + 'type': PortTypeChoices.TYPE_8P8C, + 'rear_port': rear_port_templates[5].pk, + 'position': 1, + }, + ] + + +class RearPortTemplateTest(APIViewTestCases.APIViewTestCase): + model = RearPortTemplate + brief_fields = ['id', 'name', 'url'] + + @classmethod + def setUpTestData(cls): + manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1') + devicetype = DeviceType.objects.create( + manufacturer=manufacturer, model='Device Type 1', slug='device-type-1' + ) + + rear_port_templates = ( + RearPortTemplate(device_type=devicetype, name='Rear Port Template 1', type=PortTypeChoices.TYPE_8P8C), + RearPortTemplate(device_type=devicetype, name='Rear Port Template 2', type=PortTypeChoices.TYPE_8P8C), + RearPortTemplate(device_type=devicetype, name='Rear Port Template 3', type=PortTypeChoices.TYPE_8P8C), + ) + RearPortTemplate.objects.bulk_create(rear_port_templates) + + cls.create_data = [ + { + 'device_type': devicetype.pk, + 'name': 'Rear Port Template 4', + 'type': PortTypeChoices.TYPE_8P8C, + }, + { + 'device_type': devicetype.pk, + 'name': 'Rear Port Template 5', + 'type': PortTypeChoices.TYPE_8P8C, + }, + { + 'device_type': devicetype.pk, + 'name': 'Rear Port Template 6', + 'type': PortTypeChoices.TYPE_8P8C, + }, + ] + + class DeviceBayTemplateTest(APIViewTestCases.APIViewTestCase): model = DeviceBayTemplate brief_fields = ['id', 'name', 'url'] From 062a319a7ced97457763af45b7f6c3acf820fc3b Mon Sep 17 00:00:00 2001 From: Tyler Bigler Date: Tue, 9 Jun 2020 13:35:44 -0400 Subject: [PATCH 05/19] Add example of bulk object creation --- docs/api/examples.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/api/examples.md b/docs/api/examples.md index 1906d0db9..f4348907f 100644 --- a/docs/api/examples.md +++ b/docs/api/examples.md @@ -145,3 +145,18 @@ $ curl -v -X DELETE -H "Authorization: Token d2f763479f703d80de0ec15254237bc651f ``` The response to a successful `DELETE` request will have code 204 (No Content); the body of the response will be empty. + + +## Bulk Object Creation + +The REST API supports the creation of multiple objects of the same type using a single `POST` request. For example, to create multiple devices: + +``` +curl -X POST -H "Authorization: Token " -H "Content-Type: application/json" -H "Accept: application/json; indent=4" http://localhost:8000/api/dcim/devices/ --data '[ +{"name": "device1", "device_type": 24, "device_role": 17, "site": 6}, +{"name": "device2", "device_type": 24, "device_role": 17, "site": 6}, +{"name": "device3", "device_type": 24, "device_role": 17, "site": 6}, +]' +``` + +Bulk creation is all-or-none: If any of the creations fails, the entire operation is rolled back. A successful response returns an HTTP code 201 and the body of the response will be a list/array of the objects created. \ No newline at end of file From 15004c654f626c0ebe65baa925d599910406dfda Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 9 Jun 2020 14:47:05 -0400 Subject: [PATCH 06/19] Add missing API cable trace test for interfaces --- netbox/dcim/tests/test_api.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 39bc181fe..b2d348843 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -1254,6 +1254,34 @@ class InterfaceTest(APIViewTestCases.APIViewTestCase): self.assertEqual(len(response.data), 3) self.assertEqual(response.data[0]['embed_url'], 'http://example.com/graphs.py?interface=Interface 1&foo=1') + def test_trace_interface(self): + """ + Test tracing an Interface cable. + """ + interface_a = Interface.objects.first() + peer_device = Device.objects.create( + site=Site.objects.first(), + device_type=DeviceType.objects.first(), + device_role=DeviceRole.objects.first(), + name='Peer Device' + ) + interface_b = Interface.objects.create( + device=peer_device, + name='Interface X' + ) + cable = Cable(termination_a=interface_a, termination_b=interface_b, label='Cable 1') + cable.save() + + url = reverse('dcim-api:interface-trace', kwargs={'pk': interface_a.pk}) + response = self.client.get(url, **self.header) + + self.assertHttpStatus(response, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + segment1 = response.data[0] + self.assertEqual(segment1[0]['name'], interface_a.name) + self.assertEqual(segment1[1]['label'], cable.label) + self.assertEqual(segment1[2]['name'], interface_b.name) + class FrontPortTest(APIViewTestCases.APIViewTestCase): model = FrontPort From 16cdf3006f5dbee09efabeea7f1864f9726367b7 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 9 Jun 2020 15:12:10 -0400 Subject: [PATCH 07/19] Fixes #4736: Add cable trace endpoints for pass-through ports --- docs/release-notes/version-2.8.md | 1 + netbox/dcim/api/views.py | 4 +-- netbox/dcim/tests/test_api.py | 56 +++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-2.8.md b/docs/release-notes/version-2.8.md index a1756be7b..348a6fa36 100644 --- a/docs/release-notes/version-2.8.md +++ b/docs/release-notes/version-2.8.md @@ -12,6 +12,7 @@ * [#4702](https://github.com/netbox-community/netbox/issues/4702) - Catch IntegrityError exception when adding a non-unique secret * [#4707](https://github.com/netbox-community/netbox/issues/4707) - Fix `prefix_count` population on VLAN API serializer * [#4725](https://github.com/netbox-community/netbox/issues/4725) - Fix "brief" rendering of various REST API endpoints +* [#4736](https://github.com/netbox-community/netbox/issues/4736) - Add cable trace endpoints for pass-through ports --- diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 9c8fe12de..f70193903 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -502,13 +502,13 @@ class InterfaceViewSet(CableTraceMixin, ModelViewSet): return Response(serializer.data) -class FrontPortViewSet(ModelViewSet): +class FrontPortViewSet(CableTraceMixin, ModelViewSet): queryset = FrontPort.objects.prefetch_related('device__device_type__manufacturer', 'rear_port', 'cable', 'tags') serializer_class = serializers.FrontPortSerializer filterset_class = filters.FrontPortFilterSet -class RearPortViewSet(ModelViewSet): +class RearPortViewSet(CableTraceMixin, ModelViewSet): queryset = RearPort.objects.prefetch_related('device__device_type__manufacturer', 'cable', 'tags') serializer_class = serializers.RearPortSerializer filterset_class = filters.RearPortFilterSet diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index b2d348843..4af82170a 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -1336,6 +1336,34 @@ class FrontPortTest(APIViewTestCases.APIViewTestCase): }, ] + def test_trace_frontport(self): + """ + Test tracing a FrontPort cable. + """ + frontport = FrontPort.objects.first() + peer_device = Device.objects.create( + site=Site.objects.first(), + device_type=DeviceType.objects.first(), + device_role=DeviceRole.objects.first(), + name='Peer Device' + ) + interface = Interface.objects.create( + device=peer_device, + name='Interface X' + ) + cable = Cable(termination_a=frontport, termination_b=interface, label='Cable 1') + cable.save() + + url = reverse('dcim-api:frontport-trace', kwargs={'pk': frontport.pk}) + response = self.client.get(url, **self.header) + + self.assertHttpStatus(response, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + segment1 = response.data[0] + self.assertEqual(segment1[0]['name'], frontport.name) + self.assertEqual(segment1[1]['label'], cable.label) + self.assertEqual(segment1[2]['name'], interface.name) + class RearPortTest(APIViewTestCases.APIViewTestCase): model = RearPort @@ -1374,6 +1402,34 @@ class RearPortTest(APIViewTestCases.APIViewTestCase): }, ] + def test_trace_rearport(self): + """ + Test tracing a RearPort cable. + """ + rearport = RearPort.objects.first() + peer_device = Device.objects.create( + site=Site.objects.first(), + device_type=DeviceType.objects.first(), + device_role=DeviceRole.objects.first(), + name='Peer Device' + ) + interface = Interface.objects.create( + device=peer_device, + name='Interface X' + ) + cable = Cable(termination_a=rearport, termination_b=interface, label='Cable 1') + cable.save() + + url = reverse('dcim-api:rearport-trace', kwargs={'pk': rearport.pk}) + response = self.client.get(url, **self.header) + + self.assertHttpStatus(response, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + segment1 = response.data[0] + self.assertEqual(segment1[0]['name'], rearport.name) + self.assertEqual(segment1[1]['label'], cable.label) + self.assertEqual(segment1[2]['name'], interface.name) + class DeviceBayTest(APIViewTestCases.APIViewTestCase): model = DeviceBay From 9abc67bbebc00eb078e34d5aa7a4453d3888dd51 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 10 Jun 2020 11:38:23 -0400 Subject: [PATCH 08/19] Fixes #4737: Introduce ColoredLabelColumn for consistent display of colored labels --- docs/release-notes/version-2.8.md | 1 + netbox/dcim/tables.py | 23 +++-------------------- netbox/utilities/tables.py | 17 +++++++++++++++++ netbox/virtualization/tables.py | 10 ++-------- 4 files changed, 23 insertions(+), 28 deletions(-) diff --git a/docs/release-notes/version-2.8.md b/docs/release-notes/version-2.8.md index 348a6fa36..219f94e23 100644 --- a/docs/release-notes/version-2.8.md +++ b/docs/release-notes/version-2.8.md @@ -13,6 +13,7 @@ * [#4707](https://github.com/netbox-community/netbox/issues/4707) - Fix `prefix_count` population on VLAN API serializer * [#4725](https://github.com/netbox-community/netbox/issues/4725) - Fix "brief" rendering of various REST API endpoints * [#4736](https://github.com/netbox-community/netbox/issues/4736) - Add cable trace endpoints for pass-through ports +* [#4737](https://github.com/netbox-community/netbox/issues/4737) - Fix display of role labels in virtual machines table --- diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index 9018625a0..d8cf41eaa 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -2,7 +2,7 @@ import django_tables2 as tables from django_tables2.utils import Accessor from tenancy.tables import COL_TENANT -from utilities.tables import BaseTable, BooleanColumn, ColorColumn, TagColumn, ToggleColumn +from utilities.tables import BaseTable, BooleanColumn, ColorColumn, ColoredLabelColumn, TagColumn, ToggleColumn from .models import ( Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, @@ -72,15 +72,6 @@ RACKROLE_ACTIONS = """ {% endif %} """ -RACK_ROLE = """ -{% if record.role %} - {% load helpers %} - -{% else %} - — -{% endif %} -""" - RACK_DEVICE_COUNT = """ {{ value }} """ @@ -137,11 +128,6 @@ PLATFORM_ACTIONS = """ {% endif %} """ -DEVICE_ROLE = """ -{% load helpers %} - -""" - STATUS_LABEL = """ {{ record.get_status_display }} """ @@ -325,9 +311,7 @@ class RackTable(BaseTable): status = tables.TemplateColumn( template_code=STATUS_LABEL ) - role = tables.TemplateColumn( - template_code=RACK_ROLE - ) + role = ColoredLabelColumn() u_height = tables.TemplateColumn( template_code="{{ record.u_height }}U", verbose_name='Height' @@ -806,8 +790,7 @@ class DeviceTable(BaseTable): viewname='dcim:rack', args=[Accessor('rack.pk')] ) - device_role = tables.TemplateColumn( - template_code=DEVICE_ROLE, + device_role = ColoredLabelColumn( verbose_name='Role' ) device_type = tables.LinkColumn( diff --git a/netbox/utilities/tables.py b/netbox/utilities/tables.py index 97108b5b2..10e408b43 100644 --- a/netbox/utilities/tables.py +++ b/netbox/utilities/tables.py @@ -84,6 +84,10 @@ class BaseTable(tables.Table): return [name for name in self.sequence if self.columns[name].visible] +# +# Table columns +# + class ToggleColumn(tables.CheckBoxColumn): """ Extend CheckBoxColumn to add a "toggle all" checkbox in the column header. @@ -129,6 +133,19 @@ class ColorColumn(tables.Column): ) +class ColoredLabelColumn(tables.TemplateColumn): + """ + Render a colored label (e.g. for DeviceRoles). + """ + template_code = """ + {% load helpers %} + {% if value %}{% else %}—{% endif %} + """ + + def __init__(self, *args, **kwargs): + super().__init__(template_code=self.template_code, *args, **kwargs) + + class TagColumn(tables.TemplateColumn): """ Display a list of tags assigned to the object. diff --git a/netbox/virtualization/tables.py b/netbox/virtualization/tables.py index 077add945..d957e0053 100644 --- a/netbox/virtualization/tables.py +++ b/netbox/virtualization/tables.py @@ -3,7 +3,7 @@ from django_tables2.utils import Accessor from dcim.models import Interface from tenancy.tables import COL_TENANT -from utilities.tables import BaseTable, TagColumn, ToggleColumn +from utilities.tables import BaseTable, ColoredLabelColumn, TagColumn, ToggleColumn from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine CLUSTERTYPE_ACTIONS = """ @@ -28,10 +28,6 @@ VIRTUALMACHINE_STATUS = """ {{ record.get_status_display }} """ -VIRTUALMACHINE_ROLE = """ -{% if record.role %}{% else %}—{% endif %} -""" - VIRTUALMACHINE_PRIMARY_IP = """ {{ record.primary_ip6.address.ip|default:"" }} {% if record.primary_ip6 and record.primary_ip4 %}
{% endif %} @@ -132,9 +128,7 @@ class VirtualMachineTable(BaseTable): viewname='virtualization:cluster', args=[Accessor('cluster.pk')] ) - role = tables.TemplateColumn( - template_code=VIRTUALMACHINE_ROLE - ) + role = ColoredLabelColumn() tenant = tables.TemplateColumn( template_code=COL_TENANT ) From 9fd36279abc28f4dff98d8246bd7e4a1e37021ed Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 10 Jun 2020 16:06:11 -0400 Subject: [PATCH 09/19] Fixes #4743: Allow users to create "next available" IPs without needing permission to create prefixes --- docs/release-notes/version-2.8.md | 1 + netbox/ipam/api/views.py | 10 +--------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/docs/release-notes/version-2.8.md b/docs/release-notes/version-2.8.md index 219f94e23..d179dd31c 100644 --- a/docs/release-notes/version-2.8.md +++ b/docs/release-notes/version-2.8.md @@ -14,6 +14,7 @@ * [#4725](https://github.com/netbox-community/netbox/issues/4725) - Fix "brief" rendering of various REST API endpoints * [#4736](https://github.com/netbox-community/netbox/issues/4736) - Add cable trace endpoints for pass-through ports * [#4737](https://github.com/netbox-community/netbox/issues/4737) - Fix display of role labels in virtual machines table +* [#4743](https://github.com/netbox-community/netbox/issues/4743) - Allow users to create "next available" IPs without needing permission to create prefixes --- diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 076e1f86f..c741ad0f4 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -90,10 +90,6 @@ class PrefixViewSet(CustomFieldModelViewSet): if request.method == 'POST': - # Permissions check - if not request.user.has_perm('ipam.add_prefix'): - raise PermissionDenied() - # Validate Requested Prefixes' length serializer = serializers.PrefixLengthSerializer( data=request.data if isinstance(request.data, list) else [request.data], @@ -157,7 +153,7 @@ class PrefixViewSet(CustomFieldModelViewSet): @swagger_auto_schema(method='get', responses={200: serializers.AvailableIPSerializer(many=True)}) @swagger_auto_schema(method='post', responses={201: serializers.AvailableIPSerializer(many=True)}, request_body=serializers.AvailableIPSerializer(many=False)) - @action(detail=True, url_path='available-ips', methods=['get', 'post']) + @action(detail=True, url_path='available-ips', methods=['get', 'post'], queryset=IPAddress.objects.all()) @advisory_lock(ADVISORY_LOCK_KEYS['available-ips']) def available_ips(self, request, pk=None): """ @@ -173,10 +169,6 @@ class PrefixViewSet(CustomFieldModelViewSet): # Create the next available IP within the prefix if request.method == 'POST': - # Permissions check - if not request.user.has_perm('ipam.add_ipaddress'): - raise PermissionDenied() - # Normalize to a list of objects requested_ips = request.data if isinstance(request.data, list) else [request.data] From 9fc4a4f24a44a8e73ac774ad9a080828dc86b9ea Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 12 Jun 2020 15:11:27 -0400 Subject: [PATCH 10/19] Closes #4755: Enable creation of rack reservations directly from navigation menu --- docs/release-notes/version-2.8.md | 1 + netbox/dcim/forms.py | 58 +++++++++---------- netbox/dcim/tests/test_views.py | 2 +- .../templates/dcim/rackreservation_edit.html | 20 ++++--- netbox/templates/inc/nav_menu.html | 1 + netbox/utilities/forms.py | 22 ++----- netbox/utilities/testing/testcases.py | 7 +++ 7 files changed, 52 insertions(+), 59 deletions(-) diff --git a/docs/release-notes/version-2.8.md b/docs/release-notes/version-2.8.md index d179dd31c..d9ddb2297 100644 --- a/docs/release-notes/version-2.8.md +++ b/docs/release-notes/version-2.8.md @@ -5,6 +5,7 @@ ### Enhancements * [#4698](https://github.com/netbox-community/netbox/issues/4698) - Improve display of template code for object in admin UI +* [#4755](https://github.com/netbox-community/netbox/issues/4755) - Enable creation of rack reservations directly from navigation menu ### Bug Fixes diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 94cf51fcd..0df59c475 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -21,10 +21,10 @@ from ipam.models import IPAddress, VLAN from tenancy.forms import TenancyFilterForm, TenancyForm from tenancy.models import Tenant, TenantGroup from utilities.forms import ( - APISelect, APISelectMultiple, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm, - BulkEditNullBooleanSelect, ColorSelect, CommentField, ConfirmationForm, CSVChoiceField, CSVModelChoiceField, - CSVModelForm, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, - JSONField, SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, + APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, + ColorSelect, CommentField, ConfirmationForm, CSVChoiceField, CSVModelChoiceField, CSVModelForm, + DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField, + NumericArrayField, SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, ) from virtualization.models import Cluster, ClusterGroup, VirtualMachine @@ -729,21 +729,32 @@ class RackElevationFilterForm(RackFilterForm): # class RackReservationForm(BootstrapMixin, TenancyForm, forms.ModelForm): - rack = forms.ModelChoiceField( - queryset=Rack.objects.all(), + site = DynamicModelChoiceField( + queryset=Site.objects.all(), required=False, - widget=forms.HiddenInput() - ) - # TODO: Change this to an API-backed form field. We can't do this currently because we want to retain - # the multi-line