Fix comparison for every get. Changed signature in centralized get

This commit is contained in:
rizlas 2021-12-28 18:32:33 +01:00
parent b6bdbf9028
commit 80112869e0
3 changed files with 189 additions and 84 deletions

View File

@ -3,30 +3,39 @@ from dataclasses import dataclass
@dataclass(frozen=True)
class ParentComparison:
"""Common fields of a device component"""
id: int
name: str
label: str
description: str
def __eq__(self, other):
# Ignore some fields when comparing; ignore interface name case and whitespaces
return self.name.lower().replace(" ", "") == other.name.lower().replace(" ", "")
return (
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
and (self.label == other.label)
and (self.description == other.description)
)
def __hash__(self):
# Ignore some fields when hashing; ignore interface name case and whitespaces
return hash(self.name.lower().replace(" ", ""))
@dataclass(frozen=True)
class ParentTypedComparison(ParentComparison):
"""Common fields of a device typed component"""
type: str
type_display: str
def __eq__(self, other):
# Ignore some fields when comparing; ignore interface name case and whitespaces
return (
self.name.lower().replace(" ", "") == other.name.lower().replace(" ", "")
) and (self.type == other.type)
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
and (self.label == other.label)
and (self.description == other.description)
and (self.type == other.type)
)
def __hash__(self):
# Ignore some fields when hashing; ignore interface name case and whitespaces
@ -34,28 +43,78 @@ class ParentTypedComparison(ParentComparison):
@dataclass(frozen=True)
class UnifiedInterface:
class InterfaceComparison(ParentTypedComparison):
"""A unified way to represent the interface and interface template"""
id: int
name: str
type: str = ""
type_display: str = ""
mgmt_only: bool
is_template: bool = False
def __eq__(self, other):
# Ignore some fields when comparing; ignore interface name case and whitespaces
return (
self.name.lower().replace(" ", "") == other.name.lower().replace(" ", "")
) and (self.type == other.type)
def __hash__(self):
# Ignore some fields when hashing; ignore interface name case and whitespaces
return hash((self.name.lower().replace(" ", ""), self.type))
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
and (self.label == other.label)
and (self.description == other.description)
and (self.type == other.type)
and (self.mgmt_only == other.mgmt_only)
)
@dataclass(frozen=True)
class ComparisonPowerOutlet(ParentTypedComparison):
class FrontPortComparison(ParentTypedComparison):
"""A unified way to represent the front port and front port template"""
color: str
rearports: str
is_template: bool = False
@dataclass(frozen=True)
class RearPortComparison(ParentTypedComparison):
"""A unified way to represent the rear port and rear port template"""
color: str
positions: str
is_template: bool = False
@dataclass(frozen=True, eq=False)
class ConsolePortComparison(ParentTypedComparison):
"""A unified way to represent the consoleport and consoleport template"""
is_template: bool = False
@dataclass(frozen=True, eq=False)
class ConsoleServerPortComparison(ParentTypedComparison):
"""A unified way to represent the consoleserverport and consoleserverport template"""
is_template: bool = False
@dataclass(frozen=True)
class PowerPortComparison(ParentTypedComparison):
"""A unified way to represent the power port and power port template"""
maximum_draw: str
allocated_draw: str
is_template: bool = False
def __eq__(self, other):
# Ignore some fields when comparing; ignore interface name case and whitespaces
return (
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
and (self.label == other.label)
and (self.description == other.description)
and (self.type == other.type)
and (self.maximum_draw == other.maximum_draw)
and (self.allocated_draw == other.allocated_draw)
)
@dataclass(frozen=True)
class PowerOutletComparison(ParentTypedComparison):
"""A unified way to represent the power outlet and power outlet template"""
power_port_name: str = ""
feed_leg: str = ""
@ -77,3 +136,31 @@ class ComparisonPowerOutlet(ParentTypedComparison):
return hash(
(self.name.lower().replace(" ", ""), self.type, self.power_port_name)
)
@dataclass(frozen=True, eq=False)
class DeviceBayComparison(ParentComparison):
"""A unified way to represent the interface and interface template"""
is_template: bool = False
@dataclass(frozen=True)
class UnifiedInterface:
"""A unified way to represent the interface and interface template"""
id: int
name: str
type: str = ""
type_display: str = ""
is_template: bool = False
def __eq__(self, other):
# Ignore some fields when comparing; ignore interface name case and whitespaces
return (
self.name.lower().replace(" ", "") == other.name.lower().replace(" ", "")
) and (self.type == other.type)
def __hash__(self):
# Ignore some fields when hashing; ignore interface name case and whitespaces
return hash((self.name.lower().replace(" ", ""), self.type))

View File

@ -2,11 +2,12 @@ import re
from typing import Iterable
from django.shortcuts import render, redirect
from django.contrib import messages
from .comparison import UnifiedInterface, ComparisonPowerOutlet
from .comparison import UnifiedInterface
def split(s):
for x, y in re.findall(r'(\d*)(\D*)', s):
yield '', int(x or '0')
for x, y in re.findall(r"(\d*)(\D*)", s):
yield "", int(x or "0")
yield y, 0
@ -17,18 +18,19 @@ def natural_keys(c):
def human_sorted(iterable: Iterable):
return sorted(iterable, key=natural_keys)
def get_components(request, device, components, component_templates):
try:
unified_components = [UnifiedInterface(i.id, i.name, i.type, i.get_type_display()) for i in components]
except AttributeError:
unified_components = [UnifiedInterface(i.id, i.name) for i in components]
try:
unified_component_templates = [
UnifiedInterface(i.id, i.name, i.type, i.get_type_display(), is_template=True) for i in component_templates]
except AttributeError:
unified_component_templates = [
UnifiedInterface(i.id, i.name, is_template=True) for i in component_templates]
def get_components(request, device, components, unified_components, unified_component_templates):
# try:
# unified_components = [UnifiedInterface(i.id, i.name, i.type, i.get_type_display()) for i in components]
# except AttributeError:
# unified_components = [UnifiedInterface(i.id, i.name) for i in components]
# try:
# unified_component_templates = [
# UnifiedInterface(i.id, i.name, i.type, i.get_type_display(), is_template=True) for i in component_templates]
# except AttributeError:
# unified_component_templates = [
# UnifiedInterface(i.id, i.name, is_template=True) for i in component_templates]
# List of interfaces and interface templates presented in the unified format
overall_powers = list(set(unified_component_templates + unified_components))
@ -38,35 +40,46 @@ def get_components(request, device, components, component_templates):
comparison_interfaces = []
for i in overall_powers:
try:
comparison_templates.append(unified_component_templates[unified_component_templates.index(i)])
comparison_templates.append(
unified_component_templates[unified_component_templates.index(i)]
)
except ValueError:
comparison_templates.append(None)
try:
comparison_interfaces.append(unified_components[unified_components.index(i)])
comparison_interfaces.append(
unified_components[unified_components.index(i)]
)
except ValueError:
comparison_interfaces.append(None)
comparison_items = list(zip(comparison_templates, comparison_interfaces))
return render(
request, "netbox_interface_sync/interface_comparison.html",
request,
"netbox_interface_sync/interface_comparison.html",
{
"comparison_items": comparison_items,
"templates_count": len(unified_component_templates),
"interfaces_count": len(components),
"device": device
}
"device": device,
},
)
def post_components(request, device, components, component_templates, ObjectType, ObjectTemplateType):
def post_components(
request, device, components, component_templates, ObjectType, ObjectTemplateType
):
# Manually validating interfaces and interface 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")))
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")))
map(
int,
filter(lambda x: x.isdigit(), request.POST.getlist("remove_from_device")),
),
)
# Remove selected interfaces from the device and count them
@ -89,26 +102,38 @@ def post_components(request, device, components, component_templates, ObjectType
created = len(ObjectType.objects.bulk_create(bulk_create))
# Getting and validating a list of interfaces to rename
fix_name_components = filter(lambda i: str(i.id) in request.POST.getlist("fix_name"), components)
fix_name_components = filter(
lambda i: str(i.id) in request.POST.getlist("fix_name"), components
)
# Casting interface templates into UnifiedInterface objects for proper comparison with interfaces for renaming
try:
unified_component_templates = [
UnifiedInterface(i.id, i.name, i.type, i.get_type_display()) for i in component_templates]
UnifiedInterface(i.id, i.name, i.type, i.get_type_display())
for i in component_templates
]
except AttributeError:
unified_component_templates = [
UnifiedInterface(i.id, i.name) for i in component_templates]
UnifiedInterface(i.id, i.name) for i in component_templates
]
# Rename selected interfaces
fixed = 0
for component in fix_name_components:
try:
unified_component = UnifiedInterface(component.id, component.name, component.type, component.get_type_display())
unified_component = UnifiedInterface(
component.id,
component.name,
component.type,
component.get_type_display(),
)
except AttributeError:
unified_component = UnifiedInterface(component.id, component.name)
try:
# Try to extract an interface template with the corresponding name
corresponding_template = unified_component_templates[unified_component_templates.index(unified_component)]
corresponding_template = unified_component_templates[
unified_component_templates.index(unified_component)
]
component.name = corresponding_template.name
component.save()
fixed += 1

View File

@ -8,7 +8,7 @@ from django.conf import settings
from django.contrib import messages
from .utils import natural_keys, get_components, post_components
from .comparison import ComparisonPowerOutlet, UnifiedInterface
from .comparison import PowerPortComparison, PowerOutletComparison, InterfaceComparison, ConsolePortComparison, ConsoleServerPortComparison, DeviceBayComparison
from .forms import InterfaceComparisonForm
config = settings.PLUGINS_CONFIG['netbox_interface_sync']
@ -25,7 +25,11 @@ class InterfaceComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
interfaces = list(filter(lambda i: not i.is_virtual, interfaces))
interface_templates = InterfaceTemplate.objects.filter(device_type=device.device_type)
return get_components(request, device, interfaces, interface_templates)
unified_interfaces = [InterfaceComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.mgmt_only) for i in interfaces]
unified_interface_templates = [
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]
return get_components(request, device, interfaces, unified_interfaces, unified_interface_templates)
def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST)
@ -47,8 +51,12 @@ class PowerPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
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, powerports_templates)
return get_components(request, device, powerports, unified_powerports, unified_powerport_templates)
def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST)
@ -70,7 +78,11 @@ class ConsolePortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, Vie
consoleports = device.consoleports.all()
consoleports_templates = ConsolePortTemplate.objects.filter(device_type=device.device_type)
return get_components(request, device, consoleports, consoleports_templates)
unified_consoleports = [ConsolePortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display()) for i in consoleports]
unified_consoleport_templates = [
ConsolePortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), is_template=True) for i in consoleports_templates]
return get_components(request, device, consoleports, unified_consoleports, unified_consoleport_templates)
def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST)
@ -92,7 +104,11 @@ class ConsoleServerPortComparisonView(LoginRequiredMixin, PermissionRequiredMixi
consoleserverports = device.consoleserverports.all()
consoleserverports_templates = ConsoleServerPortTemplate.objects.filter(device_type=device.device_type)
return get_components(request, device, consoleserverports, consoleserverports_templates)
unified_consoleserverports = [ConsoleServerPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display()) for i in consoleserverports]
unified_consoleserverport_templates = [
ConsoleServerPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), is_template=True) for i in consoleserverports_templates]
return get_components(request, device, consoleserverports, unified_consoleserverports, unified_consoleserverport_templates)
def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST)
@ -114,38 +130,11 @@ class PowerOutletComparisonView(LoginRequiredMixin, PermissionRequiredMixin, Vie
poweroutlets = device.poweroutlets.all()
poweroutlets_templates = PowerOutletTemplate.objects.filter(device_type=device.device_type)
unified_components = [ComparisonPowerOutlet(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_component_templates = [
ComparisonPowerOutlet(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]
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]
# List of interfaces and interface templates presented in the unified format
overall_powers = list(set(unified_component_templates + unified_components))
overall_powers.sort(key=lambda o: natural_keys(o.name))
comparison_templates = []
comparison_interfaces = []
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_interfaces.append(unified_components[unified_components.index(i)])
except ValueError:
comparison_interfaces.append(None)
comparison_items = list(zip(comparison_templates, comparison_interfaces))
return render(
request, "netbox_interface_sync/interface_comparison.html",
{
"comparison_items": comparison_items,
"templates_count": len(unified_component_templates),
"interfaces_count": len(poweroutlets),
"device": device
}
)
return get_components(request, device, poweroutlets, unified_poweroutlets, unified_poweroutlet_templates)
def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST)
@ -234,12 +223,12 @@ class PowerOutletComparisonView(LoginRequiredMixin, PermissionRequiredMixin, Vie
# Casting interface templates into UnifiedInterface objects for proper comparison with interfaces for renaming
unified_component_templates = [
ComparisonPowerOutlet(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]
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 interfaces
fixed = 0
for component in fix_name_components:
unified_component = [ComparisonPowerOutlet(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_component = [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]
try:
# Try to extract an interface template with the corresponding name
@ -318,8 +307,12 @@ class DeviceBayComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
devicebays = device.devicebays.all()
devicebays_templates = DeviceBayTemplate.objects.filter(device_type=device.device_type)
return get_components(request, device, devicebays, devicebays_templates)
unified_devicebays = [DeviceBayComparison(i.id, i.name, i.label, i.description) for i in devicebays]
unified_devicebay_templates = [
DeviceBayComparison(i.id, i.name, i.label, i.description, is_template=True) for i in devicebays_templates]
return get_components(request, device, devicebays, unified_devicebays, unified_devicebay_templates)
def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST)