From 46e3883f19a8cb25230910e5cca6f6f9e6e6d704 Mon Sep 17 00:00:00 2001
From: jeremystretch <jstretch@ns1.com>
Date: Fri, 18 Nov 2022 15:22:24 -0500
Subject: [PATCH] Closes #815: Enable specifying terminations when bulk
 importing circuits

---
 docs/release-notes/version-3.4.md         |  4 ++++
 netbox/circuits/forms/bulk_import.py      | 26 ++++++++++++++++++++++-
 netbox/circuits/tests/test_views.py       |  7 ++++++
 netbox/circuits/views.py                  | 10 +++++++++
 netbox/netbox/views/generic/bulk_views.py |  2 +-
 5 files changed, 47 insertions(+), 2 deletions(-)

diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md
index ee07fa83c..651dc4f82 100644
--- a/docs/release-notes/version-3.4.md
+++ b/docs/release-notes/version-3.4.md
@@ -2,6 +2,10 @@
 
 ## v3.4.0 (FUTURE)
 
+### Enhancements
+
+* [#815](https://github.com/netbox-community/netbox/issues/815) - Enable specifying terminations when bulk importing circuits
+
 ### Bug Fixes
 
 * [#10946](https://github.com/netbox-community/netbox/issues/10946) - Fix AttributeError exception when viewing a device with a primary IP and no platform assigned
diff --git a/netbox/circuits/forms/bulk_import.py b/netbox/circuits/forms/bulk_import.py
index 9566c2e12..b61fb1bc7 100644
--- a/netbox/circuits/forms/bulk_import.py
+++ b/netbox/circuits/forms/bulk_import.py
@@ -1,12 +1,16 @@
+from django import forms
+
 from circuits.choices import CircuitStatusChoices
 from circuits.models import *
+from dcim.models import Site
 from django.utils.translation import gettext as _
 from netbox.forms import NetBoxModelImportForm
 from tenancy.models import Tenant
-from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
+from utilities.forms import BootstrapMixin, CSVChoiceField, CSVModelChoiceField, SlugField
 
 __all__ = (
     'CircuitImportForm',
+    'CircuitTerminationImportForm',
     'CircuitTypeImportForm',
     'ProviderImportForm',
     'ProviderNetworkImportForm',
@@ -76,3 +80,23 @@ class CircuitImportForm(NetBoxModelImportForm):
             'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'termination_date', 'commit_rate',
             'description', 'comments', 'tags'
         ]
+
+
+class CircuitTerminationImportForm(BootstrapMixin, forms.ModelForm):
+    site = CSVModelChoiceField(
+        queryset=Site.objects.all(),
+        to_field_name='name',
+        required=False
+    )
+    provider_network = CSVModelChoiceField(
+        queryset=ProviderNetwork.objects.all(),
+        to_field_name='name',
+        required=False
+    )
+
+    class Meta:
+        model = CircuitTermination
+        fields = [
+            'circuit', 'term_side', 'site', 'provider_network', 'port_speed', 'upstream_speed', 'xconnect_id',
+            'pp_info', 'description',
+        ]
diff --git a/netbox/circuits/tests/test_views.py b/netbox/circuits/tests/test_views.py
index 54d001c8d..231d6a43c 100644
--- a/netbox/circuits/tests/test_views.py
+++ b/netbox/circuits/tests/test_views.py
@@ -108,6 +108,13 @@ class CircuitTypeTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
 class CircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = Circuit
 
+    def setUp(self):
+        super().setUp()
+
+        self.add_permissions(
+            'circuits.add_circuittermination',
+        )
+
     @classmethod
     def setUpTestData(cls):
 
diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py
index 5fe8eb7b7..3168509ba 100644
--- a/netbox/circuits/views.py
+++ b/netbox/circuits/views.py
@@ -233,6 +233,16 @@ class CircuitBulkImportView(generic.BulkImportView):
     queryset = Circuit.objects.all()
     model_form = forms.CircuitImportForm
     table = tables.CircuitTable
+    additional_permissions = [
+        'circuits.add_circuittermination',
+    ]
+    related_object_forms = {
+        'terminations': forms.CircuitTerminationImportForm,
+    }
+
+    def prep_related_object_data(self, parent, data):
+        data.update({'circuit': parent})
+        return data
 
 
 class CircuitBulkEditView(generic.BulkEditView):
diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py
index 444004623..445bfa261 100644
--- a/netbox/netbox/views/generic/bulk_views.py
+++ b/netbox/netbox/views/generic/bulk_views.py
@@ -458,7 +458,7 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
                         'return_url': self.get_return_url(request),
                     })
 
-            except ValidationError:
+            except (AbortTransaction, ValidationError):
                 clear_webhooks.send(sender=self)
 
             except (AbortRequest, PermissionsViolation) as e: