From b1f15de8f1015cbb1f2a68a2b197b139016bf567 Mon Sep 17 00:00:00 2001 From: Fabian Geisberger Date: Fri, 16 Dec 2022 14:11:43 -0500 Subject: [PATCH] Allow racking of existing devices from rack view --- netbox/dcim/forms/model_forms.py | 15 +++++- netbox/dcim/tables/devices.py | 17 ++++++- netbox/dcim/tables/template_code.py | 4 ++ netbox/dcim/urls.py | 1 + netbox/dcim/views.py | 43 +++++++++++++++++ netbox/templates/dcim/device_assign.html | 48 +++++++++++++++++++ netbox/templates/dcim/device_edit.html | 16 ++++--- .../dcim/inc/device_edit_header.html | 22 +++++++++ 8 files changed, 158 insertions(+), 8 deletions(-) create mode 100644 netbox/templates/dcim/device_assign.html create mode 100644 netbox/templates/dcim/inc/device_edit_header.html diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index 1614f4bae..de53a527b 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -25,6 +25,7 @@ __all__ = ( 'ConsolePortTemplateForm', 'ConsoleServerPortForm', 'ConsoleServerPortTemplateForm', + 'DeviceAssignForm', 'DeviceBayForm', 'DeviceBayTemplateForm', 'DeviceForm', @@ -40,9 +41,9 @@ __all__ = ( 'InventoryItemTemplateForm', 'LocationForm', 'ManufacturerForm', - 'ModuleForm', 'ModuleBayForm', 'ModuleBayTemplateForm', + 'ModuleForm', 'ModuleTypeForm', 'PlatformForm', 'PopulateDeviceBayForm', @@ -474,6 +475,18 @@ class PlatformForm(NetBoxModelForm): } +class DeviceAssignForm(BootstrapMixin, forms.Form): + q = forms.CharField( + required=False, + label=_('Search'), + ) + show_racked_devices = forms.BooleanField( + required=False, + initial=False, + label=_('Show Racked Devices?'), + ) + + class DeviceForm(TenancyForm, NetBoxModelForm): region = DynamicModelChoiceField( queryset=Region.objects.all(), diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 7b8ea1ed3..b67d7a1b5 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -12,6 +12,7 @@ __all__ = ( 'CableTerminationTable', 'ConsolePortTable', 'ConsoleServerPortTable', + 'DeviceAssignTable', 'DeviceBayTable', 'DeviceConsolePortTable', 'DeviceConsoleServerPortTable', @@ -21,8 +22,8 @@ __all__ = ( 'DeviceInterfaceTable', 'DeviceInventoryItemTable', 'DeviceModuleBayTable', - 'DevicePowerPortTable', 'DevicePowerOutletTable', + 'DevicePowerPortTable', 'DeviceRearPortTable', 'DeviceRoleTable', 'DeviceTable', @@ -219,6 +220,20 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable): ) +class DeviceAssignTable(NetBoxTable): + device = tables.TemplateColumn( + template_code=DEVICE_ASSIGN_LINK, + verbose_name='Device' + ) + status = columns.ChoiceFieldColumn() + + class Meta(NetBoxTable.Meta): + model = models.Device + fields = ('device', 'status', 'device_role', 'site', 'location', 'rack', 'position') + exclude = ('id',) + orderable = False + + class DeviceImportTable(TenancyColumnsMixin, NetBoxTable): name = tables.TemplateColumn( template_code=DEVICE_LINK, diff --git a/netbox/dcim/tables/template_code.py b/netbox/dcim/tables/template_code.py index dd0581ddc..3e92fe576 100644 --- a/netbox/dcim/tables/template_code.py +++ b/netbox/dcim/tables/template_code.py @@ -24,6 +24,10 @@ DEVICE_LINK = """ {{ value|default:'Unnamed device' }} """ +DEVICE_ASSIGN_LINK = """ +{{ record }} +""" + DEVICEBAY_STATUS = """ {% if record.installed_device_id %} diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 6772f96ad..4eb2f1580 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -176,6 +176,7 @@ urlpatterns = [ # Devices path('devices/', views.DeviceListView.as_view(), name='device_list'), path('devices/add/', views.DeviceEditView.as_view(), name='device_add'), + path("devices/assign/", views.DeviceAssignView.as_view(), name="device_assign"), path('devices/import/', views.DeviceBulkImportView.as_view(), name='device_import'), path('devices/import/child-devices/', views.ChildDeviceBulkImportView.as_view(), name='device_import_child'), path('devices/edit/', views.DeviceBulkEditView.as_view(), name='device_bulk_edit'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 06a486534..c39cbc033 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1880,6 +1880,49 @@ class DeviceDeleteView(generic.ObjectDeleteView): queryset = Device.objects.all() +class DeviceAssignView(generic.ObjectView): + """ + Search for Devices to be assigned to a rack. + """ + queryset = Device.objects.all() + + def dispatch(self, request, *args, **kwargs): + + # Redirect user if a rack has not been provided + if 'rack' not in request.GET: + return redirect('dcim:device_add') + + return super().dispatch(request, *args, **kwargs) + + def get(self, request): + form = forms.DeviceAssignForm() + + return render(request, 'dcim/device_assign.html', { + 'form': form, + 'return_url': request.GET.get('return_url', ''), + }) + + def post(self, request): + form = forms.DeviceAssignForm(request.POST) + table = None + + if form.is_valid(): + rack = Rack.objects.get(pk=request.GET['rack']) + # Only get devices belonging to the same site as rack + devices = self.queryset.filter(site=rack.site) + if not form.cleaned_data['show_racked_devices']: + devices = devices.exclude(rack__isnull=False) + # Limit to 100 results + devices = filtersets.DeviceFilterSet(request.POST, devices).qs[:100] + table = tables.DeviceAssignTable(devices) + + return render(request, 'dcim/device_assign.html', { + 'form': form, + 'table': table, + 'return_url': request.GET.get('return_url'), + }) + + @register_model_view(Device, 'consoleports', path='console-ports') class DeviceConsolePortsView(DeviceComponentsView): child_model = ConsolePort diff --git a/netbox/templates/dcim/device_assign.html b/netbox/templates/dcim/device_assign.html new file mode 100644 index 000000000..4270a113a --- /dev/null +++ b/netbox/templates/dcim/device_assign.html @@ -0,0 +1,48 @@ +{% extends 'generic/object_edit.html' %} +{% load static %} +{% load form_helpers %} +{% load helpers %} +{% load render_table from django_tables2 %} + +{% block title %}Assign a Device{% endblock title %} + +{% block tabs %} + {% include 'dcim/inc/device_edit_header.html' with active_tab='assign' %} +{% endblock %} + +{% block form %} +
+ {% csrf_token %} + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} +
+
+
+
Select Device
+ {% render_field form.q %} + {% render_field form.show_racked_devices %} +
+
+
+
+
+ Cancel + +
+
+
+ {% if table %} +
+
+

Search Results

+
+ {% render_table table 'inc/table.html' %} +
+
+
+ {% endif %} +{% endblock form %} + +{% block buttons %} +{% endblock buttons%} diff --git a/netbox/templates/dcim/device_edit.html b/netbox/templates/dcim/device_edit.html index b814e65ef..7cfc60cbf 100644 --- a/netbox/templates/dcim/device_edit.html +++ b/netbox/templates/dcim/device_edit.html @@ -1,9 +1,13 @@ {% extends 'generic/object_edit.html' %} {% load form_helpers %} +{% block tabs %} + {% include 'dcim/inc/device_edit_header.html' with active_tab='add' %} +{% endblock %} + {% block form %} {% render_errors form %} - +
Device
@@ -13,7 +17,7 @@ {% render_field form.description %} {% render_field form.tags %}
- +
Hardware
@@ -24,7 +28,7 @@ {% render_field form.serial %} {% render_field form.asset_tag %}
- +
Location
@@ -58,7 +62,7 @@ {% render_field form.position %} {% endif %}
- +
Management
@@ -70,7 +74,7 @@ {% render_field form.primary_ip6 %} {% endif %}
- +
Virtualization
@@ -78,7 +82,7 @@ {% render_field form.cluster_group %} {% render_field form.cluster %}
- +
Tenancy
diff --git a/netbox/templates/dcim/inc/device_edit_header.html b/netbox/templates/dcim/inc/device_edit_header.html new file mode 100644 index 000000000..2712232c6 --- /dev/null +++ b/netbox/templates/dcim/inc/device_edit_header.html @@ -0,0 +1,22 @@ +{% load helpers %} + +