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) @dataclass(frozen=True)
class ParentComparison: class ParentComparison:
"""Common fields of a device component"""
id: int id: int
name: str name: str
label: str label: str
description: str description: str
def __eq__(self, other): def __eq__(self, other):
# Ignore some fields when comparing; ignore interface name case and whitespaces return (
return self.name.lower().replace(" ", "") == other.name.lower().replace(" ", "") (self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
and (self.label == other.label)
and (self.description == other.description)
)
def __hash__(self): def __hash__(self):
# Ignore some fields when hashing; ignore interface name case and whitespaces
return hash(self.name.lower().replace(" ", "")) return hash(self.name.lower().replace(" ", ""))
@dataclass(frozen=True) @dataclass(frozen=True)
class ParentTypedComparison(ParentComparison): class ParentTypedComparison(ParentComparison):
"""Common fields of a device typed component"""
type: str type: str
type_display: str type_display: str
def __eq__(self, other): def __eq__(self, other):
# Ignore some fields when comparing; ignore interface name case and whitespaces # Ignore some fields when comparing; ignore interface name case and whitespaces
return ( return (
self.name.lower().replace(" ", "") == other.name.lower().replace(" ", "") (self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
) and (self.type == other.type) and (self.label == other.label)
and (self.description == other.description)
and (self.type == other.type)
)
def __hash__(self): def __hash__(self):
# Ignore some fields when hashing; ignore interface name case and whitespaces # Ignore some fields when hashing; ignore interface name case and whitespaces
@ -34,28 +43,78 @@ class ParentTypedComparison(ParentComparison):
@dataclass(frozen=True) @dataclass(frozen=True)
class UnifiedInterface: class InterfaceComparison(ParentTypedComparison):
"""A unified way to represent the interface and interface template""" """A unified way to represent the interface and interface template"""
id: int mgmt_only: bool
name: str
type: str = ""
type_display: str = ""
is_template: bool = False is_template: bool = False
def __eq__(self, other): def __eq__(self, other):
# Ignore some fields when comparing; ignore interface name case and whitespaces # Ignore some fields when comparing; ignore interface name case and whitespaces
return ( return (
self.name.lower().replace(" ", "") == other.name.lower().replace(" ", "") (self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
) and (self.type == other.type) and (self.label == other.label)
and (self.description == other.description)
def __hash__(self): and (self.type == other.type)
# Ignore some fields when hashing; ignore interface name case and whitespaces and (self.mgmt_only == other.mgmt_only)
return hash((self.name.lower().replace(" ", ""), self.type)) )
@dataclass(frozen=True) @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 = "" power_port_name: str = ""
feed_leg: str = "" feed_leg: str = ""
@ -77,3 +136,31 @@ class ComparisonPowerOutlet(ParentTypedComparison):
return hash( return hash(
(self.name.lower().replace(" ", ""), self.type, self.power_port_name) (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 typing import Iterable
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.contrib import messages from django.contrib import messages
from .comparison import UnifiedInterface, ComparisonPowerOutlet from .comparison import UnifiedInterface
def split(s): def split(s):
for x, y in re.findall(r'(\d*)(\D*)', s): for x, y in re.findall(r"(\d*)(\D*)", s):
yield '', int(x or '0') yield "", int(x or "0")
yield y, 0 yield y, 0
@ -17,18 +18,19 @@ def natural_keys(c):
def human_sorted(iterable: Iterable): def human_sorted(iterable: Iterable):
return sorted(iterable, key=natural_keys) 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: def get_components(request, device, components, unified_components, unified_component_templates):
unified_component_templates = [ # try:
UnifiedInterface(i.id, i.name, i.type, i.get_type_display(), is_template=True) for i in component_templates] # unified_components = [UnifiedInterface(i.id, i.name, i.type, i.get_type_display()) for i in components]
except AttributeError: # except AttributeError:
unified_component_templates = [ # unified_components = [UnifiedInterface(i.id, i.name) for i in components]
UnifiedInterface(i.id, i.name, is_template=True) for i in component_templates]
# 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 # List of interfaces and interface templates presented in the unified format
overall_powers = list(set(unified_component_templates + unified_components)) overall_powers = list(set(unified_component_templates + unified_components))
@ -38,35 +40,46 @@ def get_components(request, device, components, component_templates):
comparison_interfaces = [] comparison_interfaces = []
for i in overall_powers: for i in overall_powers:
try: 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: except ValueError:
comparison_templates.append(None) comparison_templates.append(None)
try: try:
comparison_interfaces.append(unified_components[unified_components.index(i)]) comparison_interfaces.append(
unified_components[unified_components.index(i)]
)
except ValueError: except ValueError:
comparison_interfaces.append(None) comparison_interfaces.append(None)
comparison_items = list(zip(comparison_templates, comparison_interfaces)) comparison_items = list(zip(comparison_templates, comparison_interfaces))
return render( return render(
request, "netbox_interface_sync/interface_comparison.html", request,
"netbox_interface_sync/interface_comparison.html",
{ {
"comparison_items": comparison_items, "comparison_items": comparison_items,
"templates_count": len(unified_component_templates), "templates_count": len(unified_component_templates),
"interfaces_count": len(components), "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 # Manually validating interfaces and interface templates lists
add_to_device = filter( add_to_device = filter(
lambda i: i in component_templates.values_list("id", flat=True), 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( remove_from_device = filter(
lambda i: i in components.values_list("id", flat=True), 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 # 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)) created = len(ObjectType.objects.bulk_create(bulk_create))
# Getting and validating a list of interfaces to rename # 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 # Casting interface templates into UnifiedInterface objects for proper comparison with interfaces for renaming
try: try:
unified_component_templates = [ 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: except AttributeError:
unified_component_templates = [ 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 # Rename selected interfaces
fixed = 0 fixed = 0
for component in fix_name_components: for component in fix_name_components:
try: 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: except AttributeError:
unified_component = UnifiedInterface(component.id, component.name) unified_component = UnifiedInterface(component.id, component.name)
try: try:
# Try to extract an interface template with the corresponding name # 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.name = corresponding_template.name
component.save() component.save()
fixed += 1 fixed += 1

View File

@ -8,7 +8,7 @@ from django.conf import settings
from django.contrib import messages from django.contrib import messages
from .utils import natural_keys, get_components, post_components 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 from .forms import InterfaceComparisonForm
config = settings.PLUGINS_CONFIG['netbox_interface_sync'] 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)) interfaces = list(filter(lambda i: not i.is_virtual, interfaces))
interface_templates = InterfaceTemplate.objects.filter(device_type=device.device_type) interface_templates = InterfaceTemplate.objects.filter(device_type=device.device_type)
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): def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST) form = InterfaceComparisonForm(request.POST)
@ -48,7 +52,11 @@ class PowerPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
powerports = device.powerports.all() powerports = device.powerports.all()
powerports_templates = PowerPortTemplate.objects.filter(device_type=device.device_type) powerports_templates = PowerPortTemplate.objects.filter(device_type=device.device_type)
return get_components(request, device, powerports, powerports_templates) 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)
def post(self, request, device_id): def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST) form = InterfaceComparisonForm(request.POST)
@ -70,7 +78,11 @@ class ConsolePortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, Vie
consoleports = device.consoleports.all() consoleports = device.consoleports.all()
consoleports_templates = ConsolePortTemplate.objects.filter(device_type=device.device_type) 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): def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST) form = InterfaceComparisonForm(request.POST)
@ -92,7 +104,11 @@ class ConsoleServerPortComparisonView(LoginRequiredMixin, PermissionRequiredMixi
consoleserverports = device.consoleserverports.all() consoleserverports = device.consoleserverports.all()
consoleserverports_templates = ConsoleServerPortTemplate.objects.filter(device_type=device.device_type) 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): def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST) form = InterfaceComparisonForm(request.POST)
@ -114,38 +130,11 @@ class PowerOutletComparisonView(LoginRequiredMixin, PermissionRequiredMixin, Vie
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]
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] return get_components(request, device, poweroutlets, unified_poweroutlets, unified_poweroutlet_templates)
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]
# 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
}
)
def post(self, request, device_id): def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST) 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 # Casting interface templates into UnifiedInterface objects for proper comparison with interfaces for renaming
unified_component_templates = [ 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 # Rename selected interfaces
fixed = 0 fixed = 0
for component in fix_name_components: 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:
# Try to extract an interface template with the corresponding name # Try to extract an interface template with the corresponding name
@ -319,7 +308,11 @@ class DeviceBayComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
devicebays = device.devicebays.all() devicebays = device.devicebays.all()
devicebays_templates = DeviceBayTemplate.objects.filter(device_type=device.device_type) 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): def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST) form = InterfaceComparisonForm(request.POST)