Merge pull request #3911 from netbox-community/3801-devicetype-yaml

Closes #3801: Change DeviceType export from CSV to YAML
This commit is contained in:
Jeremy Stretch 2020-01-13 15:43:20 -05:00 committed by GitHub
commit 8f636d9636
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 127 additions and 25 deletions

View File

@ -235,6 +235,7 @@ PATCH) to maintain backward compatibility. This behavior will be discontinued be
* [#3664](https://github.com/digitalocean/netbox/issues/3664) - Enable applying configuration contexts by tags
* [#3706](https://github.com/digitalocean/netbox/issues/3706) - Increase `available_power` maximum value on PowerFeed
* [#3731](https://github.com/digitalocean/netbox/issues/3731) - Change Graph.type to a ContentType foreign key field
* [#3801](https://github.com/digitalocean/netbox/issues/3801) - Use YAML for export of device types
## Bug Fixes (From Beta)

View File

@ -2,6 +2,7 @@ from collections import OrderedDict
from itertools import count, groupby
import svgwrite
import yaml
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
@ -25,15 +26,14 @@ from utilities.fields import ColorField
from utilities.managers import NaturalOrderingManager
from utilities.models import ChangeLoggedModel
from utilities.utils import foreground_color, to_meters
from .device_components import (
CableTermination, ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, InventoryItem, PowerOutlet,
PowerPort, RearPort,
)
from .device_component_templates import (
ConsolePortTemplate, ConsoleServerPortTemplate, DeviceBayTemplate, FrontPortTemplate, InterfaceTemplate,
PowerOutletTemplate, PowerPortTemplate, RearPortTemplate,
)
from .device_components import (
CableTermination, ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, InventoryItem, PowerOutlet,
PowerPort, RearPort,
)
__all__ = (
'Cable',
@ -1003,17 +1003,92 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
def get_absolute_url(self):
return reverse('dcim:devicetype', args=[self.pk])
def to_csv(self):
return (
self.manufacturer.name,
self.model,
self.slug,
self.part_number,
self.u_height,
self.is_full_depth,
self.get_subdevice_role_display(),
self.comments,
)
def to_yaml(self):
data = OrderedDict((
('manufacturer', self.manufacturer.name),
('model', self.model),
('slug', self.slug),
('part_number', self.part_number),
('u_height', self.u_height),
('is_full_depth', self.is_full_depth),
('subdevice_role', self.subdevice_role),
('comments', self.comments),
))
# Component templates
if self.consoleport_templates.exists():
data['console-ports'] = [
{
'name': c.name,
'type': c.type,
}
for c in self.consoleport_templates.all()
]
if self.consoleserverport_templates.exists():
data['console-server-ports'] = [
{
'name': c.name,
'type': c.type,
}
for c in self.consoleserverport_templates.all()
]
if self.powerport_templates.exists():
data['power-ports'] = [
{
'name': c.name,
'type': c.type,
'maximum_draw': c.maximum_draw,
'allocated_draw': c.allocated_draw,
}
for c in self.powerport_templates.all()
]
if self.poweroutlet_templates.exists():
data['power-outlets'] = [
{
'name': c.name,
'type': c.type,
'power_port': c.power_port.name if c.power_port else None,
'feed_leg': c.feed_leg,
}
for c in self.poweroutlet_templates.all()
]
if self.interface_templates.exists():
data['interfaces'] = [
{
'name': c.name,
'type': c.type,
'mgmt_only': c.mgmt_only,
}
for c in self.interface_templates.all()
]
if self.frontport_templates.exists():
data['front-ports'] = [
{
'name': c.name,
'type': c.type,
'rear_port': c.rear_port.name,
'rear_port_position': c.rear_port_position,
}
for c in self.frontport_templates.all()
]
if self.rearport_templates.exists():
data['rear-ports'] = [
{
'name': c.name,
'type': c.type,
'positions': c.positions,
}
for c in self.rearport_templates.all()
]
if self.device_bay_templates.exists():
data['device-bays'] = [
{
'name': c.name,
}
for c in self.device_bay_templates.all()
]
return yaml.dump(dict(data), sort_keys=False)
def clean(self):

View File

@ -1,5 +1,6 @@
import urllib.parse
import yaml
from django.test import Client, TestCase
from django.urls import reverse
@ -327,6 +328,17 @@ class DeviceTypeTestCase(TestCase):
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
self.assertEqual(response.status_code, 200)
def test_devicetype_export(self):
url = reverse('dcim:devicetype_list')
response = self.client.get('{}?export'.format(url))
self.assertEqual(response.status_code, 200)
data = list(yaml.load_all(response.content, Loader=yaml.SafeLoader))
self.assertEqual(len(data), 3)
self.assertEqual(data[0]['manufacturer'], 'Manufacturer 1')
self.assertEqual(data[0]['model'], 'Device Type 1')
def test_devicetype(self):
devicetype = DeviceType.objects.first()

View File

@ -2056,7 +2056,8 @@ class ConsoleConnectionsListView(PermissionRequiredMixin, ObjectListView):
obj.get_connection_status_display(),
])
csv_data.append(csv)
return csv_data
return '\n'.join(csv_data)
class PowerConnectionsListView(PermissionRequiredMixin, ObjectListView):
@ -2087,7 +2088,8 @@ class PowerConnectionsListView(PermissionRequiredMixin, ObjectListView):
obj.get_connection_status_display(),
])
csv_data.append(csv)
return csv_data
return '\n'.join(csv_data)
class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView):
@ -2126,7 +2128,8 @@ class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView):
obj.get_connection_status_display(),
])
csv_data.append(csv)
return csv_data
return '\n'.join(csv_data)
#

View File

@ -75,6 +75,14 @@ class ObjectListView(View):
table = None
template_name = None
def queryset_to_yaml(self):
"""
Export the queryset of objects as concatenated YAML documents.
"""
yaml_data = [obj.to_yaml() for obj in self.queryset]
return '---\n'.join(yaml_data)
def queryset_to_csv(self):
"""
Export the queryset of objects as comma-separated value (CSV), using the model's to_csv() method.
@ -90,7 +98,7 @@ class ObjectListView(View):
data = csv_format(obj.to_csv())
csv_data.append(data)
return csv_data
return '\n'.join(csv_data)
def get(self, request):
@ -121,13 +129,16 @@ class ObjectListView(View):
)
)
# Check for YAML export support
elif 'export' in request.GET and hasattr(model, 'to_yaml'):
response = HttpResponse(self.queryset_to_yaml(), content_type='text/yaml')
filename = 'netbox_{}.yaml'.format(self.queryset.model._meta.verbose_name_plural)
response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
return response
# Fall back to built-in CSV formatting if export requested but no template specified
elif 'export' in request.GET and hasattr(model, 'to_csv'):
data = self.queryset_to_csv()
response = HttpResponse(
'\n'.join(data),
content_type='text/csv'
)
response = HttpResponse(self.queryset_to_csv(), content_type='text/csv')
filename = 'netbox_{}.csv'.format(self.queryset.model._meta.verbose_name_plural)
response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
return response