diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 9e2cf5322..da7b583a6 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -3450,6 +3450,56 @@ class PopulateDeviceBayForm(BootstrapMixin, forms.Form): ).exclude(pk=device_bay.device.pk) +class DeviceBayCSVForm(forms.ModelForm): + device = FlexibleModelChoiceField( + queryset=Device.objects.all(), + to_field_name='name', + help_text='Name or ID of device', + error_messages={ + 'invalid_choice': 'Device not found.', + } + ) + installed_device = FlexibleModelChoiceField( + queryset=Device.objects.all(), + required=False, + to_field_name='name', + help_text='Name or ID of device', + error_messages={ + 'invalid_choice': 'Child device not found.', + } + ) + + class Meta: + model = DeviceBay + fields = DeviceBay.csv_headers + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Limit installed device choices to devices of the correct type and location + if self.is_bound: + try: + device = self.fields['device'].to_python(self.data['device']) + except forms.ValidationError: + device = None + else: + try: + device = self.instance.device + except Device.DoesNotExist: + device = None + + if device: + self.fields['installed_device'].queryset = Device.objects.filter( + site=device.site, + rack=device.rack, + parent_bay__isnull=True, + device_type__u_height=0, + device_type__subdevice_role=SUBDEVICE_ROLE_CHILD + ).exclude(pk=device.pk) + else: + self.fields['installed_device'].queryset = Interface.objects.none() + + class DeviceBayBulkRenameForm(BulkRenameForm): pk = forms.ModelMultipleChoiceField( queryset=DeviceBay.objects.all(), diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index 285b864a2..a9339b938 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -765,6 +765,16 @@ class DeviceBayTable(BaseTable): fields = ('name',) +class DeviceBayImportTable(BaseTable): + device = tables.LinkColumn('dcim:device', args=[Accessor('device.pk')], verbose_name='Device') + installed_device = tables.LinkColumn('dcim:device', args=[Accessor('installed_device.pk')], verbose_name='Installed Device') + + class Meta(BaseTable.Meta): + model = DeviceBay + fields = ('device', 'name', 'installed_device', 'description') + empty_text = False + + # # Cables # diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 77bd522bc..712134f6b 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -263,6 +263,7 @@ urlpatterns = [ path(r'device-bays//populate/', views.DeviceBayPopulateView.as_view(), name='devicebay_populate'), path(r'device-bays//depopulate/', views.DeviceBayDepopulateView.as_view(), name='devicebay_depopulate'), path(r'device-bays/rename/', views.DeviceBayBulkRenameView.as_view(), name='devicebay_bulk_rename'), + path(r'device-bays/import/', views.DeviceBayBulkImportView.as_view(), name='devicebay_import'), # Inventory items path(r'inventory-items/', views.InventoryItemListView.as_view(), name='inventoryitem_list'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index e640ca07f..2e59223e4 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1685,6 +1685,14 @@ class DeviceBayDepopulateView(PermissionRequiredMixin, View): }) +class DeviceBayBulkImportView(PermissionRequiredMixin, BulkImportView): + permission_required = 'dcim.add_devicebay' + model_form = forms.DeviceBayCSVForm + table = tables.DeviceBayImportTable + # TODO: change after netbox-community#3564 has been implemented + # default_return_url = 'dcim:devicebay_list' + + class DeviceBayBulkRenameView(PermissionRequiredMixin, BulkRenameView): permission_required = 'dcim.change_devicebay' queryset = DeviceBay.objects.all()