mirror of
https://github.com/drygdryg/netbox-plugin-interface-sync
synced 2024-11-25 18:10:52 +03:00
Refactored comparison.py
- Switched to using attrs instead of dataclasses
This commit is contained in:
parent
eb01474779
commit
87d12f05f8
@ -1,228 +1,102 @@
|
|||||||
from dataclasses import dataclass, fields
|
import attr
|
||||||
|
from attrs import fields
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
config = settings.PLUGINS_CONFIG["netbox_interface_sync"]
|
config = settings.PLUGINS_CONFIG["netbox_interface_sync"]
|
||||||
|
COMPARE_DESCRIPTIONS: bool = config["compare_description"]
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@attr.s(frozen=True, auto_attribs=True)
|
||||||
class ParentComparison:
|
class BaseComparison:
|
||||||
"""Common fields of a device component"""
|
"""Common fields of a device component"""
|
||||||
|
|
||||||
id: int
|
# Do not compare IDs
|
||||||
name: str
|
id: int = attr.ib(eq=False, hash=False, metadata={'printable': False})
|
||||||
label: str
|
# Compare names case-insensitively and spaces-insensitively
|
||||||
description: str
|
name: str = attr.ib(eq=lambda name: name.lower().replace(" ", ""), metadata={'printable': False})
|
||||||
|
label: str = attr.ib(hash=False)
|
||||||
|
# Compare descriptions if it is set by the configuration
|
||||||
|
description: str = attr.ib(eq=COMPARE_DESCRIPTIONS, hash=False)
|
||||||
|
# Do not compare `is_template` properties
|
||||||
|
is_template: bool = attr.ib(kw_only=True, default=False, eq=False, hash=False, metadata={'printable': False})
|
||||||
|
|
||||||
_non_printable_fields = ('id', 'name', 'is_template')
|
@property
|
||||||
|
def fields_display(self) -> str:
|
||||||
def __eq__(self, other):
|
"""Generate human-readable list of printable fields to display in the comparison table"""
|
||||||
# Ignore some fields when comparing; ignore component name case and whitespaces
|
|
||||||
eq = (
|
|
||||||
self.name.lower().replace(" ", "") == other.name.lower().replace(" ", "")
|
|
||||||
) and (self.label == other.label)
|
|
||||||
|
|
||||||
if config["compare_description"]:
|
|
||||||
eq = eq and (self.description == other.description)
|
|
||||||
|
|
||||||
return eq
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
# Ignore some fields when hashing; ignore component name case and whitespaces
|
|
||||||
return hash(self.name.lower().replace(" ", ""))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def __field_name_caption__(field_name: str):
|
|
||||||
field_captions = {
|
|
||||||
'type_display': 'Type',
|
|
||||||
'rear_port_position': 'Position'
|
|
||||||
}
|
|
||||||
return field_captions.get(field_name) or field_name.replace('_', ' ').capitalize()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
fields_to_display = []
|
fields_to_display = []
|
||||||
for field in fields(self):
|
for field in fields(self.__class__):
|
||||||
if field.name in self._non_printable_fields:
|
if not field.metadata.get('printable', True):
|
||||||
continue
|
continue
|
||||||
field_value = getattr(self, field.name)
|
field_value = getattr(self, field.name)
|
||||||
if not field_value:
|
if not field_value:
|
||||||
continue
|
continue
|
||||||
field_name_display = self.__field_name_caption__(field.name)
|
field_caption = field.metadata.get('displayed_caption') or field.name.replace('_', ' ').capitalize()
|
||||||
fields_to_display.append(f'{field_name_display}: {field_value}')
|
fields_to_display.append(f'{field_caption}: {field_value}')
|
||||||
return '\n'.join(fields_to_display)
|
return '\n'.join(fields_to_display)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@attr.s(frozen=True, auto_attribs=True)
|
||||||
class ParentTypedComparison(ParentComparison):
|
class BaseTypedComparison(BaseComparison):
|
||||||
"""Common fields of a device typed component"""
|
"""Common fields of a device typed component"""
|
||||||
|
|
||||||
type: str
|
type: str = attr.ib(hash=False, metadata={'printable': False})
|
||||||
type_display: str
|
type_display: str = attr.ib(eq=False, hash=False, metadata={'displayed_caption': 'Type'})
|
||||||
|
|
||||||
_non_printable_fields = ParentComparison._non_printable_fields + ('type',)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
eq = (
|
|
||||||
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
|
|
||||||
and (self.label == other.label)
|
|
||||||
and (self.type == other.type)
|
|
||||||
)
|
|
||||||
|
|
||||||
if config["compare_description"]:
|
|
||||||
eq = eq and (self.description == other.description)
|
|
||||||
|
|
||||||
return eq
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash((self.name.lower().replace(" ", ""), self.type))
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@attr.s(frozen=True, auto_attribs=True)
|
||||||
class InterfaceComparison(ParentTypedComparison):
|
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"""
|
"""A unified way to represent the interface and interface template"""
|
||||||
|
|
||||||
mgmt_only: bool
|
mgmt_only: bool = attr.ib(hash=False)
|
||||||
is_template: bool = False
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
eq = (
|
|
||||||
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
|
|
||||||
and (self.label == other.label)
|
|
||||||
and (self.type == other.type)
|
|
||||||
and (self.mgmt_only == other.mgmt_only)
|
|
||||||
)
|
|
||||||
|
|
||||||
if config["compare_description"]:
|
|
||||||
eq = eq and (self.description == other.description)
|
|
||||||
|
|
||||||
return eq
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash((self.name.lower().replace(" ", ""), self.type))
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@attr.s(frozen=True, auto_attribs=True)
|
||||||
class FrontPortComparison(ParentTypedComparison):
|
class FrontPortComparison(BaseTypedComparison):
|
||||||
"""A unified way to represent the front port and front port template"""
|
"""A unified way to represent the front port and front port template"""
|
||||||
|
|
||||||
color: str
|
color: str = attr.ib(hash=False)
|
||||||
# rear_port_id: int
|
# rear_port_id: int
|
||||||
rear_port_position: int
|
rear_port_position: int = attr.ib(hash=False, metadata={'displayed_caption': 'Position'})
|
||||||
is_template: bool = False
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
eq = (
|
|
||||||
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
|
|
||||||
and (self.label == other.label)
|
|
||||||
and (self.type == other.type)
|
|
||||||
and (self.color == other.color)
|
|
||||||
and (self.rear_port_position == other.rear_port_position)
|
|
||||||
)
|
|
||||||
|
|
||||||
if config["compare_description"]:
|
|
||||||
eq = eq and (self.description == other.description)
|
|
||||||
|
|
||||||
return eq
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash((self.name.lower().replace(" ", ""), self.type))
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@attr.s(frozen=True, auto_attribs=True)
|
||||||
class RearPortComparison(ParentTypedComparison):
|
class RearPortComparison(BaseTypedComparison):
|
||||||
"""A unified way to represent the rear port and rear port template"""
|
"""A unified way to represent the rear port and rear port template"""
|
||||||
|
|
||||||
color: str
|
color: str = attr.ib(hash=False)
|
||||||
positions: int
|
positions: int = attr.ib(hash=False)
|
||||||
is_template: bool = False
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
eq = (
|
|
||||||
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
|
|
||||||
and (self.label == other.label)
|
|
||||||
and (self.type == other.type)
|
|
||||||
and (self.color == other.color)
|
|
||||||
and (self.positions == other.positions)
|
|
||||||
)
|
|
||||||
|
|
||||||
if config["compare_description"]:
|
|
||||||
eq = eq and (self.description == other.description)
|
|
||||||
|
|
||||||
return eq
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash((self.name.lower().replace(" ", ""), self.type))
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, eq=False)
|
@attr.s(frozen=True, auto_attribs=True)
|
||||||
class ConsolePortComparison(ParentTypedComparison):
|
class ConsolePortComparison(BaseTypedComparison):
|
||||||
"""A unified way to represent the consoleport and consoleport template"""
|
"""A unified way to represent the consoleport and consoleport template"""
|
||||||
|
pass
|
||||||
is_template: bool = False
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, eq=False)
|
@attr.s(frozen=True, auto_attribs=True)
|
||||||
class ConsoleServerPortComparison(ParentTypedComparison):
|
class ConsoleServerPortComparison(BaseTypedComparison):
|
||||||
"""A unified way to represent the consoleserverport and consoleserverport template"""
|
"""A unified way to represent the consoleserverport and consoleserverport template"""
|
||||||
|
pass
|
||||||
is_template: bool = False
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@attr.s(frozen=True, auto_attribs=True)
|
||||||
class PowerPortComparison(ParentTypedComparison):
|
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
|
maximum_draw: str = attr.ib(hash=False)
|
||||||
allocated_draw: str
|
allocated_draw: str = attr.ib(hash=False)
|
||||||
is_template: bool = False
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
eq = (
|
|
||||||
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
|
|
||||||
and (self.label == other.label)
|
|
||||||
and (self.type == other.type)
|
|
||||||
and (self.maximum_draw == other.maximum_draw)
|
|
||||||
and (self.allocated_draw == other.allocated_draw)
|
|
||||||
)
|
|
||||||
|
|
||||||
if config["compare_description"]:
|
|
||||||
eq = eq and (self.description == other.description)
|
|
||||||
|
|
||||||
return eq
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash((self.name.lower().replace(" ", ""), self.type))
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@attr.s(frozen=True, auto_attribs=True)
|
||||||
class PowerOutletComparison(ParentTypedComparison):
|
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_name: str = ""
|
power_port_name: str = attr.ib(hash=False)
|
||||||
feed_leg: str = ""
|
feed_leg: str = attr.ib(hash=False)
|
||||||
is_template: bool = False
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
eq = (
|
|
||||||
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
|
|
||||||
and (self.label == other.label)
|
|
||||||
and (self.type == other.type)
|
|
||||||
and (self.power_port_name == other.power_port_name)
|
|
||||||
and (self.feed_leg == other.feed_leg)
|
|
||||||
)
|
|
||||||
|
|
||||||
if config["compare_description"]:
|
|
||||||
eq = eq and (self.description == other.description)
|
|
||||||
|
|
||||||
return eq
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(
|
|
||||||
(self.name.lower().replace(" ", ""), self.type, self.power_port_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, eq=False)
|
|
||||||
class DeviceBayComparison(ParentComparison):
|
|
||||||
"""A unified way to represent the device bay and device bay template"""
|
|
||||||
|
|
||||||
is_template: bool = False
|
|
||||||
|
@ -26,16 +26,16 @@
|
|||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
function toggle(event) {
|
function toggle(event) {
|
||||||
event = event || window.event;
|
event = event || window.event;
|
||||||
var src = event.target || event.srcElement || event;
|
const src = event.target || event.srcElement || event;
|
||||||
checkboxes = document.getElementsByName(src.id);
|
const checkboxes = document.getElementsByName(src.id);
|
||||||
for(var checkbox of checkboxes) checkbox.checked = src.checked;
|
for(const checkbox of checkboxes) checkbox.checked = src.checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
function uncheck(event) {
|
function uncheck(event) {
|
||||||
event = event || window.event;
|
event = event || window.event;
|
||||||
var src = event.target || event.srcElement || event;
|
const src = event.target || event.srcElement || event;
|
||||||
if (src.checked == false) {
|
if (src.checked === false) {
|
||||||
document.getElementById(src.name).checked = false;
|
document.getElementById(src.name).checked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,7 +99,7 @@ function uncheck(event) {
|
|||||||
{{ component_template.name }}
|
{{ component_template.name }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</th>
|
</th>
|
||||||
<td style="white-space:pre" {% if not component %}class="table-danger"{% endif %}>{{ component_template }}</td>
|
<td style="white-space:pre" {% if not component %}class="table-danger"{% endif %}>{{ component_template.fields_display }}</td>
|
||||||
<td {% if not component %}class="table-danger"{% endif %}>
|
<td {% if not component %}class="table-danger"{% endif %}>
|
||||||
{% if not component %}
|
{% if not component %}
|
||||||
<label>
|
<label>
|
||||||
@ -122,7 +122,7 @@ function uncheck(event) {
|
|||||||
{{ component.name }}
|
{{ component.name }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</th>
|
</th>
|
||||||
<td style="white-space:pre" {% if not component_template %}class="table-success"{% endif %}>{{ component }}</td>
|
<td style="white-space:pre" {% if not component_template %}class="table-success"{% endif %}>{{ component.fields_display }}</td>
|
||||||
<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>
|
||||||
|
2
setup.py
2
setup.py
@ -12,7 +12,7 @@ setup(
|
|||||||
author='Victor Golovanenko',
|
author='Victor Golovanenko',
|
||||||
author_email='drygdryg2014@yandex.com',
|
author_email='drygdryg2014@yandex.com',
|
||||||
license='GPL-3.0',
|
license='GPL-3.0',
|
||||||
install_requires=[],
|
install_requires=['attrs>=21.1.0'],
|
||||||
packages=["netbox_interface_sync"],
|
packages=["netbox_interface_sync"],
|
||||||
package_data={"netbox_interface_sync": ["templates/netbox_interface_sync/*.html"]},
|
package_data={"netbox_interface_sync": ["templates/netbox_interface_sync/*.html"]},
|
||||||
zip_safe=False
|
zip_safe=False
|
||||||
|
Loading…
Reference in New Issue
Block a user