Merge pull request #7 from rizlas/master

Sync for all components not only interfaces
This commit is contained in:
Victor Golovanenko 2022-01-20 09:53:00 +03:00 committed by GitHub
commit de4e730514
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1161 additions and 271 deletions

View File

@ -9,7 +9,12 @@ class Config(PluginConfig):
author = 'Victor Golovanenko' author = 'Victor Golovanenko'
author_email = 'drygdryg2014@yandex.ru' author_email = 'drygdryg2014@yandex.ru'
default_settings = { default_settings = {
'exclude_virtual_interfaces': True 'exclude_virtual_interfaces': True,
'include_interfaces_panel': False,
# Compare description during diff
# If compare is true, description will also be synced to device
# Otherwise not.
'compare_description': True
} }

View File

@ -0,0 +1,225 @@
from dataclasses import dataclass
from django.conf import settings
config = settings.PLUGINS_CONFIG["netbox_interface_sync"]
@dataclass(frozen=True)
class ParentComparison:
"""Common fields of a device component"""
id: int
name: str
label: str
description: str
def __eq__(self, other):
# Ignore some fields when comparing; ignore component name case and whitespaces
eq = (
self.name.lower().replace(" ", "") == other.name.lower().replace(" ", "")
) and (self.label == other.label)
if config["compare_description"]:
eq = eq and (self.description == other.description)
return eq
def __hash__(self):
# Ignore some fields when hashing; ignore component name case and whitespaces
return hash(self.name.lower().replace(" ", ""))
def __str__(self):
return f"Label: {self.label}\nDescription: {self.description}"
@dataclass(frozen=True)
class ParentTypedComparison(ParentComparison):
"""Common fields of a device typed component"""
type: str
type_display: str
def __eq__(self, other):
eq = (
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
and (self.label == other.label)
and (self.type == other.type)
)
if config["compare_description"]:
eq = eq and (self.description == other.description)
return eq
def __hash__(self):
return hash((self.name.lower().replace(" ", ""), self.type))
def __str__(self):
return f"{super().__str__()}\nType: {self.type_display}"
@dataclass(frozen=True)
class InterfaceComparison(ParentTypedComparison):
"""A unified way to represent the interface and interface template"""
mgmt_only: bool
is_template: bool = False
def __eq__(self, other):
eq = (
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
and (self.label == other.label)
and (self.type == other.type)
and (self.mgmt_only == other.mgmt_only)
)
if config["compare_description"]:
eq = eq and (self.description == other.description)
return eq
def __hash__(self):
return hash((self.name.lower().replace(" ", ""), self.type))
def __str__(self):
return f"{super().__str__()}\nManagement only: {self.mgmt_only}"
@dataclass(frozen=True)
class FrontPortComparison(ParentTypedComparison):
"""A unified way to represent the front port and front port template"""
color: str
# rear_port_id: int
rear_port_position: int
is_template: bool = False
def __eq__(self, other):
eq = (
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
and (self.label == other.label)
and (self.type == other.type)
and (self.color == other.color)
and (self.rear_port_position == other.rear_port_position)
)
if config["compare_description"]:
eq = eq and (self.description == other.description)
return eq
def __hash__(self):
return hash((self.name.lower().replace(" ", ""), self.type))
def __str__(self):
return f"{super().__str__()}\nColor: {self.color}\nPosition: {self.rear_port_position}"
@dataclass(frozen=True)
class RearPortComparison(ParentTypedComparison):
"""A unified way to represent the rear port and rear port template"""
color: str
positions: int
is_template: bool = False
def __eq__(self, other):
eq = (
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
and (self.label == other.label)
and (self.type == other.type)
and (self.color == other.color)
and (self.positions == other.positions)
)
if config["compare_description"]:
eq = eq and (self.description == other.description)
return eq
def __hash__(self):
return hash((self.name.lower().replace(" ", ""), self.type))
def __str__(self):
return f"{super().__str__()}\nColor: {self.color}\nPositions: {self.positions}"
@dataclass(frozen=True, eq=False)
class ConsolePortComparison(ParentTypedComparison):
"""A unified way to represent the consoleport and consoleport template"""
is_template: bool = False
@dataclass(frozen=True, eq=False)
class ConsoleServerPortComparison(ParentTypedComparison):
"""A unified way to represent the consoleserverport and consoleserverport template"""
is_template: bool = False
@dataclass(frozen=True)
class PowerPortComparison(ParentTypedComparison):
"""A unified way to represent the power port and power port template"""
maximum_draw: str
allocated_draw: str
is_template: bool = False
def __eq__(self, other):
eq = (
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
and (self.label == other.label)
and (self.type == other.type)
and (self.maximum_draw == other.maximum_draw)
and (self.allocated_draw == other.allocated_draw)
)
if config["compare_description"]:
eq = eq and (self.description == other.description)
return eq
def __hash__(self):
return hash((self.name.lower().replace(" ", ""), self.type))
def __str__(self):
return f"{super().__str__()}\nMaximum draw: {self.maximum_draw}\nAllocated draw: {self.allocated_draw}"
@dataclass(frozen=True)
class PowerOutletComparison(ParentTypedComparison):
"""A unified way to represent the power outlet and power outlet template"""
power_port_name: str = ""
feed_leg: str = ""
is_template: bool = False
def __eq__(self, other):
eq = (
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
and (self.label == other.label)
and (self.type == other.type)
and (self.power_port_name == other.power_port_name)
and (self.feed_leg == other.feed_leg)
)
if config["compare_description"]:
eq = eq and (self.description == other.description)
return eq
def __hash__(self):
return hash(
(self.name.lower().replace(" ", ""), self.type, self.power_port_name)
)
def __str__(self):
return f"{super().__str__()}\nPower port name: {self.power_port_name}\nFeed leg: {self.feed_leg}"
@dataclass(frozen=True, eq=False)
class DeviceBayComparison(ParentComparison):
"""A unified way to represent the device bay and device bay template"""
is_template: bool = False

View File

@ -1,6 +1,6 @@
from django import forms from django import forms
class InterfaceComparisonForm(forms.Form): class ComponentComparisonForm(forms.Form):
add_to_device = forms.BooleanField(required=False) add_to_device = forms.BooleanField(required=False)
remove_from_device = forms.BooleanField(required=False) remove_from_device = forms.BooleanField(required=False)

View File

@ -6,9 +6,9 @@ class DeviceViewExtension(PluginTemplateExtension):
model = "dcim.device" model = "dcim.device"
def buttons(self): def buttons(self):
"""Implements a compare interfaces button at the top of the page""" """Implements a compare button at the top of the page"""
obj = self.context['object'] obj = self.context['object']
return self.render("netbox_interface_sync/compare_interfaces_button.html", extra_context={ return self.render("netbox_interface_sync/compare_components_button.html", extra_context={
"device": obj "device": obj
}) })

View File

@ -0,0 +1,65 @@
{% if perms.dcim.change_device %}
<div class="dropdown">
<button id="add-device-components" type="button" class="btn btn-sm btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
Device type sync
</button>
<ul class="dropdown-menu" aria-labeled-by="add-device-components">
{% if perms.dcim.add_consoleport %}
<li>
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:consoleport_comparison' device_id=device.id %}">
Console Ports
</a>
</li>
{% endif %}
{% if perms.dcim.add_consoleserverport %}
<li>
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:consoleserverport_comparison' device_id=device.id %}">
Console Server Ports
</a>
</li>
{% endif %}
{% if perms.dcim.add_powerport %}
<li>
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:powerport_comparison' device_id=device.id %}">
Power Ports
</a>
</li>
{% endif %}
{% if perms.dcim.add_poweroutlet %}
<li>
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:poweroutlet_comparison' device_id=device.id %}">
Power Outlets
</a>
</li>
{% endif %}
{% if perms.dcim.add_interface %}
<li>
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:interface_comparison' device_id=device.id %}">
Interfaces
</a>
</li>
{% endif %}
{% if perms.dcim.add_frontport %}
<li>
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:frontport_comparison' device_id=device.id %}">
Front Ports
</a>
</li>
{% endif %}
{% if perms.dcim.add_rearport %}
<li>
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:rearport_comparison' device_id=device.id %}">
Rear Ports
</a>
</li>
{% endif %}
{% if perms.dcim.add_devicebay %}
<li>
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:devicebay_comparison' device_id=device.id %}">
Device Bays
</a>
</li>
{% endif %}
</ul>
</div>
{% endif %}

View File

@ -1,3 +0,0 @@
<a href="{% url 'plugins:netbox_interface_sync:interface_comparison' device_id=device.id %}" class="btn btn-sm btn-primary">
Interface sync
</a>

View File

@ -0,0 +1,158 @@
{% extends 'base/layout.html' %}
{% block title %}{{ device }} - {{ component_type }} comparison{% endblock %}
{% block header %}
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'dcim:device_list' %}">Devices</a></li>
<li class="breadcrumb-item"><a href="{% url 'dcim:device_list' %}?site={{ device.site.slug }}">{{ device.site }}</a></li>
<li class="breadcrumb-item"><a href="{% url 'dcim:device' pk=device.id %}">{{ device }}</a></li>
</ol>
</nav>
{{ block.super }}
{% endblock %}
{% block content %}
<style>
.caption-red {
caption-side: top;
color: red;
}
.caption-green {
caption-side: top;
color: green;
}
</style>
<script>
function toggle(event) {
event = event || window.event;
var src = event.target || event.srcElement || event;
checkboxes = document.getElementsByName(src.id);
for(var checkbox of checkboxes) checkbox.checked = src.checked;
}
function uncheck(event) {
event = event || window.event;
var src = event.target || event.srcElement || event;
if (src.checked == false) {
document.getElementById(src.name).checked = false;
}
}
</script>
<form method="post">
{% csrf_token %}
<div class="table-responsive-xl">
<table class="table table-hover table-bordered">
{% if templates_count == components_count %}
<caption class="caption-green">
The device and device type have the same number of {{ component_type }}.
</caption>
{% else %}
<caption class="caption-red">
The device and device type have different number of {{ component_type|lower }}.<br>
Device: {{ components_count }}<br>
Device type: {{ templates_count }}
</caption>
{% endif %}
<thead>
<tr>
<th scope="col" colspan="2">Device type</th>
<th scope="col">Actions</th>
<th scope="col" colspan="2">Device</th>
<th scope="col" colspan="2">Actions</th>
</tr>
<tr>
<th scope="col">Name</th>
<th scope="col">Attributes</th>
<th scope="col">
<label>
<input type="checkbox" id="add_to_device" onclick="toggle(this)">
Add to the device
</label>
</th>
<th scope="col">Name</th>
<th scope="col">Attributes</th>
<th scope="col">
<label>
<input type="checkbox" id="remove_from_device" onclick="toggle(this)">
Remove
</label>
</th>
<th scope="col">
<label>
<input type="checkbox" id="fix_name" onclick="toggle(this)">
Fix the name
</label>
</th>
</tr>
</thead>
<tbody>
{% for component_template, component in comparison_items %}
<tr>
{% if component_template %}
<th scope="row" {% if not component %}class="table-danger"{% endif %}>
{% if component and component_template.name != component.name %}
<span style="background-color: #eab2b2">{{ component_template.name }}</span>
{% else %}
{{ component_template.name }}
{% endif %}
</th>
<td style="white-space:pre" {% if not component %}class="table-danger"{% endif %}>{{ component_template }}</td>
<td {% if not component %}class="table-danger"{% endif %}>
{% if not component %}
<label>
<input type="checkbox" name="add_to_device" value="{{ component_template.id }}" onclick="uncheck(this)">
Add to device
</label>
{% endif %}
</td>
{% else %}
<th scope="row">&nbsp;</th>
<td>&nbsp;</td>
<td>&nbsp;</td>
{% endif %}
{% if component %}
<th scope="row" {% if not component_template %}class="table-success"{% endif %}>
{% if component_template and component_template.name != component.name %}
<span style="background-color: #cde8c2">{{ component.name }}</span>
{% else %}
{{ component.name }}
{% endif %}
</th>
<td style="white-space:pre" {% if not component_template %}class="table-success"{% endif %}>{{ component }}</td>
<td {% if not component_template %}class="table-success"{% endif %}>
{% if not component_template %}
<label>
<input type="checkbox" name="remove_from_device" value="{{ component.id }}" onclick="uncheck(this)">
Remove
</label>
{% endif %}
</td>
<td {% if not component_template %}class="table-success"{% endif %}>
{% if component_template and component_template.name != component.name %}
<label>
<input type="checkbox" name="fix_name" value="{{ component.id }}" onclick="uncheck(this)">
Fix name
</label>
{% endif %}
</td>
{% else %}
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div>
<input type="submit" value="Apply" class="btn btn-primary" style="float: right;">
</div>
</form>
{% endblock %}

View File

@ -1,161 +0,0 @@
{% extends 'base/layout.html' %}
{% block title %}{{ device }} - Interface comparison{% endblock %}
{% block header %}
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'dcim:device_list' %}">Devices</a></li>
<li class="breadcrumb-item"><a href="{% url 'dcim:device_list' %}?site={{ device.site.slug }}">{{ device.site }}</a></li>
<li class="breadcrumb-item"><a href="{% url 'dcim:device' pk=device.id %}">{{ device }}</a></li>
</ol>
</nav>
{{ block.super }}
{% endblock %}
{% block content %}
<style>
.checkbox-group {
position: absolute;
}
</style>
<script>
function toggle(event) {
event = event || window.event;
var src = event.target || event.srcElement || event;
checkboxes = document.getElementsByName(src.id);
for(var checkbox of checkboxes) checkbox.checked = src.checked;
}
function uncheck(event) {
event = event || window.event;
var src = event.target || event.srcElement || event;
if (src.checked == false) {
document.getElementById(src.name).checked = false;
}
}
</script>
<p>
{% if templates_count == interfaces_count %}
The device and device type have the same number of interfaces.
{% else %}
The device and device type have different number of interfaces.<br>
Device: {{ interfaces_count }}<br>
Device type: {{ templates_count }}
{% endif %}
</p>
<form method="post">
<!-- Interface templates -->
{% csrf_token %}
<table class="table" style="width: 50%; float: left;">
<tr>
<th colspan="2">Device type</th>
<th>Actions</th>
</tr>
<tr>
<th>Name</th>
<th>Type</th>
<th>
<label class="checkbox-group">
<input type="checkbox" id="add_to_device" onclick="toggle(this)">
Add to the device
</label>
</th>
</tr>
{% for template, interface in comparison_items %}
{% if template %}
<tr {% if not interface %}class="danger"{% endif %}>
<td>
{% if interface and template.name != interface.name %}
<span style="background-color: #eab2b2">{{ template.name }}</span>
{% else %}
{{ template.name }}
{% endif %}
</td>
<td>{{ template.type_display }}</td>
<td>
{% if not interface %}
<label class="checkbox-group">
<input type="checkbox" name="add_to_device" value="{{ template.id }}" onclick="uncheck(this)">
Add to device
</label>
{% endif %}
</td>
</tr>
{% else %}
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
{% endif %}
{% endfor %}
</table>
<table class="table" style="width: 50%; float: right;">
<!-- Interfaces -->
<tr>
<th colspan="2">Device</th>
<th colspan="2">Actions</th>
</tr>
<tr>
<th>Name</th>
<th>Type</th>
<th>
<label class="checkbox-group">
<input type="checkbox" id="remove_from_device" onclick="toggle(this)">
Remove
</label>
</th>
<th>
<label class="checkbox-group">
<input type="checkbox" id="fix_name" onclick="toggle(this)">
Fix the name
</label>
</th>
</tr>
{% for template, interface in comparison_items %}
{% if interface %}
<tr {% if not template %}class="success"{% endif %}>
<td>
{% if template and template.name != interface.name %}
<span style="background-color: #cde8c2">{{ interface.name }}</span>
{% else %}
{{ interface.name }}
{% endif %}
</td>
<td>{{ interface.type_display }}</td>
<td>
{% if not template %}
<label class="checkbox-group">
<input type="checkbox" name="remove_from_device" value="{{ interface.id }}" onclick="uncheck(this)">
Remove
</label>
{% endif %}
</td>
<td>
{% if template and template.name != interface.name %}
<label class="checkbox-group">
<input type="checkbox" name="fix_name" value="{{ interface.id }}" onclick="uncheck(this)">
Fix name
</label>
{% endif %}
</td>
</tr>
{% else %}
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
{% endif %}
{% endfor %}
</table>
<div class="text-right">
<input type="submit" value="Apply" class="btn btn-primary">
</div>
</form>
{% endblock %}

View File

@ -1,10 +1,12 @@
<div class="card"> {% if config.include_interfaces_panel %}
<h5 class="card-header">Number of interfaces</h5> <div class="card">
<div class="card-body"> <h5 class="card-header">Number of interfaces</h5>
Total interfaces: {{ interfaces|length }}<br> <div class="card-body">
{% if config.exclude_virtual_interfaces %} Total interfaces: {{ interfaces|length }}<br>
Non-virtual interfaces: {{ real_interfaces|length }}<br> {% if config.exclude_virtual_interfaces %}
{% endif %} Non-virtual interfaces: {{ real_interfaces|length }}<br>
Interfaces in the assigned device type: {{ interface_templates|length }} {% endif %}
Interfaces in the assigned device type: {{ interface_templates|length }}
</div>
</div> </div>
</div> {% endif %}

View File

@ -6,5 +6,44 @@ from . import views
# Define a list of URL patterns to be imported by NetBox. Each pattern maps a URL to # Define a list of URL patterns to be imported by NetBox. Each pattern maps a URL to
# a specific view so that it can be accessed by users. # a specific view so that it can be accessed by users.
urlpatterns = ( urlpatterns = (
path("interface-comparison/<int:device_id>/", views.InterfaceComparisonView.as_view(), name="interface_comparison"), path(
"interface-comparison/<int:device_id>/",
views.InterfaceComparisonView.as_view(),
name="interface_comparison",
),
path(
"powerport-comparison/<int:device_id>/",
views.PowerPortComparisonView.as_view(),
name="powerport_comparison",
),
path(
"consoleport-comparison/<int:device_id>/",
views.ConsolePortComparisonView.as_view(),
name="consoleport_comparison",
),
path(
"consoleserverport-comparison/<int:device_id>/",
views.ConsoleServerPortComparisonView.as_view(),
name="consoleserverport_comparison",
),
path(
"poweroutlet-comparison/<int:device_id>/",
views.PowerOutletComparisonView.as_view(),
name="poweroutlet_comparison",
),
path(
"frontport-comparison/<int:device_id>/",
views.FrontPortComparisonView.as_view(),
name="frontport_comparison",
),
path(
"rearport-comparison/<int:device_id>/",
views.RearPortComparisonView.as_view(),
name="rearport_comparison",
),
path(
"devicebay-comparison/<int:device_id>/",
views.DeviceBayComparisonView.as_view(),
name="devicebay_comparison",
),
) )

View File

@ -1,11 +1,16 @@
import re import re
from typing import Iterable from typing import Iterable
from dataclasses import dataclass from django.shortcuts import render, redirect
from django.contrib import messages
from django.core.exceptions import ObjectDoesNotExist
from django.conf import settings
config = settings.PLUGINS_CONFIG['netbox_interface_sync']
def split(s): def split(s):
for x, y in re.findall(r'(\d*)(\D*)', s): for x, y in re.findall(r"(\d*)(\D*)", s):
yield '', int(x or '0') yield "", int(x or "0")
yield y, 0 yield y, 0
@ -17,19 +22,117 @@ def human_sorted(iterable: Iterable):
return sorted(iterable, key=natural_keys) return sorted(iterable, key=natural_keys)
@dataclass(frozen=True) def get_components(request, device, components, unified_components, unified_component_templates, component_type):
class UnifiedInterface: # List of components and components templates presented in the unified format
"""A unified way to represent the interface and interface template""" overall_powers = list(set(unified_component_templates + unified_components))
id: int overall_powers.sort(key=lambda o: natural_keys(o.name))
name: str
type: str
type_display: str
is_template: bool = False
def __eq__(self, other): comparison_templates = []
# Ignore some fields when comparing; ignore interface name case and whitespaces comparison_components = []
return (self.name.lower().replace(' ', '') == other.name.lower().replace(' ', '')) and (self.type == other.type) for i in overall_powers:
try:
comparison_templates.append(
unified_component_templates[unified_component_templates.index(i)]
)
except ValueError:
comparison_templates.append(None)
def __hash__(self): try:
# Ignore some fields when hashing; ignore interface name case and whitespaces comparison_components.append(
return hash((self.name.lower().replace(' ', ''), self.type)) unified_components[unified_components.index(i)]
)
except ValueError:
comparison_components.append(None)
comparison_items = list(zip(comparison_templates, comparison_components))
return render(
request,
"netbox_interface_sync/components_comparison.html",
{
"component_type": component_type,
"comparison_items": comparison_items,
"templates_count": len(unified_component_templates),
"components_count": len(components),
"device": device,
},
)
def post_components(
request, device, components, component_templates, ObjectType, ObjectTemplateType, unified_component, unified_component_templates, component_type
):
# Manually validating components and component templates lists
add_to_device = filter(
lambda i: i in component_templates.values_list("id", flat=True),
map(int, filter(lambda x: x.isdigit(), request.POST.getlist("add_to_device"))),
)
remove_from_device = filter(
lambda i: i in components.values_list("id", flat=True),
map(
int,
filter(lambda x: x.isdigit(), request.POST.getlist("remove_from_device")),
),
)
# Remove selected component from the device and count them
deleted = ObjectType.objects.filter(id__in=remove_from_device).delete()[0]
# Add selected components to the device and count them
add_to_device_component = ObjectTemplateType.objects.filter(id__in=add_to_device)
bulk_create = []
updated = 0
keys_to_avoid = ["id"]
if not config["compare_description"]:
keys_to_avoid.append("description")
for i in add_to_device_component.values():
to_create = False
try:
tmp = components.get(name=i["name"])
except ObjectDoesNotExist:
tmp = ObjectType()
tmp.device = device
to_create = True
for k in i.keys():
if k not in keys_to_avoid:
setattr(tmp, k, i[k])
if to_create:
bulk_create.append(tmp)
else:
tmp.save()
updated += 1
created = len(ObjectType.objects.bulk_create(bulk_create))
# Rename selected components
fixed = 0
for component, component_comparison in unified_component:
try:
# Try to extract a component template with the corresponding name
corresponding_template = unified_component_templates[
unified_component_templates.index(component_comparison)
]
component.name = corresponding_template.name
component.save()
fixed += 1
except ValueError:
pass
# Generating result message
message = []
if created > 0:
message.append(f"created {created} {component_type}")
if updated > 0:
message.append(f"updated {updated} {component_type}")
if deleted > 0:
message.append(f"deleted {deleted} {component_type}")
if fixed > 0:
message.append(f"fixed {fixed} {component_type}")
messages.success(request, "; ".join(message).capitalize())
return redirect(request.path)

View File

@ -1,12 +1,13 @@
from django.shortcuts import get_object_or_404, render, redirect from django.shortcuts import get_object_or_404, redirect
from django.views.generic import View from django.views.generic import View
from dcim.models import Device, Interface, InterfaceTemplate from dcim.models import Device, Interface, InterfaceTemplate, PowerPort, PowerPortTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, DeviceBay, DeviceBayTemplate, FrontPort, FrontPortTemplate,PowerOutlet, PowerOutletTemplate, RearPort, RearPortTemplate
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from .utils import UnifiedInterface, natural_keys from .utils import get_components, post_components
from .forms import InterfaceComparisonForm from .comparison import FrontPortComparison, PowerPortComparison, PowerOutletComparison, InterfaceComparison, ConsolePortComparison, ConsoleServerPortComparison, DeviceBayComparison, RearPortComparison
from .forms import ComponentComparisonForm
config = settings.PLUGINS_CONFIG['netbox_interface_sync'] config = settings.PLUGINS_CONFIG['netbox_interface_sync']
@ -22,40 +23,14 @@ class InterfaceComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
interfaces = list(filter(lambda i: not i.is_virtual, interfaces)) interfaces = list(filter(lambda i: not i.is_virtual, interfaces))
interface_templates = InterfaceTemplate.objects.filter(device_type=device.device_type) interface_templates = InterfaceTemplate.objects.filter(device_type=device.device_type)
unified_interfaces = [UnifiedInterface(i.id, i.name, i.type, i.get_type_display()) for i in interfaces] unified_interfaces = [InterfaceComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.mgmt_only) for i in interfaces]
unified_interface_templates = [ unified_interface_templates = [
UnifiedInterface(i.id, i.name, i.type, i.get_type_display(), is_template=True) for i in interface_templates] InterfaceComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.mgmt_only, is_template=True) for i in interface_templates]
# List of interfaces and interface templates presented in the unified format return get_components(request, device, interfaces, unified_interfaces, unified_interface_templates, "Interfaces")
overall_interfaces = list(set(unified_interface_templates + unified_interfaces))
overall_interfaces.sort(key=lambda o: natural_keys(o.name))
comparison_templates = []
comparison_interfaces = []
for i in overall_interfaces:
try:
comparison_templates.append(unified_interface_templates[unified_interface_templates.index(i)])
except ValueError:
comparison_templates.append(None)
try:
comparison_interfaces.append(unified_interfaces[unified_interfaces.index(i)])
except ValueError:
comparison_interfaces.append(None)
comparison_items = list(zip(comparison_templates, comparison_interfaces))
return render(
request, "netbox_interface_sync/interface_comparison.html",
{
"comparison_items": comparison_items,
"templates_count": len(interface_templates),
"interfaces_count": len(interfaces),
"device": device
}
)
def post(self, request, device_id): def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST) form = ComponentComparisonForm(request.POST)
if form.is_valid(): if form.is_valid():
device = get_object_or_404(Device.objects.filter(id=device_id)) device = get_object_or_404(Device.objects.filter(id=device_id))
interfaces = device.vc_interfaces() interfaces = device.vc_interfaces()
@ -63,52 +38,534 @@ class InterfaceComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
interfaces = interfaces.exclude(type__in=["virtual", "lag"]) interfaces = interfaces.exclude(type__in=["virtual", "lag"])
interface_templates = InterfaceTemplate.objects.filter(device_type=device.device_type) interface_templates = InterfaceTemplate.objects.filter(device_type=device.device_type)
# Manually validating interfaces and interface templates lists
add_to_device = filter(
lambda i: i in interface_templates.values_list("id", flat=True),
map(int, filter(lambda x: x.isdigit(), request.POST.getlist("add_to_device")))
)
remove_from_device = filter(
lambda i: i in interfaces.values_list("id", flat=True),
map(int, filter(lambda x: x.isdigit(), request.POST.getlist("remove_from_device")))
)
# Remove selected interfaces from the device and count them
interfaces_deleted = Interface.objects.filter(id__in=remove_from_device).delete()[0]
# Add selected interfaces to the device and count them
add_to_device_interfaces = InterfaceTemplate.objects.filter(id__in=add_to_device)
interfaces_created = len(Interface.objects.bulk_create([
Interface(device=device, name=i.name, type=i.type) for i in add_to_device_interfaces
]))
# Getting and validating a list of interfaces to rename # Getting and validating a list of interfaces to rename
fix_name_interfaces = filter(lambda i: str(i.id) in request.POST.getlist("fix_name"), interfaces) fix_name_components = filter(
# Casting interface templates into UnifiedInterface objects for proper comparison with interfaces for renaming lambda i: str(i.id) in request.POST.getlist("fix_name"), interfaces
unified_interface_templates = [ )
UnifiedInterface(i.id, i.name, i.type, i.get_type_display()) for i in interface_templates]
# Rename selected interfaces unified_interface_templates = [
interfaces_fixed = 0 InterfaceComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.mgmt_only, is_template=True) for i in interface_templates]
for interface in fix_name_interfaces:
unified_interface = UnifiedInterface(interface.id, interface.name, interface.type, interface.get_type_display()) unified_interfaces = []
try:
# Try to extract an interface template with the corresponding name for component in fix_name_components:
corresponding_template = unified_interface_templates[unified_interface_templates.index(unified_interface)] unified_interfaces.append((component, InterfaceComparison(
interface.name = corresponding_template.name component.id,
interface.save() component.name,
interfaces_fixed += 1 component.label,
except ValueError: component.description,
pass component.type,
component.get_type_display(),
component.mgmt_only)))
return post_components(request, device, interfaces, interface_templates, Interface, InterfaceTemplate, unified_interfaces, unified_interface_templates, "interfaces")
class PowerPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Comparison of power ports between a device and a device type and beautiful visualization"""
permission_required = ("dcim.view_powerport", "dcim.add_powerport", "dcim.change_powerport", "dcim.delete_powerport")
def get(self, request, device_id):
device = get_object_or_404(Device.objects.filter(id=device_id))
powerports = device.powerports.all()
powerports_templates = PowerPortTemplate.objects.filter(device_type=device.device_type)
unified_powerports = [PowerPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.maximum_draw, i.allocated_draw) for i in powerports]
unified_powerport_templates = [
PowerPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.maximum_draw, i.allocated_draw, is_template=True) for i in powerports_templates]
return get_components(request, device, powerports, unified_powerports, unified_powerport_templates, "Power ports")
def post(self, request, device_id):
form = ComponentComparisonForm(request.POST)
if form.is_valid():
device = get_object_or_404(Device.objects.filter(id=device_id))
powerports = device.powerports.all()
powerports_templates = PowerPortTemplate.objects.filter(device_type=device.device_type)
# Getting and validating a list of power ports to rename
fix_name_components = filter(
lambda i: str(i.id) in request.POST.getlist("fix_name"), powerports
)
unified_powerport_templates = [
PowerPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.maximum_draw, i.allocated_draw, is_template=True) for i in powerports_templates]
unified_powerports = []
for component in fix_name_components:
unified_powerports.append((component, PowerPortComparison(
component.id,
component.name,
component.label,
component.description,
component.type,
component.get_type_display(),
component.maximum_draw,
component.allocated_draw)))
return post_components(request, device, powerports, powerports_templates, PowerPort, PowerPortTemplate, unified_powerports, unified_powerport_templates, "power ports")
class ConsolePortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Comparison of console ports between a device and a device type and beautiful visualization"""
permission_required = ("dcim.view_consoleport", "dcim.add_consoleport", "dcim.change_consoleport", "dcim.delete_consoleport")
def get(self, request, device_id):
device = get_object_or_404(Device.objects.filter(id=device_id))
consoleports = device.consoleports.all()
consoleports_templates = ConsolePortTemplate.objects.filter(device_type=device.device_type)
unified_consoleports = [ConsolePortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display()) for i in consoleports]
unified_consoleport_templates = [
ConsolePortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), is_template=True) for i in consoleports_templates]
return get_components(request, device, consoleports, unified_consoleports, unified_consoleport_templates, "Console ports")
def post(self, request, device_id):
form = ComponentComparisonForm(request.POST)
if form.is_valid():
device = get_object_or_404(Device.objects.filter(id=device_id))
consoleports = device.consoleports.all()
consoleports_templates = ConsolePortTemplate.objects.filter(device_type=device.device_type)
# Getting and validating a list of console ports to rename
fix_name_components = filter(
lambda i: str(i.id) in request.POST.getlist("fix_name"), consoleports
)
unified_consoleport_templates = [
ConsolePortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), is_template=True) for i in consoleports_templates]
unified_consoleports = []
for component in fix_name_components:
unified_consoleports.append((component, ConsolePortComparison(
component.id,
component.name,
component.label,
component.description,
component.type,
component.get_type_display())))
return post_components(request, device, consoleports, consoleports_templates, ConsolePort, ConsolePortTemplate, unified_consoleports, unified_consoleport_templates, "console ports")
class ConsoleServerPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Comparison of console server ports between a device and a device type and beautiful visualization"""
permission_required = ("dcim.view_consoleserverport", "dcim.add_consoleserverport", "dcim.change_consoleserverport", "dcim.delete_consoleserverport")
def get(self, request, device_id):
device = get_object_or_404(Device.objects.filter(id=device_id))
consoleserverports = device.consoleserverports.all()
consoleserverports_templates = ConsoleServerPortTemplate.objects.filter(device_type=device.device_type)
unified_consoleserverports = [ConsoleServerPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display()) for i in consoleserverports]
unified_consoleserverport_templates = [
ConsoleServerPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), is_template=True) for i in consoleserverports_templates]
return get_components(request, device, consoleserverports, unified_consoleserverports, unified_consoleserverport_templates, "Console server ports")
def post(self, request, device_id):
form = ComponentComparisonForm(request.POST)
if form.is_valid():
device = get_object_or_404(Device.objects.filter(id=device_id))
consoleserverports = device.consoleserverports.all()
consoleserverports_templates = ConsoleServerPortTemplate.objects.filter(device_type=device.device_type)
# Getting and validating a list of console server ports to rename
fix_name_components = filter(
lambda i: str(i.id) in request.POST.getlist("fix_name"), consoleserverports
)
unified_consoleserverport_templates = [
ConsoleServerPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), is_template=True) for i in consoleserverports_templates]
unified_consoleserverports = []
for component in fix_name_components:
unified_consoleserverports.append((component, ConsoleServerPortComparison(
component.id,
component.name,
component.label,
component.description,
component.type,
component.get_type_display())))
return post_components(request, device, consoleserverports, consoleserverports_templates, ConsoleServerPort, ConsoleServerPortTemplate, unified_consoleserverports, unified_consoleserverport_templates, "console server ports")
class PowerOutletComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Comparison of power outlets between a device and a device type and beautiful visualization"""
permission_required = ("dcim.view_poweroutlet", "dcim.add_poweroutlet", "dcim.change_poweroutlet", "dcim.delete_poweroutlet")
def get(self, request, device_id):
device = get_object_or_404(Device.objects.filter(id=device_id))
poweroutlets = device.poweroutlets.all()
poweroutlets_templates = PowerOutletTemplate.objects.filter(device_type=device.device_type)
unified_poweroutlets = [PowerOutletComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), power_port_name=PowerPort.objects.get(id=i.power_port_id).name if i.power_port_id is not None else "", feed_leg=i.feed_leg) for i in poweroutlets]
unified_poweroutlet_templates = [
PowerOutletComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), power_port_name=PowerPortTemplate.objects.get(id=i.power_port_id).name if i.power_port_id is not None else "", feed_leg=i.feed_leg, is_template=True) for i in poweroutlets_templates]
return get_components(request, device, poweroutlets, unified_poweroutlets, unified_poweroutlet_templates, "Power outlets")
def post(self, request, device_id):
form = ComponentComparisonForm(request.POST)
if form.is_valid():
device = get_object_or_404(Device.objects.filter(id=device_id))
poweroutlets = device.poweroutlets.all()
poweroutlets_templates = PowerOutletTemplate.objects.filter(device_type=device.device_type)
# Generating result message # Generating result message
message = [] message = []
if interfaces_created > 0: created = 0
message.append(f"created {interfaces_created} interfaces") updated = 0
if interfaces_deleted > 0: fixed = 0
message.append(f"deleted {interfaces_deleted} interfaces")
if interfaces_fixed > 0: remove_from_device = filter(
message.append(f"fixed {interfaces_fixed} interfaces") lambda i: i in poweroutlets.values_list("id", flat=True),
messages.success(request, "; ".join(message).capitalize()) map(int, filter(lambda x: x.isdigit(), request.POST.getlist("remove_from_device")))
)
# Remove selected power outlets from the device and count them
deleted = PowerOutlet.objects.filter(id__in=remove_from_device).delete()[0]
# Get device power ports to check dependency between power outlets
device_pp = PowerPort.objects.filter(device_id=device.id)
matching = {}
mismatch = False
for i in poweroutlets_templates:
found = False
if i.power_port_id is not None:
ppt = PowerPortTemplate.objects.get(id=i.power_port_id)
for pp in device_pp:
if pp.name == ppt.name:
# Save matching to add the correct power port later
matching[i.id] = pp.id
found = True
# If at least one power port is not found in device there is a dependency
# Better not to sync at all
if not found:
mismatch = True
break
if not mismatch:
add_to_device = filter(
lambda i: i in poweroutlets_templates.values_list("id", flat=True),
map(int, filter(lambda x: x.isdigit(), request.POST.getlist("add_to_device")))
)
# Add selected component to the device and count them
add_to_device_component = PowerOutletTemplate.objects.filter(id__in=add_to_device)
bulk_create = []
updated = 0
keys_to_avoid = ["id"]
if not config["compare_description"]:
keys_to_avoid.append("description")
for i in add_to_device_component.values():
to_create = False
try:
# If power outlets already exists, update and do not recreate
po = device.poweroutlets.get(name=i["name"])
except PowerOutlet.DoesNotExist:
po = PowerOutlet()
po.device = device
to_create = True
# Copy all fields from template
for k in i.keys():
if k not in keys_to_avoid:
setattr(po, k, i[k])
po.power_port_id = matching.get(i["id"], None)
if to_create:
bulk_create.append(po)
else:
po.save()
updated += 1
created = len(PowerOutlet.objects.bulk_create(bulk_create))
# Getting and validating a list of components to rename
fix_name_components = filter(lambda i: str(i.id) in request.POST.getlist("fix_name"), poweroutlets)
# Casting component templates into Unified objects for proper comparison with component for renaming
unified_component_templates = [
PowerOutletComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), power_port_name=PowerPortTemplate.objects.get(id=i.power_port_id).name if i.power_port_id is not None else "", feed_leg=i.feed_leg, is_template=True) for i in poweroutlets_templates]
# Rename selected power outlets
fixed = 0
for component in fix_name_components:
unified_poweroutlet = PowerOutletComparison(component.id, component.name, component.label, component.description, component.type, component.get_type_display(), power_port_name=PowerPort.objects.get(id=component.power_port_id).name if component.power_port_id is not None else "", feed_leg=component.feed_leg)
try:
# Try to extract a component template with the corresponding name
corresponding_template = unified_component_templates[unified_component_templates.index(unified_poweroutlet)]
component.name = corresponding_template.name
component.save()
fixed += 1
except ValueError:
pass
else:
messages.error(request, "Dependecy detected, sync power ports first!")
if created > 0:
message.append(f"created {created} power outlets")
if updated > 0:
message.append(f"updated {updated} power outlets")
if deleted > 0:
message.append(f"deleted {deleted} power outlets")
if fixed > 0:
message.append(f"fixed {fixed} power outlets")
messages.info(request, "; ".join(message).capitalize())
return redirect(request.path) return redirect(request.path)
class FrontPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Comparison of front ports between a device and a device type and beautiful visualization"""
permission_required = ("dcim.view_frontport", "dcim.add_frontport", "dcim.change_frontport", "dcim.delete_frontport")
def get(self, request, device_id):
device = get_object_or_404(Device.objects.filter(id=device_id))
frontports = device.frontports.all()
frontports_templates = FrontPortTemplate.objects.filter(device_type=device.device_type)
unified_frontports = [FrontPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.color, i.rear_port_position) for i in frontports]
unified_frontports_templates = [
FrontPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.color, i.rear_port_position, is_template=True) for i in frontports_templates]
return get_components(request, device, frontports, unified_frontports, unified_frontports_templates, "Front ports")
def post(self, request, device_id):
form = ComponentComparisonForm(request.POST)
if form.is_valid():
device = get_object_or_404(Device.objects.filter(id=device_id))
frontports = device.frontports.all()
frontports_templates = FrontPortTemplate.objects.filter(device_type=device.device_type)
# Generating result message
message = []
created = 0
updated = 0
fixed = 0
remove_from_device = filter(
lambda i: i in frontports.values_list("id", flat=True),
map(int, filter(lambda x: x.isdigit(), request.POST.getlist("remove_from_device")))
)
# Remove selected front ports from the device and count them
deleted = FrontPort.objects.filter(id__in=remove_from_device).delete()[0]
# Get device rear ports to check dependency between front ports
device_rp = RearPort.objects.filter(device_id=device.id)
matching = {}
mismatch = False
for i in frontports_templates:
found = False
if i.rear_port_id is not None:
rpt = RearPortTemplate.objects.get(id=i.rear_port_id)
for rp in device_rp:
if rp.name == rpt.name:
# Save matching to add the correct rear port later
matching[i.id] = rp.id
found = True
# If at least one rear port is not found in device there is a dependency
# Better not to sync at all
if not found:
mismatch = True
break
if not mismatch:
add_to_device = filter(
lambda i: i in frontports_templates.values_list("id", flat=True),
map(int, filter(lambda x: x.isdigit(), request.POST.getlist("add_to_device")))
)
# Add selected component to the device and count them
add_to_device_component = FrontPortTemplate.objects.filter(id__in=add_to_device)
bulk_create = []
updated = 0
keys_to_avoid = ["id"]
if not config["compare_description"]:
keys_to_avoid.append("description")
for i in add_to_device_component.values():
to_create = False
try:
# If fron port already exists, update and do not recreate
fp = device.frontports.get(name=i["name"])
except FrontPort.DoesNotExist:
fp = FrontPort()
fp.device = device
to_create = True
# Copy all fields from template
for k in i.keys():
if k not in keys_to_avoid:
setattr(fp, k, i[k])
fp.rear_port_id = matching.get(i["id"], None)
if to_create:
bulk_create.append(fp)
else:
fp.save()
updated += 1
created = len(FrontPort.objects.bulk_create(bulk_create))
# Getting and validating a list of components to rename
fix_name_components = filter(lambda i: str(i.id) in request.POST.getlist("fix_name"), frontports)
# Casting component templates into Unified objects for proper comparison with component for renaming
unified_frontports_templates = [
FrontPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.color, i.rear_port_position, is_template=True) for i in frontports_templates]
# Rename selected front ports
fixed = 0
for component in fix_name_components:
unified_frontport = FrontPortComparison(component.id, component.name, component.label, component.description, component.type, component.get_type_display(), component.color, component.rear_port_position)
try:
# Try to extract a component template with the corresponding name
corresponding_template = unified_frontports_templates[unified_frontports_templates.index(unified_frontport)]
component.name = corresponding_template.name
component.save()
fixed += 1
except ValueError:
pass
else:
messages.error(request, "Dependecy detected, sync rear ports first!")
if created > 0:
message.append(f"created {created} front ports")
if updated > 0:
message.append(f"updated {updated} front ports")
if deleted > 0:
message.append(f"deleted {deleted} front ports")
if fixed > 0:
message.append(f"fixed {fixed} front ports")
messages.info(request, "; ".join(message).capitalize())
return redirect(request.path)
class RearPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Comparison of rear ports between a device and a device type and beautiful visualization"""
permission_required = ("dcim.view_rearport", "dcim.add_rearport", "dcim.change_rearport", "dcim.delete_rearport")
def get(self, request, device_id):
device = get_object_or_404(Device.objects.filter(id=device_id))
rearports = device.rearports.all()
rearports_templates = RearPortTemplate.objects.filter(device_type=device.device_type)
unified_rearports = [RearPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.color, i.positions) for i in rearports]
unified_rearports_templates = [
RearPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.color, i.positions, is_template=True) for i in rearports_templates]
return get_components(request, device, rearports, unified_rearports, unified_rearports_templates, "Rear ports")
def post(self, request, device_id):
form = ComponentComparisonForm(request.POST)
if form.is_valid():
device = get_object_or_404(Device.objects.filter(id=device_id))
rearports = device.rearports.all()
rearports_templates = RearPortTemplate.objects.filter(device_type=device.device_type)
# Getting and validating a list of rear ports to rename
fix_name_components = filter(
lambda i: str(i.id) in request.POST.getlist("fix_name"), rearports
)
unified_rearports_templates = [
RearPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.color, i.positions, is_template=True) for i in rearports_templates]
unified_rearports = []
for component in fix_name_components:
unified_rearports.append((component, RearPortComparison(
component.id,
component.name,
component.label,
component.description,
component.type,
component.get_type_display(),
component.color,
component.positions)))
return post_components(request, device, rearports, rearports_templates, RearPort, RearPortTemplate, unified_rearports, unified_rearports_templates, "rear ports")
form = ComponentComparisonForm(request.POST)
if form.is_valid():
device = get_object_or_404(Device.objects.filter(id=device_id))
rearports = device.rearports.all()
rearports_templates = RearPortTemplate.objects.filter(device_type=device.device_type)
return post_components(request, device, rearports, rearports_templates, RearPort, RearPortTemplate)
class DeviceBayComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Comparison of device bays between a device and a device type and beautiful visualization"""
permission_required = ("dcim.view_devicebay", "dcim.add_devicebay", "dcim.change_devicebay", "dcim.delete_devicebay")
def get(self, request, device_id):
device = get_object_or_404(Device.objects.filter(id=device_id))
devicebays = device.devicebays.all()
devicebays_templates = DeviceBayTemplate.objects.filter(device_type=device.device_type)
unified_devicebays = [DeviceBayComparison(i.id, i.name, i.label, i.description) for i in devicebays]
unified_devicebay_templates = [
DeviceBayComparison(i.id, i.name, i.label, i.description, is_template=True) for i in devicebays_templates]
return get_components(request, device, devicebays, unified_devicebays, unified_devicebay_templates, "Device bays")
def post(self, request, device_id):
form = ComponentComparisonForm(request.POST)
if form.is_valid():
device = get_object_or_404(Device.objects.filter(id=device_id))
devicebays = device.devicebays.all()
devicebays_templates = DeviceBayTemplate.objects.filter(device_type=device.device_type)
# Getting and validating a list of devicebays to rename
fix_name_components = filter(
lambda i: str(i.id) in request.POST.getlist("fix_name"), devicebays
)
unified_devicebay_templates = [
DeviceBayComparison(i.id, i.name, i.label, i.description, is_template=True) for i in devicebays_templates]
unified_devicebays = []
for component in fix_name_components:
unified_devicebays.append((component, DeviceBayComparison(
component.id,
component.name,
component.label,
component.description
)))
return post_components(request, device, devicebays, devicebays_templates, DeviceBay, DeviceBayTemplate, unified_devicebays, unified_devicebay_templates, "device bays")