Styling rendered table with bootstrap 4. Removed the double for loop. Added str method to comparison classes.

This commit is contained in:
rizlas 2022-01-12 12:59:05 +01:00
parent 729218f577
commit 861eff8a61
7 changed files with 198 additions and 178 deletions

View File

@ -20,6 +20,9 @@ class ParentComparison:
def __hash__(self):
return hash(self.name.lower().replace(" ", ""))
def __str__(self):
return f"Label: {self.label}\nDescription: {self.description}"
@dataclass(frozen=True)
class ParentTypedComparison(ParentComparison):
@ -41,6 +44,9 @@ class ParentTypedComparison(ParentComparison):
# Ignore some fields when hashing; ignore interface name case and whitespaces
return hash((self.name.lower().replace(" ", ""), self.type))
def __str__(self):
return f"{super().__str__()}\nType: {self.type_display}"
@dataclass(frozen=True)
class InterfaceComparison(ParentTypedComparison):
@ -63,6 +69,9 @@ class InterfaceComparison(ParentTypedComparison):
# Ignore some fields when hashing; ignore interface name case and whitespaces
return hash((self.name.lower().replace(" ", ""), self.type))
def __str__(self):
return f"{super().__str__()}\nManagement only: {self.mgmt_only}"
@dataclass(frozen=True)
class FrontPortComparison(ParentTypedComparison):
@ -88,6 +97,9 @@ class FrontPortComparison(ParentTypedComparison):
# Ignore some fields when hashing; ignore interface name case and whitespaces
return hash((self.name.lower().replace(" ", ""), self.type))
def __str__(self):
return f"{super().__str__()}\nColor: {self.color}\nPosition: {self.rear_port_position}"
@dataclass(frozen=True)
class RearPortComparison(ParentTypedComparison):
@ -112,6 +124,9 @@ class RearPortComparison(ParentTypedComparison):
# Ignore some fields when hashing; ignore interface name case and whitespaces
return hash((self.name.lower().replace(" ", ""), self.type))
def __str__(self):
return f"{super().__str__()}\nColor: {self.color}\nPositions: {self.positions}"
@dataclass(frozen=True, eq=False)
class ConsolePortComparison(ParentTypedComparison):
@ -150,6 +165,9 @@ class PowerPortComparison(ParentTypedComparison):
# Ignore some fields when hashing; ignore interface name case and whitespaces
return hash((self.name.lower().replace(" ", ""), self.type))
def __str__(self):
return f"{super().__str__()}\nMaximum draw: {self.maximum_draw}\nAllocated draw: {self.allocated_draw}"
@dataclass(frozen=True)
class PowerOutletComparison(ParentTypedComparison):
@ -176,6 +194,9 @@ class PowerOutletComparison(ParentTypedComparison):
(self.name.lower().replace(" ", ""), self.type, self.power_port_name)
)
def __str__(self):
return f"{super().__str__()}\nPower port name: {self.power_port_name}\nFeed leg: {self.feed_leg}"
@dataclass(frozen=True, eq=False)
class DeviceBayComparison(ParentComparison):

View File

@ -8,7 +8,7 @@ class DeviceViewExtension(PluginTemplateExtension):
def buttons(self):
"""Implements a compare interfaces button at the top of the page"""
obj = self.context['object']
return self.render("netbox_interface_sync/compare_interfaces_button.html", extra_context={
return self.render("netbox_interface_sync/compare_components_button.html", extra_context={
"device": obj
})

View File

@ -0,0 +1,159 @@
{% extends 'base/layout.html' %}
{% block title %}{{ device }} - {{ component_type }} comparison{% endblock %}
{% block header %}
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'dcim:device_list' %}">Devices</a></li>
<li class="breadcrumb-item"><a href="{% url 'dcim:device_list' %}?site={{ device.site.slug }}">{{ device.site }}</a></li>
<li class="breadcrumb-item"><a href="{% url 'dcim:device' pk=device.id %}">{{ device }}</a></li>
</ol>
</nav>
{{ block.super }}
{% endblock %}
{% block content %}
<style>
.caption-red {
caption-side: top;
color: red;
}
.caption-green {
caption-side: top;
color: green;
}
</style>
<script>
function toggle(event) {
event = event || window.event;
var src = event.target || event.srcElement || event;
checkboxes = document.getElementsByName(src.id);
for(var checkbox of checkboxes) checkbox.checked = src.checked;
}
function uncheck(event) {
event = event || window.event;
var src = event.target || event.srcElement || event;
if (src.checked == false) {
document.getElementById(src.name).checked = false;
}
}
</script>
<form method="post">
<!-- Interface templates -->
{% csrf_token %}
<div class="table-responsive-xl">
<table class="table table-hover table-bordered">
{% if templates_count == components_count %}
<caption class="caption-green">
The device and device type have the same number of {{ component_type }}.
</caption>
{% else %}
<caption class="caption-red">
The device and device type have different number of {{ component_type|lower }}.<br>
Device: {{ components_count }}<br>
Device type: {{ templates_count }}
</caption>
{% endif %}
<thead>
<tr>
<th scope="col" colspan="2">Device type</th>
<th scope="col">Actions</th>
<th scope="col" colspan="2">Device</th>
<th scope="col" colspan="2">Actions</th>
</tr>
<tr>
<th scope="col">Name</th>
<th scope="col">Attributes</th>
<th scope="col">
<label>
<input type="checkbox" id="add_to_device" onclick="toggle(this)">
Add to the device
</label>
</th>
<th scope="col">Name</th>
<th scope="col">Attributes</th>
<th scope="col">
<label>
<input type="checkbox" id="remove_from_device" onclick="toggle(this)">
Remove
</label>
</th>
<th scope="col">
<label>
<input type="checkbox" id="fix_name" onclick="toggle(this)">
Fix the name
</label>
</th>
</tr>
</thead>
<tbody>
{% for component_template, component in comparison_items %}
<tr>
{% if component_template %}
<th scope="row" {% if not component %}class="table-danger"{% endif %}>
{% if component and component_template.name != component.name %}
<span style="background-color: #eab2b2">{{ component_template.name }}</span>
{% else %}
{{ component_template.name }}
{% endif %}
</th>
<td style="white-space:pre" {% if not component %}class="table-danger"{% endif %}>{{ component_template }}</td>
<td {% if not component %}class="table-danger"{% endif %}>
{% if not component %}
<label>
<input type="checkbox" name="add_to_device" value="{{ component_template.id }}" onclick="uncheck(this)">
Add to device
</label>
{% endif %}
</td>
{% else %}
<th scope="row">&nbsp;</th>
<td>&nbsp;</td>
<td>&nbsp;</td>
{% endif %}
{% if component %}
<th scope="row" {% if not component_template %}class="table-success"{% endif %}>
{% if component_template and component_template.name != component.name %}
<span style="background-color: #cde8c2">{{ component.name }}</span>
{% else %}
{{ component.name }}
{% endif %}
</th>
<td style="white-space:pre" {% if not component_template %}class="table-success"{% endif %}>{{ component }}</td>
<td {% if not component_template %}class="table-success"{% endif %}>
{% if not component_template %}
<label>
<input type="checkbox" name="remove_from_device" value="{{ component.id }}" onclick="uncheck(this)">
Remove
</label>
{% endif %}
</td>
<td {% if not component_template %}class="table-success"{% endif %}>
{% if component_template and component_template.name != component.name %}
<label>
<input type="checkbox" name="fix_name" value="{{ component.id }}" onclick="uncheck(this)">
Fix name
</label>
{% endif %}
</td>
{% else %}
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div>
<input type="submit" value="Apply" class="btn btn-primary" style="float: right;">
</div>
</form>
{% endblock %}

View File

@ -1,161 +0,0 @@
{% extends 'base/layout.html' %}
{% block title %}{{ device }} - Interface comparison{% endblock %}
{% block header %}
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'dcim:device_list' %}">Devices</a></li>
<li class="breadcrumb-item"><a href="{% url 'dcim:device_list' %}?site={{ device.site.slug }}">{{ device.site }}</a></li>
<li class="breadcrumb-item"><a href="{% url 'dcim:device' pk=device.id %}">{{ device }}</a></li>
</ol>
</nav>
{{ block.super }}
{% endblock %}
{% block content %}
<style>
.checkbox-group {
position: absolute;
}
</style>
<script>
function toggle(event) {
event = event || window.event;
var src = event.target || event.srcElement || event;
checkboxes = document.getElementsByName(src.id);
for(var checkbox of checkboxes) checkbox.checked = src.checked;
}
function uncheck(event) {
event = event || window.event;
var src = event.target || event.srcElement || event;
if (src.checked == false) {
document.getElementById(src.name).checked = false;
}
}
</script>
<p>
{% if templates_count == interfaces_count %}
The device and device type have the same number of interfaces.
{% else %}
The device and device type have different number of interfaces.<br>
Device: {{ interfaces_count }}<br>
Device type: {{ templates_count }}
{% endif %}
</p>
<form method="post">
<!-- Interface templates -->
{% csrf_token %}
<table class="table" style="width: 50%; float: left;">
<tr>
<th colspan="2">Device type</th>
<th>Actions</th>
</tr>
<tr>
<th>Name</th>
<th>Type</th>
<th>
<label class="checkbox-group">
<input type="checkbox" id="add_to_device" onclick="toggle(this)">
Add to the device
</label>
</th>
</tr>
{% for template, interface in comparison_items %}
{% if template %}
<tr {% if not interface %}class="danger"{% endif %}>
<td>
{% if interface and template.name != interface.name %}
<span style="background-color: #eab2b2">{{ template.name }}</span>
{% else %}
{{ template.name }}
{% endif %}
</td>
<td>{{ template.type_display }}</td>
<td>
{% if not interface %}
<label class="checkbox-group">
<input type="checkbox" name="add_to_device" value="{{ template.id }}" onclick="uncheck(this)">
Add to device
</label>
{% endif %}
</td>
</tr>
{% else %}
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
{% endif %}
{% endfor %}
</table>
<table class="table" style="width: 50%; float: right;">
<!-- Interfaces -->
<tr>
<th colspan="2">Device</th>
<th colspan="2">Actions</th>
</tr>
<tr>
<th>Name</th>
<th>Type</th>
<th>
<label class="checkbox-group">
<input type="checkbox" id="remove_from_device" onclick="toggle(this)">
Remove
</label>
</th>
<th>
<label class="checkbox-group">
<input type="checkbox" id="fix_name" onclick="toggle(this)">
Fix the name
</label>
</th>
</tr>
{% for template, interface in comparison_items %}
{% if interface %}
<tr {% if not template %}class="success"{% endif %}>
<td>
{% if template and template.name != interface.name %}
<span style="background-color: #cde8c2">{{ interface.name }}</span>
{% else %}
{{ interface.name }}
{% endif %}
</td>
<td>{{ interface.type_display }}</td>
<td>
{% if not template %}
<label class="checkbox-group">
<input type="checkbox" name="remove_from_device" value="{{ interface.id }}" onclick="uncheck(this)">
Remove
</label>
{% endif %}
</td>
<td>
{% if template and template.name != interface.name %}
<label class="checkbox-group">
<input type="checkbox" name="fix_name" value="{{ interface.id }}" onclick="uncheck(this)">
Fix name
</label>
{% endif %}
</td>
</tr>
{% else %}
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
{% endif %}
{% endfor %}
</table>
<div class="text-right">
<input type="submit" value="Apply" class="btn btn-primary">
</div>
</form>
{% endblock %}

View File

@ -19,13 +19,13 @@ def human_sorted(iterable: Iterable):
return sorted(iterable, key=natural_keys)
def get_components(request, device, components, unified_components, unified_component_templates):
# List of interfaces and interface templates presented in the unified format
def get_components(request, device, components, unified_components, unified_component_templates, component_type):
# List of components and components 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 = []
comparison_components = []
for i in overall_powers:
try:
comparison_templates.append(
@ -35,20 +35,21 @@ def get_components(request, device, components, unified_components, unified_comp
comparison_templates.append(None)
try:
comparison_interfaces.append(
comparison_components.append(
unified_components[unified_components.index(i)]
)
except ValueError:
comparison_interfaces.append(None)
comparison_components.append(None)
comparison_items = list(zip(comparison_templates, comparison_interfaces))
comparison_items = list(zip(comparison_templates, comparison_components))
return render(
request,
"netbox_interface_sync/interface_comparison.html",
"netbox_interface_sync/components_comparison.html",
{
"component_type": component_type,
"comparison_items": comparison_items,
"templates_count": len(unified_component_templates),
"interfaces_count": len(components),
"components_count": len(components),
"device": device,
},
)

View File

@ -29,7 +29,7 @@ class InterfaceComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
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)
return get_components(request, device, interfaces, unified_interfaces, unified_interface_templates, "Interfaces")
def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST)
@ -76,7 +76,7 @@ class PowerPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
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)
return get_components(request, device, powerports, unified_powerports, unified_powerport_templates, "Power ports")
def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST)
@ -123,7 +123,7 @@ class ConsolePortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, Vie
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)
return get_components(request, device, consoleports, unified_consoleports, unified_consoleport_templates, "Console ports")
def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST)
@ -168,7 +168,7 @@ class ConsoleServerPortComparisonView(LoginRequiredMixin, PermissionRequiredMixi
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)
return get_components(request, device, consoleserverports, unified_consoleserverports, unified_consoleserverport_templates, "Console server ports")
def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST)
@ -213,7 +213,7 @@ class PowerOutletComparisonView(LoginRequiredMixin, PermissionRequiredMixin, Vie
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)
return get_components(request, device, poweroutlets, unified_poweroutlets, unified_poweroutlet_templates, "Power outlets")
def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST)
@ -347,7 +347,7 @@ class FrontPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
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)
return get_components(request, device, frontports, unified_frontports, unified_frontports_templates, "Front ports")
def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST)
@ -479,7 +479,7 @@ class RearPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
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)
return get_components(request, device, rearports, unified_rearports, unified_rearports_templates, "Rear ports")
def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST)
@ -536,7 +536,7 @@ class DeviceBayComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
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)
return get_components(request, device, devicebays, unified_devicebays, unified_devicebay_templates, "Device bays")
def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST)