Repository: netbox-community/netbox

Issue:
Service Bulk Import Form with IP Address
### NetBox version

v3.4.0

### Feature type

New functionality

### Proposed functionality

Add IP Address to the Bulk Import Form of Service

### Use case

The import function of service would be complete

### Database changes

_No response_

### External dependencies

_No response_


Discussion:
assign to me

Resolving patch:
diff --git a/netbox/ipam/forms/bulk_import.py b/netbox/ipam/forms/bulk_import.py
--- a/netbox/ipam/forms/bulk_import.py
+++ b/netbox/ipam/forms/bulk_import.py
@@ -507,10 +507,28 @@ class ServiceImportForm(NetBoxModelImportForm):
         choices=ServiceProtocolChoices,
         help_text=_('IP protocol')
     )
+    ipaddresses = CSVModelMultipleChoiceField(
+        queryset=IPAddress.objects.all(),
+        required=False,
+        to_field_name='address',
+        help_text=_('IP Address'),
+    )
 
     class Meta:
         model = Service
-        fields = ('device', 'virtual_machine', 'name', 'protocol', 'ports', 'description', 'comments', 'tags')
+        fields = (
+            'device', 'virtual_machine', 'ipaddresses', 'name', 'protocol', 'ports', 'description', 'comments', 'tags',
+        )
+
+    def clean_ipaddresses(self):
+        parent = self.cleaned_data.get('device') or self.cleaned_data.get('virtual_machine')
+        for ip_address in self.cleaned_data['ipaddresses']:
+            if not ip_address.assigned_object or getattr(ip_address.assigned_object, 'parent_object') != parent:
+                raise forms.ValidationError(
+                    _("{ip} is not assigned to this device/VM.").format(ip=ip_address)
+                )
+
+        return self.cleaned_data['ipaddresses']
 
 
 class L2VPNImportForm(NetBoxModelImportForm):


Test patch:
diff --git a/netbox/ipam/tests/test_views.py b/netbox/ipam/tests/test_views.py
--- a/netbox/ipam/tests/test_views.py
+++ b/netbox/ipam/tests/test_views.py
@@ -4,6 +4,7 @@
 from django.urls import reverse
 from netaddr import IPNetwork
 
+from dcim.constants import InterfaceTypeChoices
 from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site, Interface
 from ipam.choices import *
 from ipam.models import *
@@ -911,6 +912,7 @@ def setUpTestData(cls):
         devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1')
         role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
         device = Device.objects.create(name='Device 1', site=site, device_type=devicetype, role=role)
+        interface = Interface.objects.create(device=device, name='Interface 1', type=InterfaceTypeChoices.TYPE_VIRTUAL)
 
         services = (
             Service(device=device, name='Service 1', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[101]),
@@ -919,6 +921,12 @@ def setUpTestData(cls):
         )
         Service.objects.bulk_create(services)
 
+        ip_addresses = (
+            IPAddress(assigned_object=interface, address='192.0.2.1/24'),
+            IPAddress(assigned_object=interface, address='192.0.2.2/24'),
+        )
+        IPAddress.objects.bulk_create(ip_addresses)
+
         tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
         cls.form_data = {
@@ -933,10 +941,10 @@ def setUpTestData(cls):
         }
 
         cls.csv_data = (
-            "device,name,protocol,ports,description",
-            "Device 1,Service 1,tcp,1,First service",
-            "Device 1,Service 2,tcp,2,Second service",
-            "Device 1,Service 3,udp,3,Third service",
+            "device,name,protocol,ports,ipaddresses,description",
+            "Device 1,Service 1,tcp,1,192.0.2.1/24,First service",
+            "Device 1,Service 2,tcp,2,192.0.2.2/24,Second service",
+            "Device 1,Service 3,udp,3,,Third service",
         )
 
         cls.csv_update_data = (