From 0d0e11a9251dde0b083a8459b2e3c2c3bf1fd1ba Mon Sep 17 00:00:00 2001 From: cmcknz77 Date: Fri, 11 Apr 2025 02:29:55 +0100 Subject: [PATCH 01/15] Update deviceview.html to correct malformed int.stylename value in style string Observed: `style="grid-area: 1-1-1; "` (The trailing semicolon and space here are suspicious). That trailing '; ' is almost certainly the problem, preventing the browser from correctly interpreting the grid-area property. The template always puts grid-area: `{{ int.stylename }};` (with the semicolon). The background-color part is only added if the cable_colors option is on and the cable has a color specified. If those conditions aren't met, the background-color part is omitted, leaving you with just `style="grid-area: 1-1-1; "` - exactly what was observed. Explanation of the fix: 1. We remove the semicolon that was immediately after {{ int.stylename }}. 2. We add the semicolon only if the background-color property is actually being added (i.e., inside the {% if ... %} block, right before background-color). 3. This ensures that if background-color isn't added, the style attribute will correctly be style="grid-area: 1-1-1", and if it is added, it will be style="grid-area: 1-1-1; background-color: #xxxxxx". 4. Removed the unnecessary line breaks within the style attribute definition for clarity, which shouldn't affect functionality --- .../templates/netbox_device_view/deviceview.html | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/netbox_device_view/templates/netbox_device_view/deviceview.html b/netbox_device_view/templates/netbox_device_view/deviceview.html index 4ff0329..7b5150f 100644 --- a/netbox_device_view/templates/netbox_device_view/deviceview.html +++ b/netbox_device_view/templates/netbox_device_view/deviceview.html @@ -59,12 +59,7 @@ {% endif %} {% endfor %} {% endif %} - " - style="grid-area: {{ int.stylename }}; - {% if cable_colors == "on" and int.cable.color != "" %} - background-color: #{{ int.cable.color }} - {% endif %} - " + style="grid-area: {{ int.stylename }}{% if cable_colors == 'on' and int.cable.color != '' %}; background-color: #{{ int.cable.color }}{% endif %}" data-bs-toggle="tooltip" data-bs-html="true" data-bs-custom-class="device-view-tooltip" From 27cf9fde3ad7980703971bc4c1eb30f3243ee6ad Mon Sep 17 00:00:00 2001 From: cmcknz77 Date: Sun, 13 Apr 2025 12:56:15 +0100 Subject: [PATCH 02/15] Update utils.py Stylename interface identifications not working correctly for interfaces like 1/1/1 or 1/1 as the '/'s are not digits so reworked that prefixing logic fo a 'p' to be added to the start of stylenames that start with a digit. --- netbox_device_view/utils.py | 78 ++++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 28 deletions(-) diff --git a/netbox_device_view/utils.py b/netbox_device_view/utils.py index d577dce..4391e2a 100644 --- a/netbox_device_view/utils.py +++ b/netbox_device_view/utils.py @@ -5,71 +5,92 @@ import re +# --- process_interfaces: With fix for '/' names, NO prefixing --- def process_interfaces(interfaces, ports_chassis, dev): if interfaces is not None: for itf in interfaces: + # Skip virtual/lag interfaces if itf.type == "virtual" or itf.type == "lag": continue - regex = r"^(?P([a-zA-Z\-_]*))(\/|(?P[0-9]+).|\s)?((?P[0-9]+).|\s)?((?P[0-9]+))$" - matches = re.search(regex, itf.name.lower()) - if matches: - itf.stylename = ( - (matches["type"] or "") - + (matches["module"] or "") - + "-" - + matches["port"] - ) - else: + + # --- Generate base stylename (Handles '/' correctly) --- + if "/" in itf.name: itf.stylename = re.sub(r"[^.a-zA-Z\d]", "-", itf.name.lower()) - if itf.stylename.isdigit(): - itf.stylename = f"p{itf.stylename}" + else: + # Original regex logic for other name formats + regex = r"^(?P([a-zA-Z\-_]*))(\/|(?P[0-9]+).|\s)?((?P[0-9]+).|\s)?((?P[0-9]+))$" + matches = re.search(regex, itf.name.lower()) + if matches: + itf.stylename = ( + (matches["type"] or "") + + (matches["module"] or "") + + "-" + + matches["port"] + ) + else: + # Fallback if regex fails for non-'/' names + itf.stylename = re.sub(r"[^.a-zA-Z\d]", "-", itf.name.lower()) + # --- Base stylename generated --- + + # Original purely numeric prefix logic (can be left or removed) + # This does not affect names like '1-1-1' + if (str(itf.stylename))[0].isdigit(): + stylename_str=str(itf.stylename) + if stylename_str and stylename_str[0].isdigit(): + itf.stylename = f"p{itf.stylename}" + + # Add to dictionary if dev not in ports_chassis: ports_chassis[dev] = [] ports_chassis[dev].append(itf) + return ports_chassis - def process_ports(ports, ports_chassis, dev): if ports is not None: for port in ports: if port.type == "virtual": continue port.is_port = True + # Use simple substitution for stylename port.stylename = re.sub(r"[^.a-zA-Z\d]", "-", port.name.lower()) - if port.stylename.isdigit(): + # Prefix if purely numeric + if str(port.stylename).isdigit(): port.stylename = f"p{port.stylename}" + # Add to dictionary if dev not in ports_chassis: ports_chassis[dev] = [] ports_chassis[dev].append(port) return ports_chassis +# --- prepare: With fix for consistent key '1' for standalone --- def prepare(obj): ports_chassis = {} dv = {} modules = {} - try: - if obj.virtual_chassis is None: - dv[1] = DeviceView.objects.get( - device_type=obj.device_type - ).grid_template_area + # Check if standalone device + if obj.virtual_chassis is None: + # Fetch DeviceView CSS definition + dv[1] = DeviceView.objects.get(device_type=obj.device_type).grid_template_area + # Get modules modules[1] = obj.modules.all() + # Process ports using consistent key '1' ports_chassis = process_interfaces(obj.interfaces.all(), ports_chassis, 1) - ports_chassis = process_ports(obj.frontports.all(), ports_chassis, "Front") - ports_chassis = process_ports(obj.rearports.all(), ports_chassis, "Rear") + ports_chassis = process_ports(obj.frontports.all(), ports_chassis, 1) # Use key 1 + ports_chassis = process_ports(obj.rearports.all(), ports_chassis, 1) # Use key 1 ports_chassis = process_ports( - ConsolePort.objects.filter(device_id=obj.id), - ports_chassis, - obj.name, + ConsolePort.objects.filter(device_id=obj.id), ports_chassis, 1 # Use key 1 ) + # Else handle Virtual Chassis else: - for member in obj.virtual_chassis.members.all(): + for member in obj.virtual_chassis.members.all(): dv[member.vc_position] = DeviceView.objects.get( device_type=member.device_type ).grid_template_area.replace( ".area", ".area.d" + str(member.vc_position) - ) + ) modules[member.vc_position] = member.modules.all() ports_chassis = process_interfaces( member.interfaces.all(), ports_chassis, member.vc_position @@ -77,9 +98,10 @@ def prepare(obj): ports_chassis = process_ports( ConsolePort.objects.filter(device_id=member.id), ports_chassis, - member.vc_position, + member.vc_position, ) + # Handle case where DeviceView definition doesn't exist except ObjectDoesNotExist: return None, None, None - + return dv, modules, ports_chassis From 0d39f0b17a36e42f2b2112f79ca2dc9bc80377e5 Mon Sep 17 00:00:00 2001 From: cmcknz77 Date: Thu, 1 May 2025 02:15:09 +0100 Subject: [PATCH 03/15] =?UTF-8?q?Revert=20"Update=20deviceview.html=20to?= =?UTF-8?q?=20correct=20malformed=20int.stylename=20value=20in=20st?= =?UTF-8?q?=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/netbox_device_view/deviceview.html | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/netbox_device_view/templates/netbox_device_view/deviceview.html b/netbox_device_view/templates/netbox_device_view/deviceview.html index 7b5150f..4ff0329 100644 --- a/netbox_device_view/templates/netbox_device_view/deviceview.html +++ b/netbox_device_view/templates/netbox_device_view/deviceview.html @@ -59,7 +59,12 @@ {% endif %} {% endfor %} {% endif %} - style="grid-area: {{ int.stylename }}{% if cable_colors == 'on' and int.cable.color != '' %}; background-color: #{{ int.cable.color }}{% endif %}" + " + style="grid-area: {{ int.stylename }}; + {% if cable_colors == "on" and int.cable.color != "" %} + background-color: #{{ int.cable.color }} + {% endif %} + " data-bs-toggle="tooltip" data-bs-html="true" data-bs-custom-class="device-view-tooltip" From e791852732b28703f343be9293d03770ab52a451 Mon Sep 17 00:00:00 2001 From: cmcknz77 Date: Thu, 1 May 2025 02:16:11 +0100 Subject: [PATCH 04/15] Revert "Update utils.py" --- netbox_device_view/utils.py | 78 +++++++++++++------------------------ 1 file changed, 28 insertions(+), 50 deletions(-) diff --git a/netbox_device_view/utils.py b/netbox_device_view/utils.py index 4391e2a..d577dce 100644 --- a/netbox_device_view/utils.py +++ b/netbox_device_view/utils.py @@ -5,92 +5,71 @@ import re -# --- process_interfaces: With fix for '/' names, NO prefixing --- def process_interfaces(interfaces, ports_chassis, dev): if interfaces is not None: for itf in interfaces: - # Skip virtual/lag interfaces if itf.type == "virtual" or itf.type == "lag": continue - - # --- Generate base stylename (Handles '/' correctly) --- - if "/" in itf.name: - itf.stylename = re.sub(r"[^.a-zA-Z\d]", "-", itf.name.lower()) + regex = r"^(?P([a-zA-Z\-_]*))(\/|(?P[0-9]+).|\s)?((?P[0-9]+).|\s)?((?P[0-9]+))$" + matches = re.search(regex, itf.name.lower()) + if matches: + itf.stylename = ( + (matches["type"] or "") + + (matches["module"] or "") + + "-" + + matches["port"] + ) else: - # Original regex logic for other name formats - regex = r"^(?P([a-zA-Z\-_]*))(\/|(?P[0-9]+).|\s)?((?P[0-9]+).|\s)?((?P[0-9]+))$" - matches = re.search(regex, itf.name.lower()) - if matches: - itf.stylename = ( - (matches["type"] or "") - + (matches["module"] or "") - + "-" - + matches["port"] - ) - else: - # Fallback if regex fails for non-'/' names - itf.stylename = re.sub(r"[^.a-zA-Z\d]", "-", itf.name.lower()) - # --- Base stylename generated --- - - # Original purely numeric prefix logic (can be left or removed) - # This does not affect names like '1-1-1' - if (str(itf.stylename))[0].isdigit(): - stylename_str=str(itf.stylename) - if stylename_str and stylename_str[0].isdigit(): - itf.stylename = f"p{itf.stylename}" - - # Add to dictionary + itf.stylename = re.sub(r"[^.a-zA-Z\d]", "-", itf.name.lower()) + if itf.stylename.isdigit(): + itf.stylename = f"p{itf.stylename}" if dev not in ports_chassis: ports_chassis[dev] = [] ports_chassis[dev].append(itf) - return ports_chassis + def process_ports(ports, ports_chassis, dev): if ports is not None: for port in ports: if port.type == "virtual": continue port.is_port = True - # Use simple substitution for stylename port.stylename = re.sub(r"[^.a-zA-Z\d]", "-", port.name.lower()) - # Prefix if purely numeric - if str(port.stylename).isdigit(): + if port.stylename.isdigit(): port.stylename = f"p{port.stylename}" - # Add to dictionary if dev not in ports_chassis: ports_chassis[dev] = [] ports_chassis[dev].append(port) return ports_chassis -# --- prepare: With fix for consistent key '1' for standalone --- def prepare(obj): ports_chassis = {} dv = {} modules = {} + try: - # Check if standalone device - if obj.virtual_chassis is None: - # Fetch DeviceView CSS definition - dv[1] = DeviceView.objects.get(device_type=obj.device_type).grid_template_area - # Get modules + if obj.virtual_chassis is None: + dv[1] = DeviceView.objects.get( + device_type=obj.device_type + ).grid_template_area modules[1] = obj.modules.all() - # Process ports using consistent key '1' ports_chassis = process_interfaces(obj.interfaces.all(), ports_chassis, 1) - ports_chassis = process_ports(obj.frontports.all(), ports_chassis, 1) # Use key 1 - ports_chassis = process_ports(obj.rearports.all(), ports_chassis, 1) # Use key 1 + ports_chassis = process_ports(obj.frontports.all(), ports_chassis, "Front") + ports_chassis = process_ports(obj.rearports.all(), ports_chassis, "Rear") ports_chassis = process_ports( - ConsolePort.objects.filter(device_id=obj.id), ports_chassis, 1 # Use key 1 + ConsolePort.objects.filter(device_id=obj.id), + ports_chassis, + obj.name, ) - # Else handle Virtual Chassis else: - for member in obj.virtual_chassis.members.all(): + for member in obj.virtual_chassis.members.all(): dv[member.vc_position] = DeviceView.objects.get( device_type=member.device_type ).grid_template_area.replace( ".area", ".area.d" + str(member.vc_position) - ) + ) modules[member.vc_position] = member.modules.all() ports_chassis = process_interfaces( member.interfaces.all(), ports_chassis, member.vc_position @@ -98,10 +77,9 @@ def prepare(obj): ports_chassis = process_ports( ConsolePort.objects.filter(device_id=member.id), ports_chassis, - member.vc_position, + member.vc_position, ) - # Handle case where DeviceView definition doesn't exist except ObjectDoesNotExist: return None, None, None - + return dv, modules, ports_chassis From 746314cf9c074751c53cf959790b4663a2ba9503 Mon Sep 17 00:00:00 2001 From: cmcknz77 Date: Thu, 1 May 2025 02:28:28 +0100 Subject: [PATCH 05/15] 1. Problem: Trailing Semicolon and Space in the style Attribute for Individual Ports. The HTML generated for individual port links ( tags) often includes a trailing semicolon and space within the style attribute (e.g., style="grid-area: 1-1-1; ") even when it's the only style property. This trailing ; can prevent the browser from correctly applying the grid-area property, causing the port to not be placed in its intended grid cell. This was particularly apparent when the conditional background-color style was not applied. File(s) Involved: The Django template responsible for rendering the device view, likely netbox_device_view/templates/netbox_device_view/deviceview.html. Proposed Solution: Modify the template logic to ensure the semicolon is only included in the style attribute if there are multiple style properties being added (e.g., both grid-area and background-color). The fix would involve moving the semicolon inside the conditional block that adds the background-color. --- .../templates/netbox_device_view/deviceview.html | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/netbox_device_view/templates/netbox_device_view/deviceview.html b/netbox_device_view/templates/netbox_device_view/deviceview.html index 4ff0329..ca79ae1 100644 --- a/netbox_device_view/templates/netbox_device_view/deviceview.html +++ b/netbox_device_view/templates/netbox_device_view/deviceview.html @@ -60,11 +60,7 @@ {% endfor %} {% endif %} " - style="grid-area: {{ int.stylename }}; - {% if cable_colors == "on" and int.cable.color != "" %} - background-color: #{{ int.cable.color }} - {% endif %} - " + style="grid-area: {{ int.stylename }}{% if cable_colors == 'on' and int.cable.color != '' %}; background-color: #{{ int.cable.color }}{% endif %}" data-bs-toggle="tooltip" data-bs-html="true" data-bs-custom-class="device-view-tooltip" From 62eb7d13e4088ed5ec61a25571e61f50f6bfecae Mon Sep 17 00:00:00 2001 From: cmcknz77 Date: Thu, 1 May 2025 18:08:45 +0100 Subject: [PATCH 06/15] 2. Problem: Incorrect stylename Generation Logic for Interfaces with Specific Naming Conventions (e.g., X/Y/Z). -Update utils.py Description: The Python code that generates the stylename (which is then used for the grid-area CSS property) for interfaces uses a regular expression that incorrectly parses certain naming formats, specifically names like 1/1/15. Instead of producing a stylename like 1-1-15 (which matches the likely format used in grid-template-areas), it generates 1-15. File(s) Involved: The utility function responsible for processing interfaces, netbox_device_view/utils.py (specifically the process_interfaces function). Proposed Solution: Modify the stylename generation logic within the process_interfaces function to correctly parse common interface naming conventions (like X/Y/Z, EthX/Y, etc.) and reliably produce a stylename format that matches the expected names used in the grid-template-areas CSS (e.g., converting 1/1/15 to 1-1-15). A simpler string manipulation method (like splitting and joining with hyphens) might be more robust than the current regex. Convert the entire interface name to lowercase. Replace common separators found in interface names (such as slashes /, dots ., and spaces \s) with a consistent single separator, like a hyphen (-). This can be done using a simple regular expression substitution. Clean up the resulting string to remove any potential multiple consecutive hyphens or leading/trailing hyphens that might have been introduced. --- netbox_device_view/utils.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/netbox_device_view/utils.py b/netbox_device_view/utils.py index d577dce..fd9d8bc 100644 --- a/netbox_device_view/utils.py +++ b/netbox_device_view/utils.py @@ -10,19 +10,28 @@ def process_interfaces(interfaces, ports_chassis, dev): for itf in interfaces: if itf.type == "virtual" or itf.type == "lag": continue - regex = r"^(?P([a-zA-Z\-_]*))(\/|(?P[0-9]+).|\s)?((?P[0-9]+).|\s)?((?P[0-9]+))$" - matches = re.search(regex, itf.name.lower()) - if matches: - itf.stylename = ( - (matches["type"] or "") - + (matches["module"] or "") - + "-" - + matches["port"] - ) - else: - itf.stylename = re.sub(r"[^.a-zA-Z\d]", "-", itf.name.lower()) + # --- BEGIN Modified Problem 2 Logic --- + # Convert to lowercase and replace common separators with hyphens + # This replaces the original regex matching and if/else block + stylename = re.sub(r'[/\.\s]+', '-', itf.name.lower()) + + # Clean up multiple hyphens and leading/trailing hyphens + stylename = re.sub(r'-+', '-', stylename).strip('-') + + # If the name becomes empty after cleaning, use a fallback + if not stylename: + stylename = f"iface-{itf.pk}" # Use a unique fallback + + # Assign the generated stylename back to the object property + itf.stylename = stylename + # --- END Modified Problem 2 Logic --- + + # --- Original Problem 3 Validation (kept for this comparison) --- + # This line is UNCHANGED from the original function if itf.stylename.isdigit(): itf.stylename = f"p{itf.stylename}" + # --- End Original Problem 3 Validation --- + if dev not in ports_chassis: ports_chassis[dev] = [] ports_chassis[dev].append(itf) @@ -55,7 +64,7 @@ def prepare(obj): device_type=obj.device_type ).grid_template_area modules[1] = obj.modules.all() - ports_chassis = process_interfaces(obj.interfaces.all(), ports_chassis, 1) + ports_chassis = process_interfaces(obj.interfaces.all(), ports_chassis, obj.name) ports_chassis = process_ports(obj.frontports.all(), ports_chassis, "Front") ports_chassis = process_ports(obj.rearports.all(), ports_chassis, "Rear") ports_chassis = process_ports( From b702108bdf3542f4ae8be3bbc1f08896140855d1 Mon Sep 17 00:00:00 2001 From: cmcknz77 Date: Thu, 1 May 2025 19:14:22 +0100 Subject: [PATCH 07/15] 3. Problem: Insufficient Validation of Generated stylename as a Valid CSS Identifier. - Update utils.py Description: The code checks if the generated stylename isdigit() and prepends a p if it is. However, CSS identifiers cannot start with a digit or a hyphen, not just be entirely digits. This check misses cases like 1-1-15 (starts with a digit but includes hyphens) or names starting with a hyphen, which are also invalid CSS identifiers. If an invalid stylename is generated and not caught, the corresponding grid-area style will be ignored by the browser. File(s) Involved: The utility functions generating stylenames, primarily netbox_device_view/utils.py (process_interfaces and potentially process_ports). Proposed Solution: Replace the narrow isdigit() check with a more robust validation that checks if the stylename is empty or if its first character is a digit or a hyphen. If it is, prepend a valid character (like p) to ensure it becomes a valid CSS identifier before being used in the HTML and CSS. --- netbox_device_view/utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/netbox_device_view/utils.py b/netbox_device_view/utils.py index fd9d8bc..6d943c3 100644 --- a/netbox_device_view/utils.py +++ b/netbox_device_view/utils.py @@ -26,9 +26,10 @@ def process_interfaces(interfaces, ports_chassis, dev): itf.stylename = stylename # --- END Modified Problem 2 Logic --- - # --- Original Problem 3 Validation (kept for this comparison) --- - # This line is UNCHANGED from the original function - if itf.stylename.isdigit(): + # --- BEGIN Modified Problem 3 Validation --- + # Check if the stylename exists and starts with a digit or a hyphen + # This replaces the original 'if itf.stylename.isdigit():' line + if itf.stylename and (itf.stylename[0].isdigit() or itf.stylename[0] == '-'): itf.stylename = f"p{itf.stylename}" # --- End Original Problem 3 Validation --- From 5494833b29d6db4bcf66259a787e91b2b3aa19dc Mon Sep 17 00:00:00 2001 From: cmcknz77 Date: Thu, 1 May 2025 21:21:54 +0100 Subject: [PATCH 08/15] Update setup.py Alpha version --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 6f5fb1e..ec71572 100644 --- a/setup.py +++ b/setup.py @@ -5,16 +5,16 @@ setup( name="netbox-device-view", - version="0.1.9", + version="0.1.10-alpha", description="NetBox Device View plugin", packages=find_packages(), - author="Peter Baumert", + author="Peter Baumert ft Clayton McKenzie", include_package_data=True, zip_safe=False, install_requires=[], long_description=long_description, long_description_content_type="text/markdown", - url="https://github.com/peterbaumert/netbox-device-view", + url="https://github.com/cmcknz77/netbox-device-view", keywords=["netbox", "netbox-plugin"], classifiers=[ "Framework :: Django", From 102ee6cdf62c6285ee3ac09958dacf4a3808bcf9 Mon Sep 17 00:00:00 2001 From: cmcknz77 Date: Thu, 1 May 2025 21:22:53 +0100 Subject: [PATCH 09/15] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ec71572..cc0c557 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ version="0.1.10-alpha", description="NetBox Device View plugin", packages=find_packages(), - author="Peter Baumert ft Clayton McKenzie", + author="Peter Baumert", include_package_data=True, zip_safe=False, install_requires=[], From 3bbe708134f47b703c3b6e6cf4d20b62f6551885 Mon Sep 17 00:00:00 2001 From: cmcknz77 Date: Sun, 4 May 2025 03:58:47 +0100 Subject: [PATCH 10/15] Correct formatting errors --- netbox_device_view/utils.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/netbox_device_view/utils.py b/netbox_device_view/utils.py index 6d943c3..1689801 100644 --- a/netbox_device_view/utils.py +++ b/netbox_device_view/utils.py @@ -10,28 +10,27 @@ def process_interfaces(interfaces, ports_chassis, dev): for itf in interfaces: if itf.type == "virtual" or itf.type == "lag": continue - # --- BEGIN Modified Problem 2 Logic --- # Convert to lowercase and replace common separators with hyphens # This replaces the original regex matching and if/else block - stylename = re.sub(r'[/\.\s]+', '-', itf.name.lower()) + stylename = re.sub(r"[/\.\s]+", "-", itf.name.lower()) # Clean up multiple hyphens and leading/trailing hyphens - stylename = re.sub(r'-+', '-', stylename).strip('-') + stylename = re.sub(r"-+", "-", stylename).strip("-") # If the name becomes empty after cleaning, use a fallback if not stylename: - stylename = f"iface-{itf.pk}" # Use a unique fallback + stylename = f"iface-{itf.pk}" # Use a unique fallback # Assign the generated stylename back to the object property itf.stylename = stylename - # --- END Modified Problem 2 Logic --- - # --- BEGIN Modified Problem 3 Validation --- # Check if the stylename exists and starts with a digit or a hyphen # This replaces the original 'if itf.stylename.isdigit():' line - if itf.stylename and (itf.stylename[0].isdigit() or itf.stylename[0] == '-'): + if itf.stylename and ( + itf.stylename[0].isdigit() or itf.stylename[0] == "-" + ): itf.stylename = f"p{itf.stylename}" - # --- End Original Problem 3 Validation --- + if dev not in ports_chassis: ports_chassis[dev] = [] @@ -65,7 +64,9 @@ def prepare(obj): device_type=obj.device_type ).grid_template_area modules[1] = obj.modules.all() - ports_chassis = process_interfaces(obj.interfaces.all(), ports_chassis, obj.name) + ports_chassis = process_interfaces( + obj.interfaces.all(), ports_chassis, obj.name + ) ports_chassis = process_ports(obj.frontports.all(), ports_chassis, "Front") ports_chassis = process_ports(obj.rearports.all(), ports_chassis, "Rear") ports_chassis = process_ports( From 8a2885596b87023c35c58cb2e0fabed6f97b86e4 Mon Sep 17 00:00:00 2001 From: cmcknz77 Date: Sun, 4 May 2025 04:16:42 +0100 Subject: [PATCH 11/15] Removed an unecessary comment causing black to fail. --- netbox_device_view/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/netbox_device_view/utils.py b/netbox_device_view/utils.py index 1689801..03a7ac8 100644 --- a/netbox_device_view/utils.py +++ b/netbox_device_view/utils.py @@ -31,7 +31,6 @@ def process_interfaces(interfaces, ports_chassis, dev): ): itf.stylename = f"p{itf.stylename}" - if dev not in ports_chassis: ports_chassis[dev] = [] ports_chassis[dev].append(itf) From e3ad567e6a65aea468efce7c58ece4680be199b5 Mon Sep 17 00:00:00 2001 From: cmcknz77 Date: Sun, 4 May 2025 14:12:41 +0100 Subject: [PATCH 12/15] Correct repo url in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cc0c557..dfb8d25 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ install_requires=[], long_description=long_description, long_description_content_type="text/markdown", - url="https://github.com/cmcknz77/netbox-device-view", + url="https://github.com/peterbaumert/netbox-device-view", keywords=["netbox", "netbox-plugin"], classifiers=[ "Framework :: Django", From ba2906b249775711e53e4f6cec012cb78b403a24 Mon Sep 17 00:00:00 2001 From: cmcknz77 Date: Sun, 4 May 2025 18:09:04 +0100 Subject: [PATCH 13/15] Add cleaning method for grid_template_area to standardize whitespace - Fixes https://github.com/peterbaumert/netbox-device-view/issues/43 --- netbox_device_view/forms.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/netbox_device_view/forms.py b/netbox_device_view/forms.py index 17e354d..e22db29 100644 --- a/netbox_device_view/forms.py +++ b/netbox_device_view/forms.py @@ -3,6 +3,7 @@ from .models import DeviceView from dcim.models import DeviceType from utilities.forms.fields import CSVModelChoiceField +import re class DeviceViewForm(NetBoxModelForm): @@ -10,6 +11,34 @@ class Meta: model = DeviceView fields = ("device_type", "grid_template_area") + def clean_grid_template_area(self): + """ + Cleans the grid_template_area field input, replacing common + non-standard whitespace characters with standard spaces. + """ + # Get the data from the cleaned_data dictionary + grid_string = self.cleaned_data["grid_template_area"] + + if grid_string: # Ensure the string is not empty before processing + # Replace Non-Breaking Spaces (U+00A0) with standard spaces + cleaned_string = grid_string.replace("\u00a0", " ") + + # Add replacements for other problematic unicode whitespaces. + cleaned_string = cleaned_string.replace("\u2005", " ") # Four-per-em space + + # Optional: You could add further standardization here, e.g., + # replacing any sequence of whitespace with a single space: + # cleaned_string = re.sub(r'\s+', ' ', cleaned_string).strip() + # But be cautious this doesn't alter intended structure if internal + # newlines/spacing within quotes were significant in the design. + # Simple replacement of specific characters is safer if unsure. + + else: + cleaned_string = grid_string # Keep empty string if input was empty + + # Return the cleaned value + return cleaned_string + class DeviceViewImportForm(NetBoxModelImportForm): device_type = CSVModelChoiceField( From ebe5b3425a7a8981aac2b97f785622c283b7db30 Mon Sep 17 00:00:00 2001 From: cmcknz77 Date: Sun, 4 May 2025 20:02:44 +0100 Subject: [PATCH 14/15] Revert issue-43 change --- netbox_device_view/forms.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/netbox_device_view/forms.py b/netbox_device_view/forms.py index e22db29..f981325 100644 --- a/netbox_device_view/forms.py +++ b/netbox_device_view/forms.py @@ -3,7 +3,6 @@ from .models import DeviceView from dcim.models import DeviceType from utilities.forms.fields import CSVModelChoiceField -import re class DeviceViewForm(NetBoxModelForm): @@ -11,35 +10,6 @@ class Meta: model = DeviceView fields = ("device_type", "grid_template_area") - def clean_grid_template_area(self): - """ - Cleans the grid_template_area field input, replacing common - non-standard whitespace characters with standard spaces. - """ - # Get the data from the cleaned_data dictionary - grid_string = self.cleaned_data["grid_template_area"] - - if grid_string: # Ensure the string is not empty before processing - # Replace Non-Breaking Spaces (U+00A0) with standard spaces - cleaned_string = grid_string.replace("\u00a0", " ") - - # Add replacements for other problematic unicode whitespaces. - cleaned_string = cleaned_string.replace("\u2005", " ") # Four-per-em space - - # Optional: You could add further standardization here, e.g., - # replacing any sequence of whitespace with a single space: - # cleaned_string = re.sub(r'\s+', ' ', cleaned_string).strip() - # But be cautious this doesn't alter intended structure if internal - # newlines/spacing within quotes were significant in the design. - # Simple replacement of specific characters is safer if unsure. - - else: - cleaned_string = grid_string # Keep empty string if input was empty - - # Return the cleaned value - return cleaned_string - - class DeviceViewImportForm(NetBoxModelImportForm): device_type = CSVModelChoiceField( queryset=DeviceType.objects.all(), From 99dbf02735a7aafbc171fa0bb098f2880ec5d043 Mon Sep 17 00:00:00 2001 From: cmcknz77 Date: Sun, 4 May 2025 20:05:59 +0100 Subject: [PATCH 15/15] format --- netbox_device_view/forms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netbox_device_view/forms.py b/netbox_device_view/forms.py index f981325..17e354d 100644 --- a/netbox_device_view/forms.py +++ b/netbox_device_view/forms.py @@ -10,6 +10,7 @@ class Meta: model = DeviceView fields = ("device_type", "grid_template_area") + class DeviceViewImportForm(NetBoxModelImportForm): device_type = CSVModelChoiceField( queryset=DeviceType.objects.all(),