Topology view with overlapping regexps #1231

Closed
opened 2025-12-29 16:30:25 +01:00 by adam · 3 comments
Owner

Originally created by @candlerb on GitHub (Sep 15, 2017).

Issue type

[ ] Feature request
[X] Bug report
[ ] Documentation

Environment

  • Python version: 3.5.2
  • NetBox version: v2.2-beta1

Description

When I enter a topology description like this:

sw
.

I was expecting to see all devices with "sw" in their name on the top line, and all other devices on the line below.

What I actually see is all devices on the same line.

It's not entirely clear what the intended behaviour is here. What I was hoping is that if a device matches an earlier regex, it should be excluded from matching a later one.

If I change this to:

.
sw

then I also get all the devices on the same line - but in this case, the 'sw' device is on the right-hand end of the line.

If I make a regex which explicitly matches the other hosts by name (i.e. all those without 'sw' in them), it works - but it can be long and hard to maintain.

Originally created by @candlerb on GitHub (Sep 15, 2017). ### Issue type [ ] Feature request <!-- Requesting the implementation of a new feature --> [X] Bug report <!-- Reporting unexpected or erroneous behavior --> [ ] Documentation <!-- Proposing a modification to the documentation --> ### Environment * Python version: 3.5.2 * NetBox version: v2.2-beta1 ### Description When I enter a topology description like this: ~~~ sw . ~~~ I was expecting to see all devices with "sw" in their name on the top line, and all other devices on the line below. What I actually see is all devices on the same line. It's not entirely clear what the intended behaviour is here. What I was hoping is that if a device matches an earlier regex, it should be excluded from matching a later one. If I change this to: ~~~ . sw ~~~ then I also get all the devices on the same line - but in this case, the 'sw' device is on the right-hand end of the line. If I make a regex which explicitly matches the other hosts by name (i.e. all those without 'sw' in them), it works - but it can be long and hard to maintain.
adam added the type: bug label 2025-12-29 16:30:25 +01:00
adam closed this issue 2025-12-29 16:30:26 +01:00
Author
Owner

@jeremystretch commented on GitHub (Sep 15, 2017):

I was expecting to see all devices with "sw" in their name on the top line, and all other devices on the line below.

Each line is evaluated as an independent regex (or set of regexes separated by semicolons). You should be getting two rows: the first with all devices with "sw" in their name, and the second with all devices.

If I make a regex which explicitly matches the other hosts by name (i.e. all those without 'sw' in them), it works - but it can be long and hard to maintain.

Try:

sw
^((?!sw).)*$
@jeremystretch commented on GitHub (Sep 15, 2017): > I was expecting to see all devices with "sw" in their name on the top line, and all other devices on the line below. Each line is evaluated as an independent regex (or set of regexes separated by semicolons). You should be getting two rows: the first with all devices with "sw" in their name, and the second with _all_ devices. > If I make a regex which explicitly matches the other hosts by name (i.e. all those without 'sw' in them), it works - but it can be long and hard to maintain. Try: ``` sw ^((?!sw).)*$ ```
Author
Owner

@candlerb commented on GitHub (Sep 15, 2017):

Each line is evaluated as an independent regex (or set of regexes separated by semicolons). You should be getting two rows: the first with all devices with "sw" in their name, and the second with all devices.

That doesn't happen. And

.
.

just puts one instance of each device, all on the same line.

OK, I found the issue. If you repeat a node (with the same name) in graphviz, then it's not added as another node - it's just a repeat instance of the same node.

This fixes it for me:

--- a/netbox/extras/models.py
+++ b/netbox/extras/models.py
@@ -273,6 +273,7 @@ class TopologyMap(models.Model):
         # Construct the graph
         graph = graphviz.Graph()
         graph.graph_attr['ranksep'] = '1'
+        seen = set()
         for i, device_set in enumerate(self.device_sets):

             subgraph = graphviz.Graph(name='sg{}'.format(i))
@@ -287,7 +288,10 @@ class TopologyMap(models.Model):
             devices = []
             for query in device_set.strip(';').split(';'):  # Split regexes on semicolons
                 devices += Device.objects.filter(name__regex=query).select_related('device_role')
+            # Exclude nodes which have already been added
+            devices = [d for d in devices if d.id not in seen]
             for d in devices:
+                seen.add(d.id)
                 bg_color = '#{}'.format(d.device_role.color)
                 fg_color = '#{}'.format(foreground_color(d.device_role.color))
                 subgraph.node(d.name, style='filled', fillcolor=bg_color, fontcolor=fg_color, fontname='sans')
@candlerb commented on GitHub (Sep 15, 2017): > Each line is evaluated as an independent regex (or set of regexes separated by semicolons). You should be getting two rows: the first with all devices with "sw" in their name, and the second with all devices. That doesn't happen. And ~~~ . . ~~~ just puts one instance of each device, all on the same line. OK, I found the issue. If you repeat a node (with the same name) in graphviz, then it's not added as another node - it's just a repeat instance of the same node. This fixes it for me: ~~~ --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -273,6 +273,7 @@ class TopologyMap(models.Model): # Construct the graph graph = graphviz.Graph() graph.graph_attr['ranksep'] = '1' + seen = set() for i, device_set in enumerate(self.device_sets): subgraph = graphviz.Graph(name='sg{}'.format(i)) @@ -287,7 +288,10 @@ class TopologyMap(models.Model): devices = [] for query in device_set.strip(';').split(';'): # Split regexes on semicolons devices += Device.objects.filter(name__regex=query).select_related('device_role') + # Exclude nodes which have already been added + devices = [d for d in devices if d.id not in seen] for d in devices: + seen.add(d.id) bg_color = '#{}'.format(d.device_role.color) fg_color = '#{}'.format(foreground_color(d.device_role.color)) subgraph.node(d.name, style='filled', fillcolor=bg_color, fontcolor=fg_color, fontname='sans') ~~~
Author
Owner

@candlerb commented on GitHub (Sep 15, 2017):

Or if you prefer stylistically:

--- a/netbox/extras/models.py
+++ b/netbox/extras/models.py
@@ -273,6 +273,7 @@ class TopologyMap(models.Model):
         # Construct the graph
         graph = graphviz.Graph()
         graph.graph_attr['ranksep'] = '1'
+        seen = set()
         for i, device_set in enumerate(self.device_sets):

             subgraph = graphviz.Graph(name='sg{}'.format(i))
@@ -287,6 +288,9 @@ class TopologyMap(models.Model):
             devices = []
             for query in device_set.strip(';').split(';'):  # Split regexes on semicolons
                 devices += Device.objects.filter(name__regex=query).select_related('device_role')
+            # Remove duplicate devices
+            devices = [d for d in devices if d.id not in seen]
+            seen.update([d.id for d in devices])
             for d in devices:
                 bg_color = '#{}'.format(d.device_role.color)
                 fg_color = '#{}'.format(foreground_color(d.device_role.color))
@candlerb commented on GitHub (Sep 15, 2017): Or if you prefer stylistically: ~~~ --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -273,6 +273,7 @@ class TopologyMap(models.Model): # Construct the graph graph = graphviz.Graph() graph.graph_attr['ranksep'] = '1' + seen = set() for i, device_set in enumerate(self.device_sets): subgraph = graphviz.Graph(name='sg{}'.format(i)) @@ -287,6 +288,9 @@ class TopologyMap(models.Model): devices = [] for query in device_set.strip(';').split(';'): # Split regexes on semicolons devices += Device.objects.filter(name__regex=query).select_related('device_role') + # Remove duplicate devices + devices = [d for d in devices if d.id not in seen] + seen.update([d.id for d in devices]) for d in devices: bg_color = '#{}'.format(d.device_role.color) fg_color = '#{}'.format(foreground_color(d.device_role.color)) ~~~
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/netbox#1231