mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-08 00:28:16 -06:00
Fixes #1830: Support assgin an existing device to a rack
This commit is contained in:
parent
1a8eea5aa9
commit
7245922ea7
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
## Enhancements
|
## Enhancements
|
||||||
|
|
||||||
|
* [#1830](https://github.com/netbox-community/netbox/issues/1830) - Support Assgin existing device to a rack with 'add device' button
|
||||||
* [#3840](https://github.com/netbox-community/netbox/issues/3840) - Enhance search function when selecting VLANs for interface assignment
|
* [#3840](https://github.com/netbox-community/netbox/issues/3840) - Enhance search function when selecting VLANs for interface assignment
|
||||||
* [#4170](https://github.com/netbox-community/netbox/issues/4170) - Improve color contrast in rack elevation drawings
|
* [#4170](https://github.com/netbox-community/netbox/issues/4170) - Improve color contrast in rack elevation drawings
|
||||||
|
|
||||||
|
@ -2137,6 +2137,13 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceAssignForm(BootstrapMixin, forms.Form):
|
||||||
|
q = forms.CharField(
|
||||||
|
required=False,
|
||||||
|
label='Search',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldFilterForm):
|
class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldFilterForm):
|
||||||
model = Device
|
model = Device
|
||||||
field_order = [
|
field_order = [
|
||||||
|
@ -423,9 +423,10 @@ class RackElevationHelperMixin:
|
|||||||
def _draw_empty(drawing, rack, start, end, text, id_, face_id, class_, reservation):
|
def _draw_empty(drawing, rack, start, end, text, id_, face_id, class_, reservation):
|
||||||
link = drawing.add(
|
link = drawing.add(
|
||||||
drawing.a(
|
drawing.a(
|
||||||
href='{}?{}'.format(
|
href='{}?{}&return_url=/dcim/racks/{}'.format(
|
||||||
reverse('dcim:device_add'),
|
reverse('dcim:device_add'),
|
||||||
urlencode({'rack': rack.pk, 'site': rack.site.pk, 'face': face_id, 'position': id_})
|
urlencode({'rack': rack.pk, 'site': rack.site.pk, 'face': face_id, 'position': id_}),
|
||||||
|
rack.pk,
|
||||||
),
|
),
|
||||||
target='_top'
|
target='_top'
|
||||||
)
|
)
|
||||||
|
@ -40,6 +40,14 @@ DEVICE_LINK = """
|
|||||||
</a>
|
</a>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
DEVICE_ASSIGN_LINK = """
|
||||||
|
{% if request.GET %}
|
||||||
|
<a href="{% url 'dcim:device_edit' pk=record.pk %}?rack={{ request.GET.rack }}&site={{ request.GET.site }}&face={{ request.GET.face }}&position={{ request.GET.position }}&return_url={{ request.GET.return_url }}">{{ record }}</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'dcim:device_edit' pk=record.pk %}?rack={{ record.rack.pk }}&site={{ record.site.pk }}&face={{ record.face.pk }}&position={{ record.position.pk }}&return_url={{ request.GET.return_url }}">{{ record }}</a>
|
||||||
|
{% endif %}
|
||||||
|
"""
|
||||||
|
|
||||||
REGION_ACTIONS = """
|
REGION_ACTIONS = """
|
||||||
<a href="{% url 'dcim:region_changelog' pk=record.pk %}" class="btn btn-default btn-xs" title="Changelog">
|
<a href="{% url 'dcim:region_changelog' pk=record.pk %}" class="btn btn-default btn-xs" title="Changelog">
|
||||||
<i class="fa fa-history"></i>
|
<i class="fa fa-history"></i>
|
||||||
@ -694,6 +702,28 @@ class DeviceDetailTable(DeviceTable):
|
|||||||
fields = ('pk', 'name', 'status', 'tenant', 'site', 'rack', 'device_role', 'device_type', 'primary_ip')
|
fields = ('pk', 'name', 'status', 'tenant', 'site', 'rack', 'device_role', 'device_type', 'primary_ip')
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceAssignTable(BaseTable):
|
||||||
|
pk = ToggleColumn()
|
||||||
|
name = tables.TemplateColumn(
|
||||||
|
order_by=('_name',),
|
||||||
|
template_code=DEVICE_ASSIGN_LINK
|
||||||
|
)
|
||||||
|
status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
|
||||||
|
tenant = tables.TemplateColumn(template_code=COL_TENANT)
|
||||||
|
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
|
||||||
|
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')])
|
||||||
|
device_role = tables.TemplateColumn(DEVICE_ROLE, verbose_name='Role')
|
||||||
|
device_type = tables.LinkColumn(
|
||||||
|
'dcim:devicetype', args=[Accessor('device_type.pk')], verbose_name='Type',
|
||||||
|
text=lambda record: record.device_type.display_name
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(BaseTable.Meta):
|
||||||
|
model = Device
|
||||||
|
fields = ('pk', 'name', 'status', 'tenant', 'site', 'rack', 'device_role', 'device_type', 'primary_ip')
|
||||||
|
orderable = False
|
||||||
|
|
||||||
|
|
||||||
class DeviceImportTable(BaseTable):
|
class DeviceImportTable(BaseTable):
|
||||||
name = tables.TemplateColumn(template_code=DEVICE_LINK, verbose_name='Name')
|
name = tables.TemplateColumn(template_code=DEVICE_LINK, verbose_name='Name')
|
||||||
status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
|
status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
|
||||||
|
@ -169,6 +169,7 @@ urlpatterns = [
|
|||||||
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'),
|
||||||
path('devices/delete/', views.DeviceBulkDeleteView.as_view(), name='device_bulk_delete'),
|
path('devices/delete/', views.DeviceBulkDeleteView.as_view(), name='device_bulk_delete'),
|
||||||
|
path('device/assign/', views.DeviceAssignView.as_view(), name='device_assign'),
|
||||||
path('devices/<int:pk>/', views.DeviceView.as_view(), name='device'),
|
path('devices/<int:pk>/', views.DeviceView.as_view(), name='device'),
|
||||||
path('devices/<int:pk>/edit/', views.DeviceEditView.as_view(), name='device_edit'),
|
path('devices/<int:pk>/edit/', views.DeviceEditView.as_view(), name='device_edit'),
|
||||||
path('devices/<int:pk>/delete/', views.DeviceDeleteView.as_view(), name='device_delete'),
|
path('devices/<int:pk>/delete/', views.DeviceDeleteView.as_view(), name='device_delete'),
|
||||||
|
@ -1223,6 +1223,53 @@ class DeviceEditView(DeviceCreateView):
|
|||||||
permission_required = 'dcim.change_device'
|
permission_required = 'dcim.change_device'
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceAssignView(PermissionRequiredMixin, View):
|
||||||
|
"""
|
||||||
|
Search for Devices to be assigned to a Rack.
|
||||||
|
"""
|
||||||
|
permission_required = 'dcim.change_device'
|
||||||
|
|
||||||
|
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')
|
||||||
|
else:
|
||||||
|
# Restrict device assignment in the same site
|
||||||
|
self.site = request.GET['site']
|
||||||
|
|
||||||
|
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 = Device.objects.filter(site=self.site).prefetch_related(
|
||||||
|
'tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer'
|
||||||
|
)
|
||||||
|
# Limit to 100 results
|
||||||
|
devices = filters.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(PermissionRequiredMixin, ObjectDeleteView):
|
class DeviceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
||||||
permission_required = 'dcim.delete_device'
|
permission_required = 'dcim.delete_device'
|
||||||
model = Device
|
model = Device
|
||||||
|
47
netbox/templates/dcim/device_assign.html
Normal file
47
netbox/templates/dcim/device_assign.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{% extends 'utilities/obj_edit.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% load form_helpers %}
|
||||||
|
{% load helpers %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form action="{% querystring request %}" method="post" class="form form-horizontal">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% for field in form.hidden_fields %}
|
||||||
|
{{ field }}
|
||||||
|
{% endfor %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 col-md-offset-3">
|
||||||
|
<h3>Assign a Device</h3>
|
||||||
|
{% include 'dcim/inc/device_edit_header.html' with active_tab='assign' %}
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<div class="panel panel-danger">
|
||||||
|
<div class="panel-heading"><strong>Errors</strong></div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading"><strong>Select Device</strong></div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{% render_field form.q %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 col-md-offset-3 text-right">
|
||||||
|
<button type="submit" class="btn btn-primary">Search</button>
|
||||||
|
<a href="{{ return_url }}" class="btn btn-default">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% if table %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12" style="margin-top: 20px">
|
||||||
|
<h3>Search Results</h3>
|
||||||
|
{% include 'utilities/obj_table.html' with table_template='panel_table.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
@ -1,5 +1,13 @@
|
|||||||
{% extends 'utilities/obj_edit.html' %}
|
{% extends 'utilities/obj_edit.html' %}
|
||||||
|
{% load static %}
|
||||||
{% load form_helpers %}
|
{% load form_helpers %}
|
||||||
|
{% load helpers %}
|
||||||
|
|
||||||
|
{% block tabs %}
|
||||||
|
{% if not obj.pk %}
|
||||||
|
{% include 'dcim/inc/device_edit_header.html' with active_tab='add' %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
|
12
netbox/templates/dcim/inc/device_edit_header.html
Normal file
12
netbox/templates/dcim/inc/device_edit_header.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{% load helpers %}
|
||||||
|
|
||||||
|
<ul class="nav nav-tabs" style="margin-bottom: 20px">
|
||||||
|
<li role="presentation"{% if active_tab == 'add' %} class="active"{% endif %}>
|
||||||
|
<a href="{% url 'dcim:device_add' %}{% querystring request %}">New Device</a>
|
||||||
|
</li>
|
||||||
|
{% if 'rack' in request.GET %}
|
||||||
|
<li role="presentation"{% if active_tab == 'assign' %} class="active"{% endif %}>
|
||||||
|
<a href="{% url 'dcim:device_assign' %}{% querystring request %}">Assign Device</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
Loading…
Reference in New Issue
Block a user