diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index ea663fbcc..b54a165a0 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -1212,7 +1212,6 @@ class ComponentTemplateImportForm(BootstrapMixin, forms.ModelForm): data.update({ 'device_type': device_type.pk, }) - print(data) super().__init__(data, *args, **kwargs) @@ -1279,7 +1278,7 @@ class InterfaceTemplateImportForm(ComponentTemplateImportForm): class FrontPortTemplateImportForm(ComponentTemplateImportForm): - power_port = forms.ModelChoiceField( + rear_port = forms.ModelChoiceField( queryset=RearPortTemplate.objects.all(), to_field_name='name', required=False diff --git a/netbox/dcim/tests/test_forms.py b/netbox/dcim/tests/test_forms.py index d9cf10fdb..2f333ea69 100644 --- a/netbox/dcim/tests/test_forms.py +++ b/netbox/dcim/tests/test_forms.py @@ -8,110 +8,6 @@ def get_id(model, slug): return model.objects.get(slug=slug).id -DEVICETYPE_DATA = { - 'manufacturer': 'Generic', - 'model': 'TEST-1000', - 'slug': 'test-1000', - 'u_height': 2, - 'console-ports': [ - {'name': 'Console Port 1'}, - {'name': 'Console Port 2'}, - {'name': 'Console Port 3'}, - ], - 'console-server-ports': [ - {'name': 'Console Server Port 1'}, - {'name': 'Console Server Port 2'}, - {'name': 'Console Server Port 3'}, - ], - 'power-ports': [ - {'name': 'Power Port 1'}, - {'name': 'Power Port 2'}, - {'name': 'Power Port 3'}, - ], - 'power-outlets': [ - {'name': 'Power Outlet 1', 'power_port': 'Power Port 1', 'feed_leg': POWERFEED_LEG_A}, - {'name': 'Power Outlet 2', 'power_port': 'Power Port 1', 'feed_leg': POWERFEED_LEG_A}, - {'name': 'Power Outlet 3', 'power_port': 'Power Port 1', 'feed_leg': POWERFEED_LEG_A}, - ], - 'interfaces': [ - {'name': 'Interface 1', 'type': IFACE_TYPE_1GE_FIXED, 'mgmt_only': True}, - {'name': 'Interface 2', 'type': IFACE_TYPE_1GE_FIXED}, - {'name': 'Interface 3', 'type': IFACE_TYPE_1GE_FIXED}, - ], - 'rear-ports': [ - {'name': 'Rear Port 1', 'type': PORT_TYPE_8P8C}, - {'name': 'Rear Port 2', 'type': PORT_TYPE_8P8C}, - {'name': 'Rear Port 3', 'type': PORT_TYPE_8P8C}, - ], - 'front-ports': [ - {'name': 'Front Port 1', 'type': PORT_TYPE_8P8C, 'rear_port': 'Rear Port 1'}, - {'name': 'Front Port 2', 'type': PORT_TYPE_8P8C, 'rear_port': 'Rear Port 2'}, - {'name': 'Front Port 3', 'type': PORT_TYPE_8P8C, 'rear_port': 'Rear Port 3'}, - ], - 'device-bays': [ - {'name': 'Device Bay 1'}, - {'name': 'Device Bay 2'}, - {'name': 'Device Bay 3'}, - ], -} - - -class DeviceTypeImportTestCase(TestCase): - - def setUp(self): - - Manufacturer(name='Generic', slug='generic').save() - - def test_import_devicetype_yaml(self): - - form = DeviceTypeImportForm(DEVICETYPE_DATA) - - self.assertTrue(form.is_valid(), "Form validation failed: {}".format(form.errors)) - - form.save() - dt = DeviceType.objects.get(model='TEST-1000') - - # Verify all of the components were created - # TODO: The creation of components now occurs in the view rather than the form - self.assertEqual(dt.consoleport_templates.count(), 3) - cp1 = ConsolePortTemplate.objects.first() - self.assertEqual(cp1.name, 'Console Port 1') - - self.assertEqual(dt.consoleserverport_templates.count(), 3) - csp1 = ConsoleServerPortTemplate.objects.first() - self.assertEqual(csp1.name, 'Console Server Port 1') - - self.assertEqual(dt.powerport_templates.count(), 3) - pp1 = PowerPortTemplate.objects.first() - self.assertEqual(pp1.name, 'Power Port 1') - - self.assertEqual(dt.poweroutlet_templates.count(), 3) - po1 = PowerOutletTemplate.objects.first() - self.assertEqual(po1.name, 'Power Outlet 1') - self.assertEqual(po1.power_port, pp1) - self.assertEqual(po1.feed_leg, POWERFEED_LEG_A) - - self.assertEqual(dt.interface_templates.count(), 4) - iface1 = Interface.objects.first() - self.assertEqual(iface1.name, 'Interface 1') - self.assertEqual(iface1.type, IFACE_TYPE_1GE_FIXED) - self.assertTrue(iface1.mgmt_only) - - self.assertEqual(dt.rearport_templates.count(), 3) - rp1 = RearPortTemplate.objects.first() - self.assertEqual(rp1.name, 'Rear Port 1') - - self.assertEqual(dt.frontport_templates.count(), 3) - fp1 = FrontPortTemplate.objects.first() - self.assertEqual(fp1.name, 'Front Port 1') - self.assertEqual(fp1.rear_port, rp1) - self.assertEqual(fp1.rear_port_position, 1) - - self.assertEqual(dt.devicebay_templates.count(), 3) - db1 = DeviceBayTemplate.objects.first() - self.assertEqual(db1.name, 'Device Bay 1') - - class DeviceTestCase(TestCase): fixtures = ['dcim', 'ipam'] diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index 6e34b8ae9..6af101e4c 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -3,10 +3,11 @@ import urllib.parse from django.test import Client, TestCase from django.urls import reverse -from dcim.constants import CABLE_TYPE_CAT6, IFACE_TYPE_1GE_FIXED +from dcim.constants import * from dcim.models import ( - Cable, Device, DeviceRole, DeviceType, Interface, InventoryItem, Manufacturer, Platform, Rack, RackGroup, - RackReservation, RackRole, Site, Region, VirtualChassis, + Cable, ConsolePortTemplate, ConsoleServerPortTemplate, Device, DeviceBayTemplate, DeviceRole, DeviceType, + FrontPortTemplate, Interface, InterfaceTemplate, InventoryItem, Manufacturer, Platform, PowerPortTemplate, + PowerOutletTemplate, Rack, RackGroup, RackReservation, RackRole, RearPortTemplate, Site, Region, VirtualChassis, ) from utilities.testing import create_test_user @@ -221,6 +222,132 @@ class DeviceTypeTestCase(TestCase): response = self.client.get(devicetype.get_absolute_url()) self.assertEqual(response.status_code, 200) + def test_devicetype_import(self): + + IMPORT_DATA = """ +manufacturer: Generic +model: TEST-1000 +slug: test-1000 +u_height: 2 +console-ports: + - name: Console Port 1 + - name: Console Port 2 + - name: Console Port 3 +console-server-ports: + - name: Console Server Port 1 + - name: Console Server Port 2 + - name: Console Server Port 3 +power-ports: + - name: Power Port 1 + - name: Power Port 2 + - name: Power Port 3 +power-outlets: + - name: Power Outlet 1 + power_port: Power Port 1 + feed_leg: 1 + - name: Power Outlet 2 + power_port: Power Port 1 + feed_leg: 1 + - name: Power Outlet 3 + power_port: Power Port 1 + feed_leg: 1 +interfaces: + - name: Interface 1 + type: 1000 + mgmt_only: true + - name: Interface 2 + type: 1000 + - name: Interface 3 + type: 1000 +rear-ports: + - name: Rear Port 1 + type: 1000 + - name: Rear Port 2 + type: 1000 + - name: Rear Port 3 + type: 1000 +front-ports: + - name: Front Port 1 + type: 1000 + rear_port: Rear Port 1 + - name: Front Port 2 + type: 1000 + rear_port: Rear Port 2 + - name: Front Port 3 + type: 1000 + rear_port: Rear Port 3 +device-bays: + - name: Device Bay 1 + - name: Device Bay 2 + - name: Device Bay 3 +""" + + # Create the manufacturer + Manufacturer(name='Generic', slug='generic').save() + + # Authenticate as user with necessary permissions + user = create_test_user(username='testuser2', permissions=[ + 'dcim.view_devicetype', + 'dcim.add_devicetype', + 'dcim.add_consoleporttemplate', + 'dcim.add_consoleserverporttemplate', + 'dcim.add_powerporttemplate', + 'dcim.add_poweroutlettemplate', + 'dcim.add_interfacetemplate', + 'dcim.add_frontporttemplate', + 'dcim.add_rearporttemplate', + 'dcim.add_devicebaytemplate', + ]) + self.client.force_login(user) + + form_data = { + 'data': IMPORT_DATA, + 'format': 'yaml' + } + response = self.client.post(reverse('dcim:devicetype_import'), data=form_data, follow=True) + + dt = DeviceType.objects.get(model='TEST-1000') + + # Verify all of the components were created + self.assertEqual(dt.consoleport_templates.count(), 3) + cp1 = ConsolePortTemplate.objects.first() + self.assertEqual(cp1.name, 'Console Port 1') + + self.assertEqual(dt.consoleserverport_templates.count(), 3) + csp1 = ConsoleServerPortTemplate.objects.first() + self.assertEqual(csp1.name, 'Console Server Port 1') + + self.assertEqual(dt.powerport_templates.count(), 3) + pp1 = PowerPortTemplate.objects.first() + self.assertEqual(pp1.name, 'Power Port 1') + + self.assertEqual(dt.poweroutlet_templates.count(), 3) + po1 = PowerOutletTemplate.objects.first() + self.assertEqual(po1.name, 'Power Outlet 1') + self.assertEqual(po1.power_port, pp1) + self.assertEqual(po1.feed_leg, POWERFEED_LEG_A) + + self.assertEqual(dt.interface_templates.count(), 3) + iface1 = InterfaceTemplate.objects.first() + self.assertEqual(iface1.name, 'Interface 1') + self.assertEqual(iface1.type, IFACE_TYPE_1GE_FIXED) + self.assertTrue(iface1.mgmt_only) + + self.assertEqual(dt.rearport_templates.count(), 3) + rp1 = RearPortTemplate.objects.first() + self.assertEqual(rp1.name, 'Rear Port 1') + + self.assertEqual(dt.frontport_templates.count(), 3) + fp1 = FrontPortTemplate.objects.first() + self.assertEqual(fp1.name, 'Front Port 1') + self.assertEqual(fp1.rear_port, rp1) + self.assertEqual(fp1.rear_port_position, 1) + + self.assertEqual(dt.device_bay_templates.count(), 3) + db1 = DeviceBayTemplate.objects.first() + self.assertEqual(db1.name, 'Device Bay 1') + + class DeviceRoleTestCase(TestCase): diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index e17da7353..f53e4bebb 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -445,18 +445,23 @@ class ObjectImportView(GetReturnURLMixin, View): obj = model_form.save() # Iterate through the related object forms (if any), validating and saving each instance. - for field, related_object_form in self.related_object_forms.items(): + for field_name, related_object_form in self.related_object_forms.items(): - for i, rel_obj_data in enumerate(data.get(field, list())): + for i, rel_obj_data in enumerate(data.get(field_name, list())): f = related_object_form(obj, rel_obj_data) + + for subfield_name, field in f.fields.items(): + if subfield_name not in rel_obj_data and hasattr(field, 'initial'): + f.data[subfield_name] = field.initial + if f.is_valid(): f.save() else: # Replicate errors on the related object form to the primary form for display - for field_name, errors in f.errors.items(): + for subfield_name, errors in f.errors.items(): for err in errors: - err_msg = "{}[{}] {}: {}".format(field, i, field_name, err) + err_msg = "{}[{}] {}: {}".format(field_name, i, subfield_name, err) model_form.add_error(None, err_msg) raise AbortTransaction()