mirror of
https://github.com/drygdryg/netbox-plugin-interface-sync
synced 2024-11-25 18:10:52 +03:00
Refactoring: stage 1
This commit is contained in:
parent
87d12f05f8
commit
ba0d9c9736
@ -4,17 +4,24 @@ from extras.plugins import PluginConfig
|
|||||||
class Config(PluginConfig):
|
class Config(PluginConfig):
|
||||||
name = 'netbox_interface_sync'
|
name = 'netbox_interface_sync'
|
||||||
verbose_name = 'NetBox interface synchronization'
|
verbose_name = 'NetBox interface synchronization'
|
||||||
description = 'Syncing interfaces with the interfaces from device type for NetBox devices'
|
description = 'Compare and synchronize components (interfaces, ports, outlets, etc.) between NetBox device types ' \
|
||||||
|
'and devices'
|
||||||
version = '0.2.0'
|
version = '0.2.0'
|
||||||
author = 'Victor Golovanenko'
|
author = 'Victor Golovanenko'
|
||||||
author_email = 'drygdryg2014@yandex.ru'
|
author_email = 'drygdryg2014@yandex.ru'
|
||||||
default_settings = {
|
default_settings = {
|
||||||
|
# Ignore case and spaces in names when matching components between device type and device
|
||||||
|
'name_comparison': {
|
||||||
|
'case-insensitive': True,
|
||||||
|
'space-insensitive': True
|
||||||
|
},
|
||||||
|
# Exclude virtual interfaces (bridge, link aggregation group (LAG), "virtual") from comparison
|
||||||
'exclude_virtual_interfaces': True,
|
'exclude_virtual_interfaces': True,
|
||||||
|
# Add a panel with information about the number of interfaces to the device page
|
||||||
'include_interfaces_panel': False,
|
'include_interfaces_panel': False,
|
||||||
# Compare description during diff
|
# Consider component descriptions when comparing. If this option is set to True, then take into account
|
||||||
# If compare is true, description will also be synced to device
|
# component descriptions when comparing components and synchronizing their attributes, otherwise - ignore
|
||||||
# Otherwise not.
|
'sync_descriptions': True
|
||||||
'compare_description': True
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,25 +1,30 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
from attrs import fields
|
from attrs import fields
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
from netbox.models import PrimaryModel
|
||||||
|
|
||||||
config = settings.PLUGINS_CONFIG["netbox_interface_sync"]
|
config = settings.PLUGINS_CONFIG["netbox_interface_sync"]
|
||||||
COMPARE_DESCRIPTIONS: bool = config["compare_description"]
|
SYNC_DESCRIPTIONS: bool = config["sync_descriptions"]
|
||||||
|
|
||||||
|
|
||||||
@attr.s(frozen=True, auto_attribs=True)
|
@attr.s(frozen=True, auto_attribs=True)
|
||||||
class BaseComparison:
|
class BaseComparison:
|
||||||
"""Common fields of a device component"""
|
"""Common fields of a device component"""
|
||||||
|
|
||||||
# Do not compare IDs
|
# Do not compare IDs
|
||||||
id: int = attr.ib(eq=False, hash=False, metadata={'printable': False})
|
id: int = attr.ib(eq=False, metadata={'printable': False, 'netbox_exportable': False})
|
||||||
# Compare names case-insensitively and spaces-insensitively
|
# Compare names case-insensitively and spaces-insensitively
|
||||||
name: str = attr.ib(eq=lambda name: name.lower().replace(" ", ""), metadata={'printable': False})
|
name: str = attr.ib(metadata={'printable': False})
|
||||||
label: str = attr.ib(hash=False)
|
label: str = attr.ib()
|
||||||
# Compare descriptions if it is set by the configuration
|
# Compare descriptions if it is set by the configuration
|
||||||
description: str = attr.ib(eq=COMPARE_DESCRIPTIONS, hash=False)
|
description: str = attr.ib(eq=SYNC_DESCRIPTIONS, metadata={'synced': SYNC_DESCRIPTIONS})
|
||||||
# Do not compare `is_template` properties
|
# Do not compare `is_template` properties
|
||||||
is_template: bool = attr.ib(kw_only=True, default=False, eq=False, hash=False, metadata={'printable': False})
|
is_template: bool = attr.ib(
|
||||||
|
default=False, kw_only=True, eq=False,
|
||||||
|
metadata={'printable': False, 'netbox_exportable': False}
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fields_display(self) -> str:
|
def fields_display(self) -> str:
|
||||||
@ -32,46 +37,31 @@ class BaseComparison:
|
|||||||
if not field_value:
|
if not field_value:
|
||||||
continue
|
continue
|
||||||
field_caption = field.metadata.get('displayed_caption') or field.name.replace('_', ' ').capitalize()
|
field_caption = field.metadata.get('displayed_caption') or field.name.replace('_', ' ').capitalize()
|
||||||
|
if isinstance(field_value, BaseComparison):
|
||||||
|
field_value = f'{field_value.name} (ID: {field_value.id})'
|
||||||
fields_to_display.append(f'{field_caption}: {field_value}')
|
fields_to_display.append(f'{field_caption}: {field_value}')
|
||||||
return '\n'.join(fields_to_display)
|
return '\n'.join(fields_to_display)
|
||||||
|
|
||||||
|
def get_fields_for_netbox_component(self, sync=False):
|
||||||
|
"""
|
||||||
|
Returns a dict of fields and values for creating or updating a NetBox component object
|
||||||
|
:param sync: if True, returns fields for syncing an existing component, otherwise - for creating a new one.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def field_filter(field: attr.Attribute, _):
|
||||||
|
result = field.metadata.get('netbox_exportable', True)
|
||||||
|
if sync:
|
||||||
|
result &= field.metadata.get('synced', True)
|
||||||
|
return result
|
||||||
|
|
||||||
|
return attr.asdict(self, recurse=True, filter=field_filter)
|
||||||
|
|
||||||
|
|
||||||
@attr.s(frozen=True, auto_attribs=True)
|
@attr.s(frozen=True, auto_attribs=True)
|
||||||
class BaseTypedComparison(BaseComparison):
|
class BaseTypedComparison(BaseComparison):
|
||||||
"""Common fields of a device typed component"""
|
"""Common fields of a device typed component"""
|
||||||
|
type: str = attr.ib(metadata={'printable': False})
|
||||||
type: str = attr.ib(hash=False, metadata={'printable': False})
|
type_display: str = attr.ib(eq=False, metadata={'displayed_caption': 'Type', 'netbox_exportable': False})
|
||||||
type_display: str = attr.ib(eq=False, hash=False, metadata={'displayed_caption': 'Type'})
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(frozen=True, auto_attribs=True)
|
|
||||||
class DeviceBayComparison(BaseComparison):
|
|
||||||
"""A unified way to represent the device bay and device bay template"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(frozen=True, auto_attribs=True)
|
|
||||||
class InterfaceComparison(BaseTypedComparison):
|
|
||||||
"""A unified way to represent the interface and interface template"""
|
|
||||||
|
|
||||||
mgmt_only: bool = attr.ib(hash=False)
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(frozen=True, auto_attribs=True)
|
|
||||||
class FrontPortComparison(BaseTypedComparison):
|
|
||||||
"""A unified way to represent the front port and front port template"""
|
|
||||||
|
|
||||||
color: str = attr.ib(hash=False)
|
|
||||||
# rear_port_id: int
|
|
||||||
rear_port_position: int = attr.ib(hash=False, metadata={'displayed_caption': 'Position'})
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(frozen=True, auto_attribs=True)
|
|
||||||
class RearPortComparison(BaseTypedComparison):
|
|
||||||
"""A unified way to represent the rear port and rear port template"""
|
|
||||||
|
|
||||||
color: str = attr.ib(hash=False)
|
|
||||||
positions: int = attr.ib(hash=False)
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(frozen=True, auto_attribs=True)
|
@attr.s(frozen=True, auto_attribs=True)
|
||||||
@ -89,14 +79,78 @@ class ConsoleServerPortComparison(BaseTypedComparison):
|
|||||||
@attr.s(frozen=True, auto_attribs=True)
|
@attr.s(frozen=True, auto_attribs=True)
|
||||||
class PowerPortComparison(BaseTypedComparison):
|
class PowerPortComparison(BaseTypedComparison):
|
||||||
"""A unified way to represent the power port and power port template"""
|
"""A unified way to represent the power port and power port template"""
|
||||||
|
maximum_draw: str = attr.ib()
|
||||||
maximum_draw: str = attr.ib(hash=False)
|
allocated_draw: str = attr.ib()
|
||||||
allocated_draw: str = attr.ib(hash=False)
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(frozen=True, auto_attribs=True)
|
@attr.s(frozen=True, auto_attribs=True)
|
||||||
class PowerOutletComparison(BaseTypedComparison):
|
class PowerOutletComparison(BaseTypedComparison):
|
||||||
"""A unified way to represent the power outlet and power outlet template"""
|
"""A unified way to represent the power outlet and power outlet template"""
|
||||||
|
power_port: PowerPortComparison = attr.ib()
|
||||||
|
feed_leg: str = attr.ib()
|
||||||
|
|
||||||
power_port_name: str = attr.ib(hash=False)
|
|
||||||
feed_leg: str = attr.ib(hash=False)
|
@attr.s(frozen=True, auto_attribs=True)
|
||||||
|
class InterfaceComparison(BaseTypedComparison):
|
||||||
|
"""A unified way to represent the interface and interface template"""
|
||||||
|
mgmt_only: bool = attr.ib()
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(frozen=True, auto_attribs=True)
|
||||||
|
class FrontPortComparison(BaseTypedComparison):
|
||||||
|
"""A unified way to represent the front port and front port template"""
|
||||||
|
color: str = attr.ib()
|
||||||
|
# rear_port_id: int
|
||||||
|
rear_port_position: int = attr.ib(metadata={'displayed_caption': 'Position'})
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(frozen=True, auto_attribs=True)
|
||||||
|
class RearPortComparison(BaseTypedComparison):
|
||||||
|
"""A unified way to represent the rear port and rear port template"""
|
||||||
|
color: str = attr.ib()
|
||||||
|
positions: int = attr.ib()
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(frozen=True, auto_attribs=True)
|
||||||
|
class DeviceBayComparison(BaseComparison):
|
||||||
|
"""A unified way to represent the device bay and device bay template"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def from_netbox_object(netbox_object: PrimaryModel) -> Optional[BaseComparison]:
|
||||||
|
"""Makes a comparison object from the NetBox object"""
|
||||||
|
type_map = {
|
||||||
|
"DeviceBay": DeviceBayComparison,
|
||||||
|
"Interface": InterfaceComparison,
|
||||||
|
"FrontPort": FrontPortComparison,
|
||||||
|
"RearPort": RearPortComparison,
|
||||||
|
"ConsolePort": ConsolePortComparison,
|
||||||
|
"ConsoleServerPort": ConsoleServerPortComparison,
|
||||||
|
"PowerPort": PowerPortComparison,
|
||||||
|
"PowerOutlet": PowerOutletComparison
|
||||||
|
}
|
||||||
|
|
||||||
|
obj_name = netbox_object._meta.object_name
|
||||||
|
if obj_name.endswith("Template"):
|
||||||
|
is_template = True
|
||||||
|
obj_name = obj_name[:-8] # TODO: use `removesuffix` introduced in Python 3.9
|
||||||
|
else:
|
||||||
|
is_template = False
|
||||||
|
|
||||||
|
comparison = type_map.get(obj_name)
|
||||||
|
if not comparison:
|
||||||
|
return
|
||||||
|
|
||||||
|
values = {}
|
||||||
|
for field in fields(comparison):
|
||||||
|
if field.name == "is_template":
|
||||||
|
continue
|
||||||
|
if field.name == "type_display":
|
||||||
|
values[field.name] = netbox_object.get_type_display()
|
||||||
|
else:
|
||||||
|
field_value = getattr(netbox_object, field.name)
|
||||||
|
if isinstance(field_value, PrimaryModel):
|
||||||
|
field_value = from_netbox_object(field_value)
|
||||||
|
values[field.name] = field_value
|
||||||
|
|
||||||
|
return comparison(**values, is_template=is_template)
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
from django import forms
|
|
||||||
|
|
||||||
|
|
||||||
class ComponentComparisonForm(forms.Form):
|
|
||||||
add_to_device = forms.BooleanField(required=False)
|
|
||||||
remove_from_device = forms.BooleanField(required=False)
|
|
@ -1,9 +1,9 @@
|
|||||||
{% if perms.dcim.change_device %}
|
{% if perms.dcim.change_device %}
|
||||||
<div class="dropdown">
|
<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">
|
<button id="device-type-sync" type="button" class="btn btn-sm btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
Device type sync
|
Device type sync
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" aria-labeled-by="add-device-components">
|
<ul class="dropdown-menu" aria-labeled-by="device-type-sync">
|
||||||
{% if perms.dcim.add_consoleport %}
|
{% if perms.dcim.add_consoleport %}
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:consoleport_comparison' device_id=device.id %}">
|
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:consoleport_comparison' device_id=device.id %}">
|
||||||
@ -39,13 +39,13 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_frontport %}
|
{# {% if perms.dcim.add_frontport %}#}
|
||||||
<li>
|
{# <li>#}
|
||||||
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:frontport_comparison' device_id=device.id %}">
|
{# <a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:frontport_comparison' device_id=device.id %}">#}
|
||||||
Front Ports
|
{# Front Ports#}
|
||||||
</a>
|
{# </a>#}
|
||||||
</li>
|
{# </li>#}
|
||||||
{% endif %}
|
{# {% endif %}#}
|
||||||
{% if perms.dcim.add_rearport %}
|
{% if perms.dcim.add_rearport %}
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:rearport_comparison' device_id=device.id %}">
|
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:rearport_comparison' device_id=device.id %}">
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{% extends 'base/layout.html' %}
|
{% extends 'base/layout.html' %}
|
||||||
|
|
||||||
{% block title %}{{ device }} - {{ component_type }} comparison{% endblock %}
|
{% block title %}{{ device }} - {{ component_type_name|capfirst }} comparison{% endblock %}
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
|
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
@ -47,11 +47,11 @@ function uncheck(event) {
|
|||||||
<table class="table table-hover table-bordered">
|
<table class="table table-hover table-bordered">
|
||||||
{% if templates_count == components_count %}
|
{% if templates_count == components_count %}
|
||||||
<caption class="caption-green">
|
<caption class="caption-green">
|
||||||
The device and device type have the same number of {{ component_type }}.
|
The device and device type have the same number of {{ component_type_name }}.
|
||||||
</caption>
|
</caption>
|
||||||
{% else %}
|
{% else %}
|
||||||
<caption class="caption-red">
|
<caption class="caption-red">
|
||||||
The device and device type have different number of {{ component_type|lower }}.<br>
|
The device and device type have different number of {{ component_type_name }}.<br>
|
||||||
Device: {{ components_count }}<br>
|
Device: {{ components_count }}<br>
|
||||||
Device type: {{ templates_count }}
|
Device type: {{ templates_count }}
|
||||||
</caption>
|
</caption>
|
||||||
@ -68,7 +68,7 @@ function uncheck(event) {
|
|||||||
<th scope="col">Attributes</th>
|
<th scope="col">Attributes</th>
|
||||||
<th scope="col">
|
<th scope="col">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" id="add_to_device" onclick="toggle(this)">
|
<input type="checkbox" id="add" onclick="toggle(this)">
|
||||||
Add to the device
|
Add to the device
|
||||||
</label>
|
</label>
|
||||||
</th>
|
</th>
|
||||||
@ -76,14 +76,14 @@ function uncheck(event) {
|
|||||||
<th scope="col">Attributes</th>
|
<th scope="col">Attributes</th>
|
||||||
<th scope="col">
|
<th scope="col">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" id="remove_from_device" onclick="toggle(this)">
|
<input type="checkbox" id="remove" onclick="toggle(this)">
|
||||||
Remove
|
Remove
|
||||||
</label>
|
</label>
|
||||||
</th>
|
</th>
|
||||||
<th scope="col">
|
<th scope="col">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" id="fix_name" onclick="toggle(this)">
|
<input type="checkbox" id="sync" onclick="toggle(this)">
|
||||||
Fix the name
|
Sync attributes
|
||||||
</label>
|
</label>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -103,7 +103,7 @@ function uncheck(event) {
|
|||||||
<td {% if not component %}class="table-danger"{% endif %}>
|
<td {% if not component %}class="table-danger"{% endif %}>
|
||||||
{% if not component %}
|
{% if not component %}
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" name="add_to_device" value="{{ component_template.id }}" onclick="uncheck(this)">
|
<input type="checkbox" name="add" value="{{ component_template.id }}" onclick="uncheck(this)">
|
||||||
Add to device
|
Add to device
|
||||||
</label>
|
</label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -126,16 +126,16 @@ function uncheck(event) {
|
|||||||
<td {% if not component_template %}class="table-success"{% endif %}>
|
<td {% if not component_template %}class="table-success"{% endif %}>
|
||||||
{% if not component_template %}
|
{% if not component_template %}
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" name="remove_from_device" value="{{ component.id }}" onclick="uncheck(this)">
|
<input type="checkbox" name="remove" value="{{ component.id }}" onclick="uncheck(this)">
|
||||||
Remove
|
Remove
|
||||||
</label>
|
</label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td {% if not component_template %}class="table-success"{% endif %}>
|
<td {% if not component_template %}class="table-success"{% endif %}>
|
||||||
{% if component_template and component_template.name != component.name %}
|
{% if component_template and component_template != component %}
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" name="fix_name" value="{{ component.id }}" onclick="uncheck(this)">
|
<input type="checkbox" name="sync" value="{{ component.id }}" onclick="uncheck(this)">
|
||||||
Fix name
|
Sync attributes
|
||||||
</label>
|
</label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
@ -6,16 +6,6 @@ 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(
|
|
||||||
"powerport-comparison/<int:device_id>/",
|
|
||||||
views.PowerPortComparisonView.as_view(),
|
|
||||||
name="powerport_comparison",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"consoleport-comparison/<int:device_id>/",
|
"consoleport-comparison/<int:device_id>/",
|
||||||
views.ConsolePortComparisonView.as_view(),
|
views.ConsolePortComparisonView.as_view(),
|
||||||
@ -26,16 +16,26 @@ urlpatterns = (
|
|||||||
views.ConsoleServerPortComparisonView.as_view(),
|
views.ConsoleServerPortComparisonView.as_view(),
|
||||||
name="consoleserverport_comparison",
|
name="consoleserverport_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(
|
path(
|
||||||
"poweroutlet-comparison/<int:device_id>/",
|
"poweroutlet-comparison/<int:device_id>/",
|
||||||
views.PowerOutletComparisonView.as_view(),
|
views.PowerOutletComparisonView.as_view(),
|
||||||
name="poweroutlet_comparison",
|
name="poweroutlet_comparison",
|
||||||
),
|
),
|
||||||
path(
|
# path(
|
||||||
"frontport-comparison/<int:device_id>/",
|
# "frontport-comparison/<int:device_id>/",
|
||||||
views.FrontPortComparisonView.as_view(),
|
# views.FrontPortComparisonView.as_view(),
|
||||||
name="frontport_comparison",
|
# name="frontport_comparison",
|
||||||
),
|
# ),
|
||||||
path(
|
path(
|
||||||
"rearport-comparison/<int:device_id>/",
|
"rearport-comparison/<int:device_id>/",
|
||||||
views.RearPortComparisonView.as_view(),
|
views.RearPortComparisonView.as_view(),
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
from typing import Iterable
|
from typing import Iterable, List
|
||||||
from django.shortcuts import render, redirect
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
config = settings.PLUGINS_CONFIG['netbox_interface_sync']
|
config = settings.PLUGINS_CONFIG['netbox_interface_sync']
|
||||||
@ -22,117 +19,21 @@ def human_sorted(iterable: Iterable):
|
|||||||
return sorted(iterable, key=natural_keys)
|
return sorted(iterable, key=natural_keys)
|
||||||
|
|
||||||
|
|
||||||
def get_components(request, device, components, unified_components, unified_component_templates, component_type):
|
def make_integer_list(lst: List[str]):
|
||||||
# List of components and components templates presented in the unified format
|
return [int(i) for i in lst if i.isdigit()]
|
||||||
overall_powers = list(set(unified_component_templates + unified_components))
|
|
||||||
overall_powers.sort(key=lambda o: natural_keys(o.name))
|
|
||||||
|
|
||||||
comparison_templates = []
|
|
||||||
comparison_components = []
|
|
||||||
for i in overall_powers:
|
|
||||||
try:
|
|
||||||
comparison_templates.append(
|
|
||||||
unified_component_templates[unified_component_templates.index(i)]
|
|
||||||
)
|
|
||||||
except ValueError:
|
|
||||||
comparison_templates.append(None)
|
|
||||||
|
|
||||||
try:
|
|
||||||
comparison_components.append(
|
|
||||||
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(
|
def get_permissions_for_model(model, actions: Iterable[str]) -> List[str]:
|
||||||
request, device, components, component_templates, ObjectType, ObjectTemplateType, unified_component, unified_component_templates, component_type
|
"""
|
||||||
):
|
Resolve a list of permissions for a given model (or instance).
|
||||||
# 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
|
:param model: A model or instance
|
||||||
deleted = ObjectType.objects.filter(id__in=remove_from_device).delete()[0]
|
:param actions: List of actions: view, add, change, or delete
|
||||||
|
"""
|
||||||
|
permissions = []
|
||||||
|
for action in actions:
|
||||||
|
if action not in ("view", "add", "change", "delete"):
|
||||||
|
raise ValueError(f"Unsupported action: {action}")
|
||||||
|
permissions.append(f'{model._meta.app_label}.{action}_{model._meta.model_name}')
|
||||||
|
|
||||||
# Add selected components to the device and count them
|
return permissions
|
||||||
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)
|
|
||||||
|
@ -1,571 +1,461 @@
|
|||||||
from django.shortcuts import get_object_or_404, redirect
|
from collections import namedtuple
|
||||||
|
from typing import Type, Tuple
|
||||||
|
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
from dcim.models import Device, Interface, InterfaceTemplate, PowerPort, PowerPortTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, DeviceBay, DeviceBayTemplate, FrontPort, FrontPortTemplate,PowerOutlet, PowerOutletTemplate, RearPort, RearPortTemplate
|
from dcim.models import (Device, Interface, InterfaceTemplate, PowerPort, PowerPortTemplate, ConsolePort,
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, DeviceBay,
|
||||||
|
DeviceBayTemplate, FrontPort, FrontPortTemplate, PowerOutlet, PowerOutletTemplate, RearPort,
|
||||||
|
RearPortTemplate)
|
||||||
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
|
||||||
from .utils import get_components, post_components
|
from netbox.models import PrimaryModel
|
||||||
from .comparison import FrontPortComparison, PowerPortComparison, PowerOutletComparison, InterfaceComparison, ConsolePortComparison, ConsoleServerPortComparison, DeviceBayComparison, RearPortComparison
|
from dcim.constants import VIRTUAL_IFACE_TYPES
|
||||||
from .forms import ComponentComparisonForm
|
|
||||||
|
from . import comparison
|
||||||
|
from .utils import get_permissions_for_model, make_integer_list, human_sorted
|
||||||
|
|
||||||
config = settings.PLUGINS_CONFIG['netbox_interface_sync']
|
config = settings.PLUGINS_CONFIG['netbox_interface_sync']
|
||||||
|
ComparisonTableRow = namedtuple('ComparisonTableRow', ('component_template', 'component'))
|
||||||
|
|
||||||
|
|
||||||
class InterfaceComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
|
class GenericComparisonView(PermissionRequiredMixin, View):
|
||||||
"""Comparison of interfaces between a device and a device type and beautiful visualization"""
|
"""
|
||||||
permission_required = ("dcim.view_interface", "dcim.add_interface", "dcim.change_interface", "dcim.delete_interface")
|
Generic object comparison view
|
||||||
|
|
||||||
|
obj_model: Model of the object involved in the comparison (for example, Interface)
|
||||||
|
obj_template_model: Model of the object template involved in the comparison (for example, InterfaceTemplate)
|
||||||
|
"""
|
||||||
|
obj_model: Type[PrimaryModel] = None
|
||||||
|
obj_template_model: Type[PrimaryModel] = None
|
||||||
|
|
||||||
|
def get_permission_required(self):
|
||||||
|
# User must have permission to view the device whose components are being compared
|
||||||
|
permissions = ["dcim.view_device"]
|
||||||
|
|
||||||
|
# Resolve permissions related to the object and the object template
|
||||||
|
permissions.extend(get_permissions_for_model(self.obj_model, ("view", "add", "change", "delete")))
|
||||||
|
permissions.extend(get_permissions_for_model(self.obj_template_model, ("view",)))
|
||||||
|
|
||||||
|
return permissions
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def filter_comparison_components(component_templates: QuerySet, components: QuerySet) -> Tuple[QuerySet, QuerySet]:
|
||||||
|
"""Override this in the inherited View to implement special comparison objects filtering logic"""
|
||||||
|
return component_templates, components
|
||||||
|
|
||||||
|
def _fetch_comparison_objects(self, device_id: int):
|
||||||
|
self.device = get_object_or_404(Device, id=device_id)
|
||||||
|
component_templates = self.obj_template_model.objects.filter(device_type_id=self.device.device_type.id)
|
||||||
|
components = self.obj_model.objects.filter(device_id=device_id)
|
||||||
|
self.component_templates, self.components = self.filter_comparison_components(component_templates, components)
|
||||||
|
self.comparison_component_templates = [comparison.from_netbox_object(obj) for obj in self.component_templates]
|
||||||
|
self.comparison_components = [comparison.from_netbox_object(obj) for obj in self.components]
|
||||||
|
|
||||||
|
name_comparison_config = config['name_comparison']
|
||||||
|
|
||||||
|
def name_key(obj_name: str) -> str:
|
||||||
|
name = obj_name
|
||||||
|
if name_comparison_config.get('case-insensitive'):
|
||||||
|
name = name.lower()
|
||||||
|
if name_comparison_config.get('space-insensitive'):
|
||||||
|
name = name.replace(' ', '')
|
||||||
|
return name
|
||||||
|
|
||||||
|
component_templates_dict = {name_key(obj.name): obj for obj in self.comparison_component_templates}
|
||||||
|
components_dict = {name_key(obj.name): obj for obj in self.comparison_components}
|
||||||
|
|
||||||
|
self.comparison_table = tuple(
|
||||||
|
ComparisonTableRow(
|
||||||
|
component_template=component_templates_dict.get(component_name),
|
||||||
|
component=components_dict.get(component_name)
|
||||||
|
)
|
||||||
|
for component_name in human_sorted(set().union(component_templates_dict.keys(), components_dict.keys()))
|
||||||
|
)
|
||||||
|
|
||||||
def get(self, request, device_id):
|
def get(self, request, device_id):
|
||||||
device = get_object_or_404(Device.objects.filter(id=device_id))
|
self._fetch_comparison_objects(device_id)
|
||||||
interfaces = device.vc_interfaces()
|
|
||||||
if config["exclude_virtual_interfaces"]:
|
|
||||||
interfaces = list(filter(lambda i: not i.is_virtual, interfaces))
|
|
||||||
interface_templates = InterfaceTemplate.objects.filter(device_type=device.device_type)
|
|
||||||
|
|
||||||
unified_interfaces = [InterfaceComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.mgmt_only) for i in interfaces]
|
return render(request, "netbox_interface_sync/components_comparison.html", {
|
||||||
unified_interface_templates = [
|
"component_type_name": self.obj_model._meta.verbose_name_plural,
|
||||||
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]
|
"comparison_items": self.comparison_table,
|
||||||
|
"templates_count": len(self.comparison_component_templates),
|
||||||
return get_components(request, device, interfaces, unified_interfaces, unified_interface_templates, "Interfaces")
|
"components_count": len(self.comparison_components),
|
||||||
|
"device": self.device,
|
||||||
|
})
|
||||||
|
|
||||||
def post(self, request, device_id):
|
def post(self, request, device_id):
|
||||||
form = ComponentComparisonForm(request.POST)
|
components_to_add = make_integer_list(request.POST.getlist("add"))
|
||||||
if form.is_valid():
|
components_to_delete = make_integer_list(request.POST.getlist("remove"))
|
||||||
device = get_object_or_404(Device.objects.filter(id=device_id))
|
components_to_sync = make_integer_list(request.POST.getlist("sync"))
|
||||||
interfaces = device.vc_interfaces()
|
if not any((components_to_add, components_to_delete, components_to_sync)):
|
||||||
if config["exclude_virtual_interfaces"]:
|
messages.warning(request, "No actions selected")
|
||||||
interfaces = interfaces.exclude(type__in=["virtual", "lag"])
|
return redirect(request.path)
|
||||||
interface_templates = InterfaceTemplate.objects.filter(device_type=device.device_type)
|
|
||||||
|
|
||||||
# Getting and validating a list of interfaces to rename
|
self._fetch_comparison_objects(device_id)
|
||||||
fix_name_components = filter(
|
|
||||||
lambda i: str(i.id) in request.POST.getlist("fix_name"), interfaces
|
|
||||||
)
|
|
||||||
|
|
||||||
unified_interface_templates = [
|
component_ids_to_delete = []
|
||||||
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]
|
components_to_bulk_create = []
|
||||||
|
synced_count = 0
|
||||||
unified_interfaces = []
|
for template, component in self.comparison_table:
|
||||||
|
if template and (template.id in components_to_add):
|
||||||
|
# Add component to the device from the template
|
||||||
|
components_to_bulk_create.append(
|
||||||
|
self.obj_model(device=self.device, **template.get_fields_for_netbox_component())
|
||||||
|
)
|
||||||
|
elif component and (component.id in components_to_delete):
|
||||||
|
# Delete component from the device
|
||||||
|
component_ids_to_delete.append(component.id)
|
||||||
|
elif (template and component) and (component.id in components_to_sync):
|
||||||
|
# Update component attributes from the template
|
||||||
|
synced_count += self.components.filter(id=component.id).update(
|
||||||
|
**template.get_fields_for_netbox_component(sync=True)
|
||||||
|
)
|
||||||
|
|
||||||
for component in fix_name_components:
|
deleted_count = self.obj_model.objects.filter(id__in=component_ids_to_delete).delete()[0]
|
||||||
unified_interfaces.append((component, InterfaceComparison(
|
created_count = len(self.obj_model.objects.bulk_create(components_to_bulk_create))
|
||||||
component.id,
|
|
||||||
component.name,
|
|
||||||
component.label,
|
|
||||||
component.description,
|
|
||||||
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):
|
# Generating result message
|
||||||
"""Comparison of power ports between a device and a device type and beautiful visualization"""
|
component_type_name = self.obj_model._meta.verbose_name_plural
|
||||||
permission_required = ("dcim.view_powerport", "dcim.add_powerport", "dcim.change_powerport", "dcim.delete_powerport")
|
message = []
|
||||||
|
if synced_count > 0:
|
||||||
|
message.append(f"synced {synced_count} {component_type_name}")
|
||||||
|
if created_count > 0:
|
||||||
|
message.append(f"created {created_count} {component_type_name}")
|
||||||
|
if deleted_count > 0:
|
||||||
|
message.append(f"deleted {deleted_count} {component_type_name}")
|
||||||
|
messages.success(request, "; ".join(message).capitalize())
|
||||||
|
|
||||||
def get(self, request, device_id):
|
return redirect(request.path)
|
||||||
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):
|
class ConsolePortComparisonView(GenericComparisonView):
|
||||||
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"""
|
"""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")
|
obj_model = ConsolePort
|
||||||
|
obj_template_model = ConsolePortTemplate
|
||||||
|
|
||||||
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]
|
class ConsoleServerPortComparisonView(GenericComparisonView):
|
||||||
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"""
|
"""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")
|
obj_model = ConsoleServerPort
|
||||||
|
obj_template_model = ConsoleServerPortTemplate
|
||||||
|
|
||||||
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]
|
class InterfaceComparisonView(GenericComparisonView):
|
||||||
unified_consoleserverport_templates = [
|
"""Comparison of interfaces between a device and a device type and beautiful visualization"""
|
||||||
ConsoleServerPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), is_template=True) for i in consoleserverports_templates]
|
obj_model = Interface
|
||||||
|
obj_template_model = InterfaceTemplate
|
||||||
|
|
||||||
return get_components(request, device, consoleserverports, unified_consoleserverports, unified_consoleserverport_templates, "Console server ports")
|
@staticmethod
|
||||||
|
def filter_comparison_components(component_templates: QuerySet, components: QuerySet) -> Tuple[QuerySet, QuerySet]:
|
||||||
|
if config["exclude_virtual_interfaces"]:
|
||||||
|
components = components.exclude(type__in=VIRTUAL_IFACE_TYPES)
|
||||||
|
component_templates = component_templates.exclude(type__in=VIRTUAL_IFACE_TYPES)
|
||||||
|
return component_templates, components
|
||||||
|
|
||||||
|
|
||||||
|
class PowerPortComparisonView(GenericComparisonView):
|
||||||
|
"""Comparison of power ports between a device and a device type and beautiful visualization"""
|
||||||
|
obj_model = PowerPort
|
||||||
|
obj_template_model = PowerPortTemplate
|
||||||
|
|
||||||
|
|
||||||
|
class PowerOutletComparisonView(GenericComparisonView):
|
||||||
|
"""Comparison of power outlets between a device and a device type and beautiful visualization"""
|
||||||
|
obj_model = PowerOutlet
|
||||||
|
obj_template_model = PowerOutletTemplate
|
||||||
|
|
||||||
def post(self, request, device_id):
|
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))
|
device = get_object_or_404(Device.objects.filter(id=device_id))
|
||||||
|
|
||||||
poweroutlets = device.poweroutlets.all()
|
poweroutlets = device.poweroutlets.all()
|
||||||
poweroutlets_templates = PowerOutletTemplate.objects.filter(device_type=device.device_type)
|
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")
|
# Generating result message
|
||||||
|
message = []
|
||||||
|
created = 0
|
||||||
|
updated = 0
|
||||||
|
fixed = 0
|
||||||
|
|
||||||
def post(self, request, device_id):
|
remove_from_device = filter(
|
||||||
form = ComponentComparisonForm(request.POST)
|
lambda i: i in poweroutlets.values_list("id", flat=True),
|
||||||
if form.is_valid():
|
map(int, filter(lambda x: x.isdigit(), request.POST.getlist("remove")))
|
||||||
device = get_object_or_404(Device.objects.filter(id=device_id))
|
)
|
||||||
|
|
||||||
poweroutlets = device.poweroutlets.all()
|
# Remove selected power outlets from the device and count them
|
||||||
poweroutlets_templates = PowerOutletTemplate.objects.filter(device_type=device.device_type)
|
deleted = PowerOutlet.objects.filter(id__in=remove_from_device).delete()[0]
|
||||||
|
|
||||||
# Generating result message
|
# Get device power ports to check dependency between power outlets
|
||||||
message = []
|
device_pp = PowerPort.objects.filter(device_id=device.id)
|
||||||
created = 0
|
|
||||||
updated = 0
|
matching = {}
|
||||||
fixed = 0
|
mismatch = False
|
||||||
|
for i in poweroutlets_templates:
|
||||||
remove_from_device = filter(
|
found = False
|
||||||
lambda i: i in poweroutlets.values_list("id", flat=True),
|
if i.power_port_id is not None:
|
||||||
map(int, filter(lambda x: x.isdigit(), request.POST.getlist("remove_from_device")))
|
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")))
|
||||||
)
|
)
|
||||||
|
|
||||||
# Remove selected power outlets from the device and count them
|
# Add selected component to the device and count them
|
||||||
deleted = PowerOutlet.objects.filter(id__in=remove_from_device).delete()[0]
|
add_to_device_component = PowerOutletTemplate.objects.filter(id__in=add_to_device)
|
||||||
|
|
||||||
# Get device power ports to check dependency between power outlets
|
bulk_create = []
|
||||||
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)
|
|
||||||
|
|
||||||
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
|
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
|
fixed = 0
|
||||||
|
for component in fix_name_components:
|
||||||
remove_from_device = filter(
|
unified_poweroutlet = PowerOutletComparison(
|
||||||
lambda i: i in frontports.values_list("id", flat=True),
|
component.id, component.name, component.label, component.description, component.type,
|
||||||
map(int, filter(lambda x: x.isdigit(), request.POST.getlist("remove_from_device")))
|
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 "",
|
||||||
# Remove selected front ports from the device and count them
|
feed_leg=component.feed_leg
|
||||||
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")))
|
|
||||||
)
|
)
|
||||||
|
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, "Dependency detected, sync power ports first!")
|
||||||
|
|
||||||
# Add selected component to the device and count them
|
if created > 0:
|
||||||
add_to_device_component = FrontPortTemplate.objects.filter(id__in=add_to_device)
|
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")
|
||||||
|
|
||||||
bulk_create = []
|
messages.info(request, "; ".join(message).capitalize())
|
||||||
updated = 0
|
|
||||||
keys_to_avoid = ["id"]
|
|
||||||
|
|
||||||
if not config["compare_description"]:
|
return redirect(request.path)
|
||||||
keys_to_avoid.append("description")
|
|
||||||
|
|
||||||
for i in add_to_device_component.values():
|
|
||||||
to_create = False
|
|
||||||
|
|
||||||
try:
|
class RearPortComparisonView(GenericComparisonView):
|
||||||
# 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"""
|
"""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")
|
obj_model = RearPort
|
||||||
|
obj_template_model = RearPortTemplate
|
||||||
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)
|
class DeviceBayComparisonView(GenericComparisonView):
|
||||||
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"""
|
"""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")
|
obj_model = DeviceBay
|
||||||
|
obj_template_model = DeviceBayTemplate
|
||||||
def get(self, request, device_id):
|
#
|
||||||
device = get_object_or_404(Device.objects.filter(id=device_id))
|
#
|
||||||
|
# class FrontPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
|
||||||
devicebays = device.devicebays.all()
|
# """Comparison of front ports between a device and a device type and beautiful visualization"""
|
||||||
devicebays_templates = DeviceBayTemplate.objects.filter(device_type=device.device_type)
|
# permission_required = get_permissions_for_object("dcim", "frontport")
|
||||||
|
#
|
||||||
unified_devicebays = [DeviceBayComparison(i.id, i.name, i.label, i.description) for i in devicebays]
|
# def get(self, request, device_id):
|
||||||
unified_devicebay_templates = [
|
#
|
||||||
DeviceBayComparison(i.id, i.name, i.label, i.description, is_template=True) for i in devicebays_templates]
|
# device = get_object_or_404(Device.objects.filter(id=device_id))
|
||||||
|
#
|
||||||
return get_components(request, device, devicebays, unified_devicebays, unified_devicebay_templates, "Device bays")
|
# frontports = device.frontports.all()
|
||||||
|
# frontports_templates = FrontPortTemplate.objects.filter(device_type=device.device_type)
|
||||||
def post(self, request, device_id):
|
#
|
||||||
form = ComponentComparisonForm(request.POST)
|
# unified_frontports = [
|
||||||
if form.is_valid():
|
# FrontPortComparison(
|
||||||
device = get_object_or_404(Device.objects.filter(id=device_id))
|
# i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.color, i.rear_port_position)
|
||||||
|
# for i in frontports]
|
||||||
devicebays = device.devicebays.all()
|
# unified_frontports_templates = [
|
||||||
devicebays_templates = DeviceBayTemplate.objects.filter(device_type=device.device_type)
|
# FrontPortComparison(
|
||||||
|
# i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.color,
|
||||||
# Getting and validating a list of devicebays to rename
|
# i.rear_port_position, is_template=True)
|
||||||
fix_name_components = filter(
|
# for i in frontports_templates]
|
||||||
lambda i: str(i.id) in request.POST.getlist("fix_name"), devicebays
|
#
|
||||||
)
|
# return get_components(request, device, frontports, unified_frontports, unified_frontports_templates)
|
||||||
|
#
|
||||||
unified_devicebay_templates = [
|
# def post(self, request, device_id):
|
||||||
DeviceBayComparison(i.id, i.name, i.label, i.description, is_template=True) for i in devicebays_templates]
|
# form = ComponentComparisonForm(request.POST)
|
||||||
|
# if form.is_valid():
|
||||||
unified_devicebays = []
|
# device = get_object_or_404(Device.objects.filter(id=device_id))
|
||||||
|
#
|
||||||
for component in fix_name_components:
|
# frontports = device.frontports.all()
|
||||||
unified_devicebays.append((component, DeviceBayComparison(
|
# frontports_templates = FrontPortTemplate.objects.filter(device_type=device.device_type)
|
||||||
component.id,
|
#
|
||||||
component.name,
|
# # Generating result message
|
||||||
component.label,
|
# message = []
|
||||||
component.description
|
# created = 0
|
||||||
)))
|
# updated = 0
|
||||||
|
# fixed = 0
|
||||||
return post_components(request, device, devicebays, devicebays_templates, DeviceBay, DeviceBayTemplate, unified_devicebays, unified_devicebay_templates, "device bays")
|
#
|
||||||
|
# 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 front 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, "Dependency 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)
|
||||||
|
Loading…
Reference in New Issue
Block a user