Refactoring: stage 1

This commit is contained in:
Victor Golovanenko 2022-03-03 12:39:16 +03:00
parent 87d12f05f8
commit ba0d9c9736
Signed by: drygdryg
GPG Key ID: 50E0F568281DB835
8 changed files with 573 additions and 727 deletions

View File

@ -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
} }

View File

@ -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)

View File

@ -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)

View File

@ -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 %}">

View File

@ -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>

View File

@ -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(),

View File

@ -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)

View File

@ -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)