diff --git a/netbox/dcim/forms/models.py b/netbox/dcim/forms/models.py index ca9aa6d3a..88f0cffe2 100644 --- a/netbox/dcim/forms/models.py +++ b/netbox/dcim/forms/models.py @@ -29,6 +29,7 @@ __all__ = ( 'DeviceBayForm', 'DeviceBayTemplateForm', 'DeviceForm', + 'DeviceAssignForm', 'DeviceRoleForm', 'DeviceTypeForm', 'DeviceVCMembershipForm', @@ -631,6 +632,23 @@ class DeviceForm(TenancyForm, CustomFieldModelForm): self.fields['position'].widget.choices = [(position, f'U{position}')] +class DeviceAssignForm(BootstrapMixin, forms.Form): + rack_id = DynamicModelMultipleChoiceField( + queryset=Rack.objects.all(), + required=False, + null_option='None', + query_params={ + 'site_id': '$site_id', + 'location_id': '$location_id', + }, + label=_('Rack') + ) + q = forms.CharField( + required=False, + label='Search', + ) + + class CableForm(TenancyForm, CustomFieldModelForm): tags = DynamicModelMultipleChoiceField( queryset=Tag.objects.all(), diff --git a/netbox/dcim/svg.py b/netbox/dcim/svg.py index e333320b6..2e1c68f78 100644 --- a/netbox/dcim/svg.py +++ b/netbox/dcim/svg.py @@ -163,8 +163,9 @@ class RackElevationSVG: 'position': id_ }) ) + return_url = "&return_url={}".format(rack.get_absolute_url()) link = drawing.add( - drawing.a(href=link_url, target='_top') + drawing.a(href=link_url+"{}".format(return_url), target='_top') ) if reservation: link.set_desc('{} — {} · {}'.format( diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 3c2b3dace..21bdb4782 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -30,6 +30,7 @@ __all__ = ( 'DeviceRearPortTable', 'DeviceRoleTable', 'DeviceTable', + 'DeviceAssignTable', 'FrontPortTable', 'InterfaceTable', 'InventoryItemTable', @@ -213,6 +214,40 @@ class DeviceTable(BaseTable): ) +class DeviceAssignTable(BaseTable): + name = tables.TemplateColumn( + template_code=DEVICE_ASSIGN_LINK, + verbose_name='Name' + ) + status = ChoiceFieldColumn() + tenant = TenantColumn() + site = tables.Column() + location = tables.Column() + rack = tables.Column() + device_role = ColoredLabelColumn( + verbose_name='Role' + ) + manufacturer = tables.Column( + accessor=Accessor('device_type__manufacturer'), + ) + device_type = tables.Column( + verbose_name='Type' + ) + primary_ip = tables.Column( + order_by=('primary_ip4', 'primary_ip6'), + verbose_name='IP Address' + ) + + class Meta(BaseTable.Meta): + model = Device + fields = ( + 'name', 'status', 'tenant', 'site', 'location', 'rack', 'device_role', 'manufacturer', 'device_type', + 'primary_ip', + ) + exclude = ('id', ) + orderable = False + + class DeviceImportTable(BaseTable): name = tables.TemplateColumn( template_code=DEVICE_LINK diff --git a/netbox/dcim/tables/template_code.py b/netbox/dcim/tables/template_code.py index 1d68c466a..8cf21a247 100644 --- a/netbox/dcim/tables/template_code.py +++ b/netbox/dcim/tables/template_code.py @@ -94,6 +94,14 @@ LOCATION_ELEVATIONS = """ """ +# +# Device table +# + +DEVICE_ASSIGN_LINK = """ +{{ record }} +""" + # # Device component buttons # diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 51d91d7c9..28fbf6890 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -210,6 +210,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 cee516f5c..b4e82dd23 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1463,6 +1463,46 @@ class DeviceEditView(generic.ObjectEditView): model_form = forms.DeviceForm template_name = 'dcim/device_edit.html' +class DeviceAssignView(generic.ObjectEditView): + """ + Search for Devices to be assigned to an Rack. + """ + queryset = Device.objects.all() + + def dispatch(self, request, *args, **kwargs): + + # Redirect user if an interface has not been provided + if 'rack' not in request.GET: + return redirect('ipam:ipaddress_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(): + + devices = self.queryset.prefetch_related('rack') + print(devices[0].rack.pk) + # 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'), + }) + class DeviceDeleteView(generic.ObjectDeleteView): queryset = Device.objects.all() diff --git a/netbox/templates/dcim/device_assign.html b/netbox/templates/dcim/device_assign.html new file mode 100644 index 000000000..411f7db1f --- /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 %} +
+ {% if table %} +