Browse Source

Implemented built-in CSV export for IPAM objects

Jeremy Stretch 8 years ago
parent
commit
66600ef984

+ 44 - 0
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]

+ 20 - 0
netbox/templates/inc/export_button.html

@@ -0,0 +1,20 @@
+{% if export_templates %}
+    <div class="btn-group">
+        <button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+            <span class="glyphicon glyphicon-export" aria-hidden="true"></span>
+            Export {{ obj_type }} <span class="caret"></span>
+        </button>
+        <ul class="dropdown-menu">
+            <li><a href="?{% if request.GET %}{{ request.GET.urlencode }}&{% endif %}export">CSV (default)</a></li>
+            <li class="divider"></li>
+            {% for et in export_templates %}
+                <li><a href="?{% if request.GET %}{{ request.GET.urlencode }}&{% endif %}export={{ et.name }}">{{ et.name }}</a></li>
+            {% endfor %}
+        </ul>
+    </div>
+{% else %}
+    <a href="?{% if request.GET %}{{ request.GET.urlencode }}&{% endif %}export" class="btn btn-success">
+        <span class="glyphicon glyphicon-export" aria-hidden="true"></span>
+        Export {{ obj_type }}
+    </a>
+{% endif %}

+ 1 - 13
netbox/templates/ipam/aggregate_list.html

@@ -12,19 +12,7 @@
 			Add an aggregate
 		</a>
     {% endif %}
-    {% if export_templates %}
-        <div class="btn-group">
-            <button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                <span class="glyphicon glyphicon-export" aria-hidden="true"></span>
-                Export aggregates <span class="caret"></span>
-            </button>
-            <ul class="dropdown-menu">
-                {% for et in export_templates %}
-                    <li><a href="{% url 'ipam:aggregate_list' %}?{% if request.GET %}{{ request.GET.urlencode }}&{% endif %}export={{ et.name }}">{{ et.name }}</a></li>
-                {% endfor %}
-            </ul>
-        </div>
-    {% endif %}
+    {% include 'inc/export_button.html' with obj_type='aggregates' %}
 </div>
 <h1>Aggregates</h1>
 <div class="row">

+ 1 - 13
netbox/templates/ipam/ipaddress_list.html

@@ -16,19 +16,7 @@
 			Import IPs
 		</a>
 	{% endif %}
-    {% if export_templates %}
-        <div class="btn-group">
-            <button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                <span class="glyphicon glyphicon-export" aria-hidden="true"></span>
-                Export IP addresses <span class="caret"></span>
-            </button>
-            <ul class="dropdown-menu">
-                {% for et in export_templates %}
-                    <li><a href="{% url 'ipam:ipaddress_list' %}?{% if request.GET %}{{ request.GET.urlencode }}&{% endif %}export={{ et.name }}">{{ et.name }}</a></li>
-                {% endfor %}
-            </ul>
-        </div>
-    {% endif %}
+    {% include 'inc/export_button.html' with obj_type='IPs' %}
 </div>
 <h1>IP Addresses</h1>
 <div class="row">

+ 1 - 13
netbox/templates/ipam/prefix_list.html

@@ -16,19 +16,7 @@
 			Import prefixes
 		</a>
 	{% endif %}
-    {% if export_templates %}
-        <div class="btn-group">
-            <button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                <span class="glyphicon glyphicon-export" aria-hidden="true"></span>
-                Export prefixes <span class="caret"></span>
-            </button>
-            <ul class="dropdown-menu">
-                {% for et in export_templates %}
-                    <li><a href="{% url 'ipam:prefix_list' %}?{% if request.GET %}{{ request.GET.urlencode }}&{% endif %}export={{ et.name }}">{{ et.name }}</a></li>
-                {% endfor %}
-            </ul>
-        </div>
-    {% endif %}
+    {% include 'inc/export_button.html' with obj_type='prefixes' %}
 </div>
 <h1>Prefixes</h1>
 <div class="row">

+ 1 - 13
netbox/templates/ipam/vlan_list.html

@@ -16,19 +16,7 @@
 			Import VLANs
 		</a>
 	{% endif %}
-    {% if export_templates %}
-        <div class="btn-group">
-            <button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                <span class="glyphicon glyphicon-export" aria-hidden="true"></span>
-                Export VLANs <span class="caret"></span>
-            </button>
-            <ul class="dropdown-menu">
-                {% for et in export_templates %}
-                    <li><a href="{% url 'ipam:vlan_list' %}?{% if request.GET %}{{ request.GET.urlencode }}&{% endif %}export={{ et.name }}">{{ et.name }}</a></li>
-                {% endfor %}
-            </ul>
-        </div>
-    {% endif %}
+    {% include 'inc/export_button.html' with obj_type='VLANs' %}
 </div>
 <h1>VLANs</h1>
 <div class="row">

+ 1 - 13
netbox/templates/ipam/vrf_list.html

@@ -16,19 +16,7 @@
 			Import VRFs
 		</a>
 	{% endif %}
-    {% if export_templates %}
-        <div class="btn-group">
-            <button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                <span class="glyphicon glyphicon-export" aria-hidden="true"></span>
-                Export VRFs <span class="caret"></span>
-            </button>
-            <ul class="dropdown-menu">
-                {% for et in export_templates %}
-                    <li><a href="{% url 'ipam:vrf_list' %}?{% if request.GET %}{{ request.GET.urlencode }}&{% endif %}export={{ et.name }}">{{ et.name }}</a></li>
-                {% endfor %}
-            </ul>
-        </div>
-    {% endif %}
+    {% include 'inc/export_button.html' with obj_type='VRFs' %}
 </div>
 <h1>VRFs</h1>
 <div class="row">

+ 11 - 1
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: