diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 356753abc..48f725510 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -16,7 +16,7 @@ from .serializers import SiteSerializer, RackGroupSerializer, RackSerializer, Ra ManufacturerSerializer, DeviceTypeSerializer, DeviceRoleSerializer, PlatformSerializer, DeviceSerializer, \ DeviceNestedSerializer, ConsolePortSerializer, ConsoleServerPortSerializer, PowerPortSerializer, \ PowerOutletSerializer, InterfaceSerializer, InterfaceDetailSerializer, InterfaceConnectionSerializer -from extras.api.renderers import BINDZoneRenderer +from extras.api.renderers import BINDZoneRenderer, FlatJSONRenderer from utilities.api import ServiceUnavailable @@ -198,7 +198,7 @@ class DeviceListView(generics.ListAPIView): .prefetch_related('primary_ip__nat_outside') serializer_class = DeviceSerializer filter_class = DeviceFilter - renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [BINDZoneRenderer] + renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [BINDZoneRenderer, FlatJSONRenderer] class DeviceDetailView(generics.RetrieveAPIView): diff --git a/netbox/dcim/tests/test_apis.py b/netbox/dcim/tests/test_apis.py index b6bd72051..45d6dd2f0 100644 --- a/netbox/dcim/tests/test_apis.py +++ b/netbox/dcim/tests/test_apis.py @@ -349,6 +349,48 @@ class DeviceTest(APITestCase): sorted(RackTest.nested_fields), ) + def test_get_list_flat(self, endpoint='/api/dcim/devices/?format=json_flat'): + + flat_fields = [ + 'comments', + 'device_role_id', + 'device_role_name', + 'device_role_slug', + 'device_type_id', + 'device_type_manufacturer_id', + 'device_type_manufacturer_name', + 'device_type_manufacturer_slug', + 'device_type_model', + 'device_type_slug', + 'display_name', + 'face', + 'id', + 'name', + 'platform_id', + 'platform_name', + 'platform_slug', + 'position', + 'primary_ip_address', + 'primary_ip_family', + 'primary_ip_id', + 'rack_display_name', + 'rack_facility_id', + 'rack_id', + 'rack_name', + 'ro_snmp', + 'serial', + 'status', + ] + + response = self.client.get(endpoint) + content = json.loads(response.content) + self.assertEqual(response.status_code, status.HTTP_200_OK) + device = content[0] + self.assertEqual( + sorted(device.keys()), + sorted(flat_fields), + ) + def test_get_detail(self, endpoint='/api/dcim/devices/1/'): response = self.client.get(endpoint) content = json.loads(response.content) diff --git a/netbox/extras/api/renderers.py b/netbox/extras/api/renderers.py index 0a464c3b1..e557928bd 100644 --- a/netbox/extras/api/renderers.py +++ b/netbox/extras/api/renderers.py @@ -1,6 +1,6 @@ +import json from rest_framework import renderers - # IP address family designations AF = { 4: 'A', @@ -29,3 +29,23 @@ class BINDZoneRenderer(renderers.BaseRenderer): except KeyError: pass return '\n'.join(records) + + +class FlatJSONRenderer(renderers.BaseRenderer): + """ + Flattens a nested JSON reponse. + """ + format = 'json_flat' + media_type = 'application/json' + + def render(self, data, media_type=None, renderer_context=None): + + def flatten(entry): + for key, val in entry.iteritems(): + if isinstance(val, dict): + for child_key, child_val in flatten(val): + yield "{}_{}".format(key, child_key), child_val + else: + yield key, val + + return json.dumps([dict(flatten(i)) for i in data])