Allow racking of existing devices from rack view

This commit is contained in:
Fabian Geisberger 2022-12-16 14:11:43 -05:00
parent c8f4a7c742
commit b1f15de8f1
8 changed files with 158 additions and 8 deletions

View File

@ -25,6 +25,7 @@ __all__ = (
'ConsolePortTemplateForm', 'ConsolePortTemplateForm',
'ConsoleServerPortForm', 'ConsoleServerPortForm',
'ConsoleServerPortTemplateForm', 'ConsoleServerPortTemplateForm',
'DeviceAssignForm',
'DeviceBayForm', 'DeviceBayForm',
'DeviceBayTemplateForm', 'DeviceBayTemplateForm',
'DeviceForm', 'DeviceForm',
@ -40,9 +41,9 @@ __all__ = (
'InventoryItemTemplateForm', 'InventoryItemTemplateForm',
'LocationForm', 'LocationForm',
'ManufacturerForm', 'ManufacturerForm',
'ModuleForm',
'ModuleBayForm', 'ModuleBayForm',
'ModuleBayTemplateForm', 'ModuleBayTemplateForm',
'ModuleForm',
'ModuleTypeForm', 'ModuleTypeForm',
'PlatformForm', 'PlatformForm',
'PopulateDeviceBayForm', '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): class DeviceForm(TenancyForm, NetBoxModelForm):
region = DynamicModelChoiceField( region = DynamicModelChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),

View File

@ -12,6 +12,7 @@ __all__ = (
'CableTerminationTable', 'CableTerminationTable',
'ConsolePortTable', 'ConsolePortTable',
'ConsoleServerPortTable', 'ConsoleServerPortTable',
'DeviceAssignTable',
'DeviceBayTable', 'DeviceBayTable',
'DeviceConsolePortTable', 'DeviceConsolePortTable',
'DeviceConsoleServerPortTable', 'DeviceConsoleServerPortTable',
@ -21,8 +22,8 @@ __all__ = (
'DeviceInterfaceTable', 'DeviceInterfaceTable',
'DeviceInventoryItemTable', 'DeviceInventoryItemTable',
'DeviceModuleBayTable', 'DeviceModuleBayTable',
'DevicePowerPortTable',
'DevicePowerOutletTable', 'DevicePowerOutletTable',
'DevicePowerPortTable',
'DeviceRearPortTable', 'DeviceRearPortTable',
'DeviceRoleTable', 'DeviceRoleTable',
'DeviceTable', '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): class DeviceImportTable(TenancyColumnsMixin, NetBoxTable):
name = tables.TemplateColumn( name = tables.TemplateColumn(
template_code=DEVICE_LINK, template_code=DEVICE_LINK,

View File

@ -24,6 +24,10 @@ DEVICE_LINK = """
{{ value|default:'<span class="badge bg-info">Unnamed device</span>' }} {{ value|default:'<span class="badge bg-info">Unnamed device</span>' }}
""" """
DEVICE_ASSIGN_LINK = """
<a href="{% url 'dcim:device_edit' pk=record.pk %}?{% if request.GET.rack %}rack={{ request.GET.rack }}&face={{ request.GET.face }}&position={{ request.GET.position }}{% endif %}&return_url={{ request.GET.return_url }}">{{ record }}</a>
"""
DEVICEBAY_STATUS = """ DEVICEBAY_STATUS = """
{% if record.installed_device_id %} {% if record.installed_device_id %}
<span class="badge bg-{{ record.installed_device.get_status_color }}"> <span class="badge bg-{{ record.installed_device.get_status_color }}">

View File

@ -176,6 +176,7 @@ urlpatterns = [
# Devices # Devices
path('devices/', views.DeviceListView.as_view(), name='device_list'), path('devices/', views.DeviceListView.as_view(), name='device_list'),
path('devices/add/', views.DeviceEditView.as_view(), name='device_add'), 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/', views.DeviceBulkImportView.as_view(), name='device_import'),
path('devices/import/child-devices/', views.ChildDeviceBulkImportView.as_view(), name='device_import_child'), path('devices/import/child-devices/', views.ChildDeviceBulkImportView.as_view(), name='device_import_child'),
path('devices/edit/', views.DeviceBulkEditView.as_view(), name='device_bulk_edit'), path('devices/edit/', views.DeviceBulkEditView.as_view(), name='device_bulk_edit'),

View File

@ -1880,6 +1880,49 @@ class DeviceDeleteView(generic.ObjectDeleteView):
queryset = Device.objects.all() 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') @register_model_view(Device, 'consoleports', path='console-ports')
class DeviceConsolePortsView(DeviceComponentsView): class DeviceConsolePortsView(DeviceComponentsView):
child_model = ConsolePort child_model = ConsolePort

View File

@ -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 %}
<form action="{% querystring request %}" method="post" class="form form-horizontal">
{% csrf_token %}
{% for field in form.hidden_fields %}
{{ field }}
{% endfor %}
<div class="row mb-3">
<div class="col col-md-8 offset-md-2">
<div class="field-group">
<h6>Select Device</h6>
{% render_field form.q %}
{% render_field form.show_racked_devices %}
</div>
</div>
</div>
<div class="row mb-3">
<div class="col col-md-8 offset-md-2 text-end">
<a href="{{ return_url }}" class="btn btn-outline-danger">Cancel</a>
<button type="submit" class="btn btn-primary">Search</button>
</div>
</div>
</form>
{% if table %}
<div class="row mb-3">
<div class="col col-md-12">
<h3>Search Results</h3>
<div class="table-responsive">
{% render_table table 'inc/table.html' %}
</div>
</div>
</div>
{% endif %}
{% endblock form %}
{% block buttons %}
{% endblock buttons%}

View File

@ -1,9 +1,13 @@
{% extends 'generic/object_edit.html' %} {% extends 'generic/object_edit.html' %}
{% load form_helpers %} {% load form_helpers %}
{% block tabs %}
{% include 'dcim/inc/device_edit_header.html' with active_tab='add' %}
{% endblock %}
{% block form %} {% block form %}
{% render_errors form %} {% render_errors form %}
<div class="field-group my-5"> <div class="field-group my-5">
<div class="row mb-2"> <div class="row mb-2">
<h5 class="offset-sm-3">Device</h5> <h5 class="offset-sm-3">Device</h5>
@ -13,7 +17,7 @@
{% render_field form.description %} {% render_field form.description %}
{% render_field form.tags %} {% render_field form.tags %}
</div> </div>
<div class="field-group my-5"> <div class="field-group my-5">
<div class="row mb-2"> <div class="row mb-2">
<h5 class="offset-sm-3">Hardware</h5> <h5 class="offset-sm-3">Hardware</h5>
@ -24,7 +28,7 @@
{% render_field form.serial %} {% render_field form.serial %}
{% render_field form.asset_tag %} {% render_field form.asset_tag %}
</div> </div>
<div class="field-group my-5"> <div class="field-group my-5">
<div class="row mb-2"> <div class="row mb-2">
<h5 class="offset-sm-3">Location</h5> <h5 class="offset-sm-3">Location</h5>
@ -58,7 +62,7 @@
{% render_field form.position %} {% render_field form.position %}
{% endif %} {% endif %}
</div> </div>
<div class="field-group my-5"> <div class="field-group my-5">
<div class="row mb-2"> <div class="row mb-2">
<h5 class="offset-sm-3">Management</h5> <h5 class="offset-sm-3">Management</h5>
@ -70,7 +74,7 @@
{% render_field form.primary_ip6 %} {% render_field form.primary_ip6 %}
{% endif %} {% endif %}
</div> </div>
<div class="field-group my-5"> <div class="field-group my-5">
<div class="row mb-2"> <div class="row mb-2">
<h5 class="offset-sm-3">Virtualization</h5> <h5 class="offset-sm-3">Virtualization</h5>
@ -78,7 +82,7 @@
{% render_field form.cluster_group %} {% render_field form.cluster_group %}
{% render_field form.cluster %} {% render_field form.cluster %}
</div> </div>
<div class="field-group my-5"> <div class="field-group my-5">
<div class="row mb-2"> <div class="row mb-2">
<h5 class="offset-sm-3">Tenancy</h5> <h5 class="offset-sm-3">Tenancy</h5>

View File

@ -0,0 +1,22 @@
{% load helpers %}
<ul class="nav nav-tabs px-3">
<li class="nav-item">
<a
class="nav-link {% if active_tab == 'add' %}active{% endif %}"
href="{% url 'dcim:device_add' %}{% querystring request %}"
>
{% if obj.pk %}Edit{% else %}Create{% endif %}
</a>
</li>
{% if 'rack' in request.GET %}
<li class="nav-item">
<a
class="nav-link {% if active_tab == 'assign' %}active{% endif %}"
href="{% url 'dcim:device_assign' %}{% querystring request %}"
>
Assign Device
</a>
</li>
{% endif %}
</ul>