Browse Source

Address review findings

Arthur 9 giờ trước cách đây
mục cha
commit
b2775bb3d3

+ 66 - 0
netbox/circuits/tests/test_views.py

@@ -263,6 +263,37 @@ class CircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase):
         self.assertHttpStatus(self.client.post(self._get_url('bulk_import'), data), 302)
         self.assertEqual(self._get_queryset().count(), initial_count + 1)
 
+    def test_bulk_import_objects_without_termination_permission(self):
+        """
+        Importing circuits requires add_circuittermination only when terminations are included.
+        """
+        self.remove_permissions('circuits.add_circuittermination')
+        self.add_permissions(
+            'circuits.add_circuit',
+            'circuits.view_provider',
+            'circuits.view_circuittype',
+            'dcim.view_site',
+        )
+        url = self._get_url('bulk_import')
+        self.assertHttpStatus(self.client.get(url), 200)
+
+        data = {
+            'data': '[{"cid": "Circuit 8", "provider": "Provider 1", "type": "Circuit Type 1", "status": "active"}]',
+            'format': ImportFormatChoices.JSON,
+        }
+        self.assertHttpStatus(self.client.post(url, data), 302)
+        self.assertTrue(Circuit.objects.filter(cid='Circuit 8').exists())
+
+        site = Site.objects.first()
+        data['data'] = (
+            '[{"cid": "Circuit 9", "provider": "Provider 1", "type": "Circuit Type 1", "status": "active", '
+            f'"terminations": [{{"term_side": "A", "termination_type": "dcim.site", "termination_id": {site.pk}}}]}}]'
+        )
+        response = self.client.post(url, data)
+        self.assertHttpStatus(response, 200)
+        self.assertContains(response, 'circuits.add_circuittermination')
+        self.assertFalse(Circuit.objects.filter(cid='Circuit 9').exists())
+
 
 class ProviderAccountTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = ProviderAccount
@@ -775,6 +806,41 @@ class VirtualCircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase):
         self.assertHttpStatus(self.client.post(self._get_url('bulk_import'), data), 302)
         self.assertEqual(self._get_queryset().count(), initial_count + 1)
 
+    def test_bulk_import_objects_without_termination_permission(self):
+        """
+        Importing virtual circuits requires add_virtualcircuittermination only when terminations are included.
+        """
+        self.remove_permissions('circuits.add_virtualcircuittermination')
+        self.add_permissions(
+            'circuits.add_virtualcircuit',
+            'circuits.view_providernetwork',
+            'circuits.view_virtualcircuittype',
+            'dcim.view_interface',
+        )
+        url = self._get_url('bulk_import')
+        self.assertHttpStatus(self.client.get(url), 200)
+
+        data = {
+            'data': (
+                '[{"cid": "Virtual Circuit 8", "provider_network": "Provider Network 1", '
+                '"type": "Virtual Circuit Type 1", "status": "active"}]'
+            ),
+            'format': ImportFormatChoices.JSON,
+        }
+        self.assertHttpStatus(self.client.post(url, data), 302)
+        self.assertTrue(VirtualCircuit.objects.filter(cid='Virtual Circuit 8').exists())
+
+        interface = Interface.objects.filter(type=InterfaceTypeChoices.TYPE_VIRTUAL).first()
+        data['data'] = (
+            '[{"cid": "Virtual Circuit 9", "provider_network": "Provider Network 1", '
+            '"type": "Virtual Circuit Type 1", "status": "active", '
+            f'"terminations": [{{"role": "hub", "interface": {interface.pk}}}]}}]'
+        )
+        response = self.client.post(url, data)
+        self.assertHttpStatus(response, 200)
+        self.assertContains(response, 'circuits.add_virtualcircuittermination')
+        self.assertFalse(VirtualCircuit.objects.filter(cid='Virtual Circuit 9').exists())
+
 
 class VirtualCircuitTerminationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = VirtualCircuitTermination

+ 0 - 6
netbox/circuits/views.py

@@ -468,9 +468,6 @@ class CircuitDeleteView(generic.ObjectDeleteView):
 class CircuitBulkImportView(generic.BulkImportView):
     queryset = Circuit.objects.all()
     model_form = forms.CircuitImportForm
-    additional_permissions = [
-        'circuits.add_circuittermination',
-    ]
     related_object_forms = {
         'terminations': forms.CircuitTerminationImportRelatedForm,
     }
@@ -858,9 +855,6 @@ class VirtualCircuitDeleteView(generic.ObjectDeleteView):
 class VirtualCircuitBulkImportView(generic.BulkImportView):
     queryset = VirtualCircuit.objects.all()
     model_form = forms.VirtualCircuitImportForm
-    additional_permissions = [
-        'circuits.add_virtualcircuittermination',
-    ]
     related_object_forms = {
         'terminations': forms.VirtualCircuitTerminationImportRelatedForm,
     }

+ 25 - 0
netbox/dcim/tests/test_views.py

@@ -2038,6 +2038,31 @@ module-bays:
         self.assertEqual(mb1.position, '1')
         self.assertEqual(list(mb1.module_bay_types.values_list('name', flat=True)), ['SFP28'])
 
+    def test_import_objects_without_component_permissions(self):
+        """
+        Importing a module type requires only add_moduletype unless component templates are included.
+        """
+        self.add_permissions('dcim.view_manufacturer', 'dcim.add_moduletype')
+        url = reverse('dcim:moduletype_bulk_import')
+
+        self.assertHttpStatus(self.client.get(url), 200)
+
+        import_data = "manufacturer: Manufacturer 1\nmodel: TEST-2000\n"
+        response = self.client.post(url, data={'data': import_data, 'format': 'yaml'})
+        self.assertHttpStatus(response, 302)
+        self.assertTrue(ModuleType.objects.filter(model='TEST-2000').exists())
+
+        # Including component templates requires the corresponding add permission
+        import_data = (
+            "manufacturer: Manufacturer 1\nmodel: TEST-3000\n"
+            "interfaces:\n  - name: Interface 1\n    type: 1000base-t\n"
+        )
+        response = self.client.post(url, data={'data': import_data, 'format': 'yaml'})
+        self.assertHttpStatus(response, 200)
+        self.assertContains(response, 'dcim.add_interfacetemplate')
+        self.assertFalse(ModuleType.objects.filter(model='TEST-3000').exists())
+        self.assertFalse(InterfaceTemplate.objects.exists())
+
     def test_import_channelized_interfaces(self):
         """
         A channel subinterface template resolves its parent by name within the module type being imported.