From 66600ef98445fb247a1443bf8a8faeae397c60f1 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 16 Jun 2016 12:08:50 -0400 Subject: [PATCH] Implemented built-in CSV export for IPAM objects --- netbox/ipam/models.py | 44 +++++++++++++++++++++++ netbox/templates/inc/export_button.html | 20 +++++++++++ netbox/templates/ipam/aggregate_list.html | 14 +------- netbox/templates/ipam/ipaddress_list.html | 14 +------- netbox/templates/ipam/prefix_list.html | 14 +------- netbox/templates/ipam/vlan_list.html | 14 +------- netbox/templates/ipam/vrf_list.html | 14 +------- netbox/utilities/views.py | 12 ++++++- 8 files changed, 80 insertions(+), 66 deletions(-) create mode 100644 netbox/templates/inc/export_button.html diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 97fb6504a..46c1fbd08 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -55,6 +55,13 @@ class VRF(models.Model): def get_absolute_url(self): return reverse('ipam:vrf', args=[self.pk]) + def to_csv(self): + return ','.join([ + self.name, + self.rd, + self.description, + ]) + class RIR(models.Model): """ @@ -115,6 +122,14 @@ class Aggregate(models.Model): self.family = self.prefix.version super(Aggregate, self).save(*args, **kwargs) + def to_csv(self): + return ','.join([ + str(self.prefix), + self.rir.name, + self.date_added.isoformat() if self.date_added else '', + self.description, + ]) + def get_utilization(self): """ Determine the utilization rate of the aggregate prefix and return it as a percentage. @@ -221,6 +236,16 @@ class Prefix(models.Model): self.family = self.prefix.version super(Prefix, self).save(*args, **kwargs) + def to_csv(self): + return ','.join([ + str(self.prefix), + self.vrf.rd if self.vrf else '', + self.site.name, + self.get_status_display(), + self.role.name if self.role else '', + self.description, + ]) + @property def new_subnet(self): if self.family == 4: @@ -267,6 +292,16 @@ class IPAddress(models.Model): self.family = self.address.version super(IPAddress, self).save(*args, **kwargs) + def to_csv(self): + return ','.join([ + str(self.address), + self.vrf.rd if self.vrf else '', + self.device.name if self.device else '', + self.interface.name if self.interface else '', + 'True' if getattr(self, 'primary_for', False) else '', + self.description, + ]) + @property def device(self): if self.interface: @@ -298,5 +333,14 @@ class VLAN(models.Model): def get_absolute_url(self): return reverse('ipam:vlan', args=[self.pk]) + def to_csv(self): + return ','.join([ + self.site.name, + str(self.vid), + self.name, + self.get_status_display(), + self.role.name if self.role else '', + ]) + def get_status_class(self): return STATUS_CHOICE_CLASSES[self.status] diff --git a/netbox/templates/inc/export_button.html b/netbox/templates/inc/export_button.html new file mode 100644 index 000000000..1d9651121 --- /dev/null +++ b/netbox/templates/inc/export_button.html @@ -0,0 +1,20 @@ +{% if export_templates %} +
+ + +
+{% else %} + + + Export {{ obj_type }} + +{% endif %} diff --git a/netbox/templates/ipam/aggregate_list.html b/netbox/templates/ipam/aggregate_list.html index 5f249741c..af8f87893 100644 --- a/netbox/templates/ipam/aggregate_list.html +++ b/netbox/templates/ipam/aggregate_list.html @@ -12,19 +12,7 @@ Add an aggregate {% endif %} - {% if export_templates %} -
- - -
- {% endif %} + {% include 'inc/export_button.html' with obj_type='aggregates' %}

Aggregates

diff --git a/netbox/templates/ipam/ipaddress_list.html b/netbox/templates/ipam/ipaddress_list.html index 93954cbdb..edcc140c3 100644 --- a/netbox/templates/ipam/ipaddress_list.html +++ b/netbox/templates/ipam/ipaddress_list.html @@ -16,19 +16,7 @@ Import IPs {% endif %} - {% if export_templates %} -
- - -
- {% endif %} + {% include 'inc/export_button.html' with obj_type='IPs' %}

IP Addresses

diff --git a/netbox/templates/ipam/prefix_list.html b/netbox/templates/ipam/prefix_list.html index 0dd314ee6..0b871f22a 100644 --- a/netbox/templates/ipam/prefix_list.html +++ b/netbox/templates/ipam/prefix_list.html @@ -16,19 +16,7 @@ Import prefixes {% endif %} - {% if export_templates %} -
- - -
- {% endif %} + {% include 'inc/export_button.html' with obj_type='prefixes' %}

Prefixes

diff --git a/netbox/templates/ipam/vlan_list.html b/netbox/templates/ipam/vlan_list.html index f10e2dc15..f9ed6d816 100644 --- a/netbox/templates/ipam/vlan_list.html +++ b/netbox/templates/ipam/vlan_list.html @@ -16,19 +16,7 @@ Import VLANs {% endif %} - {% if export_templates %} -
- - -
- {% endif %} + {% include 'inc/export_button.html' with obj_type='VLANs' %}

VLANs

diff --git a/netbox/templates/ipam/vrf_list.html b/netbox/templates/ipam/vrf_list.html index cb59fa3d6..641aae1d6 100644 --- a/netbox/templates/ipam/vrf_list.html +++ b/netbox/templates/ipam/vrf_list.html @@ -16,19 +16,7 @@ Import VRFs {% endif %} - {% if export_templates %} -
- - -
- {% endif %} + {% include 'inc/export_button.html' with obj_type='VRFs' %}

VRFs

diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index e849ac163..bae3a54bb 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -6,7 +6,7 @@ from django.contrib.contenttypes.models import ContentType from django.core.urlresolvers import reverse from django.db import transaction, IntegrityError from django.db.models import ProtectedError -from django.http import HttpResponseRedirect +from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect, render from django.template import TemplateSyntaxError from django.utils.decorators import method_decorator @@ -47,6 +47,16 @@ class ObjectListView(View): except TemplateSyntaxError: messages.error(request, "There was an error rendering the selected export template ({})." .format(et.name)) + # Fall back to built-in CSV export + elif 'export' in request.GET and hasattr(model, 'to_csv'): + output = '\n'.join([obj.to_csv() for obj in self.queryset]) + response = HttpResponse( + output, + content_type='text/csv' + ) + response['Content-Disposition'] = 'attachment; filename="netbox_{}.csv"'\ + .format(self.queryset.model._meta.verbose_name_plural) + return response # Attempt to redirect automatically if the search query returns a single result if self.redirect_on_single_result and self.queryset.count() == 1 and request.GET: