diff --git a/base_requirements.txt b/base_requirements.txt
index f0f6cfe38..ca3f4ba6f 100644
--- a/base_requirements.txt
+++ b/base_requirements.txt
@@ -54,10 +54,6 @@ djangorestframework
# https://github.com/axnsan12/drf-yasg
drf-yasg[validation]
-# Python interface to the graphviz graph rendering utility
-# https://github.com/xflr6/graphviz
-graphviz
-
# Simple markup language for rendering HTML
# https://github.com/Python-Markdown/markdown
# py-gfm requires Markdown<3.0
diff --git a/docs/additional-features/topology-maps.md b/docs/additional-features/topology-maps.md
deleted file mode 100644
index 21bbe404d..000000000
--- a/docs/additional-features/topology-maps.md
+++ /dev/null
@@ -1,17 +0,0 @@
-# Topology Maps
-
-NetBox can generate simple topology maps from the physical network connections recorded in its database. First, you'll need to create a topology map definition under the admin UI at Extras > Topology Maps.
-
-Each topology map is associated with a site. A site can have multiple topology maps, which might each illustrate a different aspect of its infrastructure (for example, production versus backend infrastructure).
-
-To define the scope of a topology map, decide which devices you want to include. The map will only include interface connections with both points terminated on an included device. Specify the devices to include in the **device patterns** field by entering a list of [regular expressions](https://en.wikipedia.org/wiki/Regular_expression) matching device names. For example, if you wanted to include "mgmt-switch1" through "mgmt-switch99", you might use the regex `mgmt-switch\d+`.
-
-Each line of the **device patterns** field represents a hierarchical layer within the topology map. For example, you might map a traditional network with core, distribution, and access tiers like this:
-
-```
-core-switch-[abcd]
-dist-switch\d
-access-switch\d+;oob-switch\d+
-```
-
-Note that you can combine multiple regexes onto one line using semicolons. The order in which regexes are listed on a line is significant: devices matching the first regex will be rendered first, and subsequent groups will be rendered to the right of those.
diff --git a/docs/installation/2-netbox.md b/docs/installation/2-netbox.md
index 30894670e..c825c3590 100644
--- a/docs/installation/2-netbox.md
+++ b/docs/installation/2-netbox.md
@@ -5,14 +5,14 @@ This section of the documentation discusses installing and configuring the NetBo
**Ubuntu**
```no-highlight
-# apt-get install -y python3 python3-pip python3-dev build-essential libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev libssl-dev redis-server zlib1g-dev
+# apt-get install -y python3 python3-pip python3-dev build-essential libxml2-dev libxslt1-dev libffi-dev libpq-dev libssl-dev redis-server zlib1g-dev
```
**CentOS**
```no-highlight
# yum install -y epel-release
-# yum install -y gcc python36 python36-devel python36-setuptools libxml2-devel libxslt-devel libffi-devel graphviz openssl-devel redhat-rpm-config redis
+# yum install -y gcc python36 python36-devel python36-setuptools libxml2-devel libxslt-devel libffi-devel openssl-devel redhat-rpm-config redis
# easy_install-3.6 pip
# ln -s /usr/bin/python36 /usr/bin/python3
```
diff --git a/mkdocs.yml b/mkdocs.yml
index 99f77d06c..9cbf38484 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -32,7 +32,6 @@ pages:
- Context Data: 'additional-features/context-data.md'
- Export Templates: 'additional-features/export-templates.md'
- Graphs: 'additional-features/graphs.md'
- - Topology Maps: 'additional-features/topology-maps.md'
- Reports: 'additional-features/reports.md'
- Webhooks: 'additional-features/webhooks.md'
- Change Logging: 'additional-features/change-logging.md'
diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py
index 5ddaf15ed..ef7666153 100644
--- a/netbox/dcim/views.py
+++ b/netbox/dcim/views.py
@@ -16,7 +16,7 @@ from django.utils.safestring import mark_safe
from django.views.generic import View
from circuits.models import Circuit
-from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
+from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
from extras.views import ObjectConfigContextView
from ipam.models import Prefix, VLAN
from ipam.tables import InterfaceIPAddressTable, InterfaceVLANTable
@@ -207,14 +207,12 @@ class SiteView(PermissionRequiredMixin, View):
'vm_count': VirtualMachine.objects.filter(cluster__site=site).count(),
}
rack_groups = RackGroup.objects.filter(site=site).annotate(rack_count=Count('racks'))
- topology_maps = TopologyMap.objects.filter(site=site)
show_graphs = Graph.objects.filter(type=GRAPH_TYPE_SITE).exists()
return render(request, 'dcim/site.html', {
'site': site,
'stats': stats,
'rack_groups': rack_groups,
- 'topology_maps': topology_maps,
'show_graphs': show_graphs,
})
diff --git a/netbox/extras/admin.py b/netbox/extras/admin.py
index d93b04037..ca469d711 100644
--- a/netbox/extras/admin.py
+++ b/netbox/extras/admin.py
@@ -3,7 +3,7 @@ from django.contrib import admin
from netbox.admin import admin_site
from utilities.forms import LaxURLField
-from .models import CustomField, CustomFieldChoice, CustomLink, Graph, ExportTemplate, TopologyMap, Webhook
+from .models import CustomField, CustomFieldChoice, CustomLink, Graph, ExportTemplate, Webhook
def order_content_types(field):
@@ -137,15 +137,3 @@ class ExportTemplateForm(forms.ModelForm):
class ExportTemplateAdmin(admin.ModelAdmin):
list_display = ['name', 'content_type', 'description', 'mime_type', 'file_extension']
form = ExportTemplateForm
-
-
-#
-# Topology maps
-#
-
-@admin.register(TopologyMap, site=admin_site)
-class TopologyMapAdmin(admin.ModelAdmin):
- list_display = ['name', 'slug', 'site']
- prepopulated_fields = {
- 'slug': ['name'],
- }
diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py
index abf0d8cf5..7ab825ea5 100644
--- a/netbox/extras/api/serializers.py
+++ b/netbox/extras/api/serializers.py
@@ -10,8 +10,7 @@ from dcim.api.nested_serializers import (
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site
from extras.constants import *
from extras.models import (
- ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap,
- Tag
+ ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, Tag,
)
from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantGroupSerializer
from tenancy.models import Tenant, TenantGroup
@@ -69,18 +68,6 @@ class ExportTemplateSerializer(ValidatedModelSerializer):
]
-#
-# Topology maps
-#
-
-class TopologyMapSerializer(ValidatedModelSerializer):
- site = NestedSiteSerializer()
-
- class Meta:
- model = TopologyMap
- fields = ['id', 'name', 'slug', 'site', 'device_patterns', 'description']
-
-
#
# Tags
#
diff --git a/netbox/extras/api/urls.py b/netbox/extras/api/urls.py
index c135280ea..ddfe2107c 100644
--- a/netbox/extras/api/urls.py
+++ b/netbox/extras/api/urls.py
@@ -26,9 +26,6 @@ router.register(r'graphs', views.GraphViewSet)
# Export templates
router.register(r'export-templates', views.ExportTemplateViewSet)
-# Topology maps
-router.register(r'topology-maps', views.TopologyMapViewSet)
-
# Tags
router.register(r'tags', views.TagViewSet)
diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py
index 44e010cd2..cbc851c4d 100644
--- a/netbox/extras/api/views.py
+++ b/netbox/extras/api/views.py
@@ -2,8 +2,7 @@ from collections import OrderedDict
from django.contrib.contenttypes.models import ContentType
from django.db.models import Count
-from django.http import Http404, HttpResponse
-from django.shortcuts import get_object_or_404
+from django.http import Http404
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from rest_framework.response import Response
@@ -11,8 +10,7 @@ from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
from extras import filters
from extras.models import (
- ConfigContext, CustomFieldChoice, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap,
- Tag,
+ ConfigContext, CustomFieldChoice, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, Tag,
)
from extras.reports import get_report, get_reports
from utilities.api import FieldChoicesViewSet, IsAuthenticatedOrLoginNotRequired, ModelViewSet
@@ -115,34 +113,6 @@ class ExportTemplateViewSet(ModelViewSet):
filterset_class = filters.ExportTemplateFilter
-#
-# Topology maps
-#
-
-class TopologyMapViewSet(ModelViewSet):
- queryset = TopologyMap.objects.select_related('site')
- serializer_class = serializers.TopologyMapSerializer
- filterset_class = filters.TopologyMapFilter
-
- @action(detail=True)
- def render(self, request, pk):
-
- tmap = get_object_or_404(TopologyMap, pk=pk)
- img_format = 'png'
-
- try:
- data = tmap.render(img_format=img_format)
- except Exception as e:
- return HttpResponse(
- "There was an error generating the requested graph: %s" % e
- )
-
- response = HttpResponse(data, content_type='image/{}'.format(img_format))
- response['Content-Disposition'] = 'inline; filename="{}.{}"'.format(tmap.slug, img_format)
-
- return response
-
-
#
# Tags
#
diff --git a/netbox/extras/constants.py b/netbox/extras/constants.py
index b72ae8c08..51add5a4c 100644
--- a/netbox/extras/constants.py
+++ b/netbox/extras/constants.py
@@ -134,16 +134,6 @@ TEMPLATE_LANGUAGE_CHOICES = (
(TEMPLATE_LANGUAGE_JINJA2, 'Jinja2'),
)
-# Topology map types
-TOPOLOGYMAP_TYPE_NETWORK = 1
-TOPOLOGYMAP_TYPE_CONSOLE = 2
-TOPOLOGYMAP_TYPE_POWER = 3
-TOPOLOGYMAP_TYPE_CHOICES = (
- (TOPOLOGYMAP_TYPE_NETWORK, 'Network'),
- (TOPOLOGYMAP_TYPE_CONSOLE, 'Console'),
- (TOPOLOGYMAP_TYPE_POWER, 'Power'),
-)
-
# Change log actions
OBJECTCHANGE_ACTION_CREATE = 1
OBJECTCHANGE_ACTION_UPDATE = 2
diff --git a/netbox/extras/filters.py b/netbox/extras/filters.py
index 49e879fe4..bb8c78e2e 100644
--- a/netbox/extras/filters.py
+++ b/netbox/extras/filters.py
@@ -5,7 +5,7 @@ from django.db.models import Q
from dcim.models import DeviceRole, Platform, Region, Site
from tenancy.models import Tenant, TenantGroup
from .constants import CF_FILTER_DISABLED, CF_FILTER_EXACT, CF_TYPE_BOOLEAN, CF_TYPE_SELECT
-from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, Tag, TopologyMap
+from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, Tag
class CustomFieldFilter(django_filters.Filter):
@@ -103,24 +103,6 @@ class TagFilter(django_filters.FilterSet):
)
-class TopologyMapFilter(django_filters.FilterSet):
- site_id = django_filters.ModelMultipleChoiceFilter(
- field_name='site',
- queryset=Site.objects.all(),
- label='Site',
- )
- site = django_filters.ModelMultipleChoiceFilter(
- field_name='site__slug',
- queryset=Site.objects.all(),
- to_field_name='slug',
- label='Site (slug)',
- )
-
- class Meta:
- model = TopologyMap
- fields = ['name', 'slug']
-
-
class ConfigContextFilter(django_filters.FilterSet):
q = django_filters.CharFilter(
method='search',
diff --git a/netbox/extras/migrations/0024_remove_topology_maps.py b/netbox/extras/migrations/0024_remove_topology_maps.py
new file mode 100644
index 000000000..c019f4cec
--- /dev/null
+++ b/netbox/extras/migrations/0024_remove_topology_maps.py
@@ -0,0 +1,16 @@
+# Generated by Django 2.2 on 2019-08-09 01:26
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('extras', '0023_fix_tag_sequences'),
+ ]
+
+ operations = [
+ migrations.DeleteModel(
+ name='TopologyMap',
+ ),
+ ]
diff --git a/netbox/extras/models.py b/netbox/extras/models.py
index c5df5c2e5..68889dc33 100644
--- a/netbox/extras/models.py
+++ b/netbox/extras/models.py
@@ -7,17 +7,14 @@ from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import JSONField
from django.core.validators import ValidationError
from django.db import models
-from django.db.models import F, Q
from django.http import HttpResponse
from django.template import Template, Context
from django.urls import reverse
-import graphviz
from jinja2 import Environment
from taggit.models import TagBase, GenericTaggedItemBase
-from dcim.constants import CONNECTION_STATUS_CONNECTED
from utilities.fields import ColorField
-from utilities.utils import deepmerge, foreground_color, model_names_to_filter_dict
+from utilities.utils import deepmerge, model_names_to_filter_dict
from .constants import *
from .querysets import ConfigContextQuerySet
@@ -496,154 +493,6 @@ class ExportTemplate(models.Model):
return response
-#
-# Topology maps
-#
-
-class TopologyMap(models.Model):
- name = models.CharField(
- max_length=50,
- unique=True
- )
- slug = models.SlugField(
- unique=True
- )
- type = models.PositiveSmallIntegerField(
- choices=TOPOLOGYMAP_TYPE_CHOICES,
- default=TOPOLOGYMAP_TYPE_NETWORK
- )
- site = models.ForeignKey(
- to='dcim.Site',
- on_delete=models.CASCADE,
- related_name='topology_maps',
- blank=True,
- null=True
- )
- device_patterns = models.TextField(
- help_text='Identify devices to include in the diagram using regular '
- 'expressions, one per line. Each line will result in a new '
- 'tier of the drawing. Separate multiple regexes within a '
- 'line using semicolons. Devices will be rendered in the '
- 'order they are defined.'
- )
- description = models.CharField(
- max_length=100,
- blank=True
- )
-
- class Meta:
- ordering = ['name']
-
- def __str__(self):
- return self.name
-
- @property
- def device_sets(self):
- if not self.device_patterns:
- return None
- return [line.strip() for line in self.device_patterns.split('\n')]
-
- def render(self, img_format='png'):
-
- from dcim.models import Device
-
- # Construct the graph
- if self.type == TOPOLOGYMAP_TYPE_NETWORK:
- G = graphviz.Graph
- else:
- G = graphviz.Digraph
- self.graph = G()
- self.graph.graph_attr['ranksep'] = '1'
- seen = set()
- for i, device_set in enumerate(self.device_sets):
-
- subgraph = G(name='sg{}'.format(i))
- subgraph.graph_attr['rank'] = 'same'
- subgraph.graph_attr['directed'] = 'true'
-
- # Add a pseudonode for each device_set to enforce hierarchical layout
- subgraph.node('set{}'.format(i), label='', shape='none', width='0')
- if i:
- self.graph.edge('set{}'.format(i - 1), 'set{}'.format(i), style='invis')
-
- # Add each device to the graph
- 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))
- subgraph.node(d.name, style='filled', fillcolor=bg_color, fontcolor=fg_color, fontname='sans')
-
- # Add an invisible connection to each successive device in a set to enforce horizontal order
- for j in range(0, len(devices) - 1):
- subgraph.edge(devices[j].name, devices[j + 1].name, style='invis')
-
- self.graph.subgraph(subgraph)
-
- # Compile list of all devices
- device_superset = Q()
- for device_set in self.device_sets:
- for query in device_set.split(';'): # Split regexes on semicolons
- device_superset = device_superset | Q(name__regex=query)
- devices = Device.objects.filter(*(device_superset,))
-
- # Draw edges depending on graph type
- if self.type == TOPOLOGYMAP_TYPE_NETWORK:
- self.add_network_connections(devices)
- elif self.type == TOPOLOGYMAP_TYPE_CONSOLE:
- self.add_console_connections(devices)
- elif self.type == TOPOLOGYMAP_TYPE_POWER:
- self.add_power_connections(devices)
-
- return self.graph.pipe(format=img_format)
-
- def add_network_connections(self, devices):
-
- from circuits.models import CircuitTermination
- from dcim.models import Interface
-
- # Add all interface connections to the graph
- connected_interfaces = Interface.objects.select_related(
- '_connected_interface__device'
- ).filter(
- Q(device__in=devices) | Q(_connected_interface__device__in=devices),
- _connected_interface__isnull=False,
- pk__lt=F('_connected_interface')
- )
- for interface in connected_interfaces:
- style = 'solid' if interface.connection_status == CONNECTION_STATUS_CONNECTED else 'dashed'
- self.graph.edge(interface.device.name, interface.connected_endpoint.device.name, style=style)
-
- # Add all circuits to the graph
- for termination in CircuitTermination.objects.filter(term_side='A', connected_endpoint__device__in=devices):
- peer_termination = termination.get_peer_termination()
- if (peer_termination is not None and peer_termination.interface is not None and
- peer_termination.interface.device in devices):
- self.graph.edge(termination.interface.device.name, peer_termination.interface.device.name, color='blue')
-
- def add_console_connections(self, devices):
-
- from dcim.models import ConsolePort
-
- # Add all console connections to the graph
- for cp in ConsolePort.objects.filter(device__in=devices, connected_endpoint__device__in=devices):
- style = 'solid' if cp.connection_status == CONNECTION_STATUS_CONNECTED else 'dashed'
- self.graph.edge(cp.connected_endpoint.device.name, cp.device.name, style=style)
-
- def add_power_connections(self, devices):
-
- from dcim.models import PowerPort
-
- # Add all power connections to the graph
- for pp in PowerPort.objects.filter(device__in=devices, _connected_poweroutlet__device__in=devices):
- style = 'solid' if pp.connection_status == CONNECTION_STATUS_CONNECTED else 'dashed'
- self.graph.edge(pp.connected_endpoint.device.name, pp.device.name, style=style)
-
-
#
# Image attachments
#
diff --git a/netbox/netbox/views.py b/netbox/netbox/views.py
index 146bba6db..9d431be41 100644
--- a/netbox/netbox/views.py
+++ b/netbox/netbox/views.py
@@ -21,7 +21,7 @@ from dcim.tables import (
CableTable, DeviceDetailTable, DeviceTypeTable, PowerFeedTable, RackTable, RackGroupTable, SiteTable,
VirtualChassisTable,
)
-from extras.models import ObjectChange, ReportResult, TopologyMap
+from extras.models import ObjectChange, ReportResult
from ipam.filters import AggregateFilter, IPAddressFilter, PrefixFilter, VLANFilter, VRFFilter
from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF
from ipam.tables import AggregateTable, IPAddressTable, PrefixTable, VLANTable, VRFTable
@@ -222,7 +222,6 @@ class HomeView(View):
return render(request, self.template_name, {
'search_form': SearchForm(),
'stats': stats,
- 'topology_maps': TopologyMap.objects.filter(site__isnull=True),
'report_results': ReportResult.objects.order_by('-created')[:10],
'changelog': ObjectChange.objects.select_related('user', 'changed_object_type')[:50]
})
diff --git a/netbox/templates/dcim/site.html b/netbox/templates/dcim/site.html
index 0e38d2967..8af48968c 100644
--- a/netbox/templates/dcim/site.html
+++ b/netbox/templates/dcim/site.html
@@ -285,25 +285,6 @@
{% endif %}
-
-
- Topology Maps
-
- {% if topology_maps %}
-
- {% for tm in topology_maps %}
-
- {{ tm }} |
- {{ tm.description }} |
-
- {% endfor %}
-
- {% else %}
-
- None
-
- {% endif %}
-
{% include 'inc/modal.html' with modal_name='graphs' %}
diff --git a/netbox/templates/home.html b/netbox/templates/home.html
index 8d483568f..be63b19c5 100644
--- a/netbox/templates/home.html
+++ b/netbox/templates/home.html
@@ -259,29 +259,6 @@
-
-
- Global Topology Maps
-
- {% if topology_maps and perms.extras.view_topologymap %}
-
- {% for tm in topology_maps %}
-
- {{ tm }} |
- {{ tm.description }} |
-
- {% endfor %}
-
- {% elif perms.extras.view_topologymap %}
-
- None found
-
- {% else %}
-
- No permission
-
- {% endif %}
-
Reports
diff --git a/requirements.txt b/requirements.txt
index 3ad165a4b..f2a5c84ba 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -12,7 +12,6 @@ django-taggit-serializer==0.1.7
django-timezone-field==3.0
djangorestframework==3.9.4
drf-yasg[validation]==1.16.0
-graphviz==0.10.1
Jinja2==2.10.1
Markdown==2.6.11
netaddr==0.7.19