Added API endpoint, tests for Graphs

This commit is contained in:
Jeremy Stretch 2017-03-20 15:14:33 -04:00
parent 42fd14f5c0
commit 266f9cc370
9 changed files with 204 additions and 11 deletions

View File

@ -7,7 +7,7 @@ from rest_framework.viewsets import ModelViewSet
from circuits import filters from circuits import filters
from circuits.models import Provider, CircuitTermination, CircuitType, Circuit from circuits.models import Provider, CircuitTermination, CircuitType, Circuit
from extras.models import Graph, GRAPH_TYPE_PROVIDER from extras.models import Graph, GRAPH_TYPE_PROVIDER
from extras.api.serializers import GraphSerializer from extras.api.serializers import RenderedGraphSerializer
from extras.api.views import CustomFieldModelViewSet from extras.api.views import CustomFieldModelViewSet
from utilities.api import WritableSerializerMixin from utilities.api import WritableSerializerMixin
from . import serializers from . import serializers
@ -25,9 +25,12 @@ class ProviderViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
@detail_route() @detail_route()
def graphs(self, request, pk=None): def graphs(self, request, pk=None):
"""
A convenience method for rendering graphs for a particular provider.
"""
provider = get_object_or_404(Provider, pk=pk) provider = get_object_or_404(Provider, pk=pk)
queryset = Graph.objects.filter(type=GRAPH_TYPE_PROVIDER) queryset = Graph.objects.filter(type=GRAPH_TYPE_PROVIDER)
serializer = GraphSerializer(queryset, many=True, context={'graphed_object': provider}) serializer = RenderedGraphSerializer(queryset, many=True, context={'graphed_object': provider})
return Response(serializer.data) return Response(serializer.data)

View File

@ -5,6 +5,7 @@ from django.contrib.auth.models import User
from django.urls import reverse from django.urls import reverse
from dcim.models import Site from dcim.models import Site
from extras.models import Graph, GRAPH_TYPE_PROVIDER
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider, TERM_SIDE_A, TERM_SIDE_Z from circuits.models import Circuit, CircuitTermination, CircuitType, Provider, TERM_SIDE_A, TERM_SIDE_Z
from users.models import Token from users.models import Token
from utilities.tests import HttpStatusMixin from utilities.tests import HttpStatusMixin
@ -29,6 +30,27 @@ class ProviderTest(HttpStatusMixin, APITestCase):
self.assertEqual(response.data['name'], self.provider1.name) self.assertEqual(response.data['name'], self.provider1.name)
def test_get_provider_graphs(self):
self.graph1 = Graph.objects.create(
type=GRAPH_TYPE_PROVIDER, name='Test Graph 1',
source='http://example.com/graphs.py?provider={{ obj.slug }}&foo=1'
)
self.graph2 = Graph.objects.create(
type=GRAPH_TYPE_PROVIDER, name='Test Graph 2',
source='http://example.com/graphs.py?provider={{ obj.slug }}&foo=2'
)
self.graph3 = Graph.objects.create(
type=GRAPH_TYPE_PROVIDER, name='Test Graph 3',
source='http://example.com/graphs.py?provider={{ obj.slug }}&foo=3'
)
url = reverse('circuits-api:provider-graphs', kwargs={'pk': self.provider1.pk})
response = self.client.get(url, **self.header)
self.assertEqual(len(response.data), 3)
self.assertEqual(response.data[0]['embed_url'], 'http://example.com/graphs.py?provider=test-provider-1&foo=1')
def test_list_providers(self): def test_list_providers(self):
url = reverse('circuits-api:provider-list') url = reverse('circuits-api:provider-list')

View File

@ -15,7 +15,7 @@ from dcim.models import (
) )
from dcim import filters from dcim import filters
from extras.api.renderers import BINDZoneRenderer, FlatJSONRenderer from extras.api.renderers import BINDZoneRenderer, FlatJSONRenderer
from extras.api.serializers import GraphSerializer from extras.api.serializers import RenderedGraphSerializer
from extras.api.views import CustomFieldModelViewSet from extras.api.views import CustomFieldModelViewSet
from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
from utilities.api import ServiceUnavailable, WritableSerializerMixin from utilities.api import ServiceUnavailable, WritableSerializerMixin
@ -45,9 +45,12 @@ class SiteViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
@detail_route() @detail_route()
def graphs(self, request, pk=None): def graphs(self, request, pk=None):
"""
A convenience method for rendering graphs for a particular site.
"""
site = get_object_or_404(Site, pk=pk) site = get_object_or_404(Site, pk=pk)
queryset = Graph.objects.filter(type=GRAPH_TYPE_SITE) queryset = Graph.objects.filter(type=GRAPH_TYPE_SITE)
serializer = GraphSerializer(queryset, many=True, context={'graphed_object': site}) serializer = RenderedGraphSerializer(queryset, many=True, context={'graphed_object': site})
return Response(serializer.data) return Response(serializer.data)
@ -278,9 +281,12 @@ class InterfaceViewSet(WritableSerializerMixin, ModelViewSet):
@detail_route() @detail_route()
def graphs(self, request, pk=None): def graphs(self, request, pk=None):
"""
A convenience method for rendering graphs for a particular interface.
"""
interface = get_object_or_404(Interface, pk=pk) interface = get_object_or_404(Interface, pk=pk)
queryset = Graph.objects.filter(type=GRAPH_TYPE_INTERFACE) queryset = Graph.objects.filter(type=GRAPH_TYPE_INTERFACE)
serializer = GraphSerializer(queryset, many=True, context={'graphed_object': interface}) serializer = RenderedGraphSerializer(queryset, many=True, context={'graphed_object': interface})
return Response(serializer.data) return Response(serializer.data)

View File

@ -10,6 +10,7 @@ from dcim.models import (
Manufacturer, Module, Platform, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, Rack, RackGroup, Manufacturer, Module, Platform, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, Rack, RackGroup,
RackReservation, RackRole, Region, Site, SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT, RackReservation, RackRole, Region, Site, SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT,
) )
from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
from users.models import Token from users.models import Token
from utilities.tests import HttpStatusMixin from utilities.tests import HttpStatusMixin
@ -102,6 +103,27 @@ class SiteTest(HttpStatusMixin, APITestCase):
self.assertEqual(response.data['name'], self.site1.name) self.assertEqual(response.data['name'], self.site1.name)
def test_get_site_graphs(self):
self.graph1 = Graph.objects.create(
type=GRAPH_TYPE_SITE, name='Test Graph 1',
source='http://example.com/graphs.py?site={{ obj.slug }}&foo=1'
)
self.graph2 = Graph.objects.create(
type=GRAPH_TYPE_SITE, name='Test Graph 2',
source='http://example.com/graphs.py?site={{ obj.slug }}&foo=2'
)
self.graph3 = Graph.objects.create(
type=GRAPH_TYPE_SITE, name='Test Graph 3',
source='http://example.com/graphs.py?site={{ obj.slug }}&foo=3'
)
url = reverse('dcim-api:site-graphs', kwargs={'pk': self.site1.pk})
response = self.client.get(url, **self.header)
self.assertEqual(len(response.data), 3)
self.assertEqual(response.data[0]['embed_url'], 'http://example.com/graphs.py?site=test-site-1&foo=1')
def test_list_sites(self): def test_list_sites(self):
url = reverse('dcim-api:site-list') url = reverse('dcim-api:site-list')
@ -1655,6 +1677,27 @@ class InterfaceTest(HttpStatusMixin, APITestCase):
self.assertEqual(response.data['name'], self.interface1.name) self.assertEqual(response.data['name'], self.interface1.name)
def test_get_interface_graphs(self):
self.graph1 = Graph.objects.create(
type=GRAPH_TYPE_INTERFACE, name='Test Graph 1',
source='http://example.com/graphs.py?interface={{ obj.name }}&foo=1'
)
self.graph2 = Graph.objects.create(
type=GRAPH_TYPE_INTERFACE, name='Test Graph 2',
source='http://example.com/graphs.py?interface={{ obj.name }}&foo=2'
)
self.graph3 = Graph.objects.create(
type=GRAPH_TYPE_INTERFACE, name='Test Graph 3',
source='http://example.com/graphs.py?interface={{ obj.name }}&foo=3'
)
url = reverse('dcim-api:interface-graphs', kwargs={'pk': self.interface1.pk})
response = self.client.get(url, **self.header)
self.assertEqual(len(response.data), 3)
self.assertEqual(response.data[0]['embed_url'], 'http://example.com/graphs.py?interface=Test Interface 1&foo=1')
def test_list_interfaces(self): def test_list_interfaces(self):
url = reverse('dcim-api:interface-list') url = reverse('dcim-api:interface-list')

View File

@ -1,7 +1,7 @@
from rest_framework import serializers from rest_framework import serializers
from dcim.api.serializers import NestedSiteSerializer from dcim.api.serializers import NestedSiteSerializer
from extras.models import ACTION_CHOICES, Graph, TopologyMap, UserAction from extras.models import ACTION_CHOICES, Graph, GRAPH_TYPE_CHOICES, TopologyMap, UserAction
from users.api.serializers import NestedUserSerializer from users.api.serializers import NestedUserSerializer
from utilities.api import ChoiceFieldSerializer from utilities.api import ChoiceFieldSerializer
@ -11,12 +11,28 @@ from utilities.api import ChoiceFieldSerializer
# #
class GraphSerializer(serializers.ModelSerializer): class GraphSerializer(serializers.ModelSerializer):
embed_url = serializers.SerializerMethodField() type = ChoiceFieldSerializer(choices=GRAPH_TYPE_CHOICES)
embed_link = serializers.SerializerMethodField()
class Meta: class Meta:
model = Graph model = Graph
fields = ['name', 'embed_url', 'embed_link'] fields = ['id', 'type', 'weight', 'name', 'source', 'link']
class WritableGraphSerializer(serializers.ModelSerializer):
class Meta:
model = Graph
fields = ['id', 'type', 'weight', 'name', 'source', 'link']
class RenderedGraphSerializer(serializers.ModelSerializer):
embed_url = serializers.SerializerMethodField()
embed_link = serializers.SerializerMethodField()
type = ChoiceFieldSerializer(choices=GRAPH_TYPE_CHOICES)
class Meta:
model = Graph
fields = ['id', 'type', 'weight', 'name', 'embed_url', 'embed_link']
def get_embed_url(self, obj): def get_embed_url(self, obj):
return obj.embed_url(self.context['graphed_object']) return obj.embed_url(self.context['graphed_object'])

View File

@ -5,6 +5,9 @@ from . import views
router = routers.DefaultRouter() router = routers.DefaultRouter()
# Graphs
router.register(r'graphs', views.GraphViewSet)
# Topology maps # Topology maps
router.register(r'topology-maps', views.TopologyMapViewSet) router.register(r'topology-maps', views.TopologyMapViewSet)

View File

@ -6,7 +6,7 @@ from django.http import HttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from extras import filters from extras import filters
from extras.models import TopologyMap, UserAction from extras.models import Graph, TopologyMap, UserAction
from utilities.api import WritableSerializerMixin from utilities.api import WritableSerializerMixin
from . import serializers from . import serializers
@ -41,6 +41,13 @@ class CustomFieldModelViewSet(ModelViewSet):
return super(CustomFieldModelViewSet, self).get_queryset().prefetch_related('custom_field_values__field') return super(CustomFieldModelViewSet, self).get_queryset().prefetch_related('custom_field_values__field')
class GraphViewSet(WritableSerializerMixin, ModelViewSet):
queryset = Graph.objects.all()
serializer_class = serializers.GraphSerializer
write_serializer_class = serializers.WritableGraphSerializer
filter_class = filters.GraphFilter
class TopologyMapViewSet(WritableSerializerMixin, ModelViewSet): class TopologyMapViewSet(WritableSerializerMixin, ModelViewSet):
queryset = TopologyMap.objects.select_related('site') queryset = TopologyMap.objects.select_related('site')
serializer_class = serializers.TopologyMapSerializer serializer_class = serializers.TopologyMapSerializer

View File

@ -4,7 +4,7 @@ from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from dcim.models import Site from dcim.models import Site
from .models import CF_TYPE_SELECT, CustomField, TopologyMap, UserAction from .models import CF_TYPE_SELECT, CustomField, Graph, TopologyMap, UserAction
class CustomFieldFilter(django_filters.Filter): class CustomFieldFilter(django_filters.Filter):
@ -48,6 +48,13 @@ class CustomFieldFilterSet(django_filters.FilterSet):
self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(name=cf.name, cf_type=cf.type) self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(name=cf.name, cf_type=cf.type)
class GraphFilter(django_filters.FilterSet):
class Meta:
model = Graph
fields = ['type', 'name']
class TopologyMapFilter(django_filters.FilterSet): class TopologyMapFilter(django_filters.FilterSet):
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
name='site', name='site',

View File

@ -0,0 +1,86 @@
from rest_framework import status
from rest_framework.test import APITestCase
from django.contrib.auth.models import User
from django.urls import reverse
from extras.models import Graph, GRAPH_TYPE_SITE
from users.models import Token
from utilities.tests import HttpStatusMixin
class GraphTest(HttpStatusMixin, APITestCase):
def setUp(self):
user = User.objects.create(username='testuser', is_superuser=True)
token = Token.objects.create(user=user)
self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(token.key)}
self.graph1 = Graph.objects.create(
type=GRAPH_TYPE_SITE, name='Test Graph 1', source='http://example.com/graphs.py?site={{ obj.name }}&foo=1'
)
self.graph2 = Graph.objects.create(
type=GRAPH_TYPE_SITE, name='Test Graph 2', source='http://example.com/graphs.py?site={{ obj.name }}&foo=2'
)
self.graph3 = Graph.objects.create(
type=GRAPH_TYPE_SITE, name='Test Graph 3', source='http://example.com/graphs.py?site={{ obj.name }}&foo=3'
)
def test_get_graph(self):
url = reverse('extras-api:graph-detail', kwargs={'pk': self.graph1.pk})
response = self.client.get(url, **self.header)
self.assertEqual(response.data['name'], self.graph1.name)
def test_list_graphs(self):
url = reverse('extras-api:graph-list')
response = self.client.get(url, **self.header)
self.assertEqual(response.data['count'], 3)
def test_create_graph(self):
data = {
'type': GRAPH_TYPE_SITE,
'name': 'Test Graph 4',
'source': 'http://example.com/graphs.py?site={{ obj.name }}&foo=4',
}
url = reverse('extras-api:graph-list')
response = self.client.post(url, data, **self.header)
self.assertHttpStatus(response, status.HTTP_201_CREATED)
self.assertEqual(Graph.objects.count(), 4)
graph4 = Graph.objects.get(pk=response.data['id'])
self.assertEqual(graph4.type, data['type'])
self.assertEqual(graph4.name, data['name'])
self.assertEqual(graph4.source, data['source'])
def test_update_graph(self):
data = {
'type': GRAPH_TYPE_SITE,
'name': 'Test Graph X',
'source': 'http://example.com/graphs.py?site={{ obj.name }}&foo=99',
}
url = reverse('extras-api:graph-detail', kwargs={'pk': self.graph1.pk})
response = self.client.put(url, data, **self.header)
self.assertHttpStatus(response, status.HTTP_200_OK)
self.assertEqual(Graph.objects.count(), 3)
graph1 = Graph.objects.get(pk=response.data['id'])
self.assertEqual(graph1.type, data['type'])
self.assertEqual(graph1.name, data['name'])
self.assertEqual(graph1.source, data['source'])
def test_delete_graph(self):
url = reverse('extras-api:graph-detail', kwargs={'pk': self.graph1.pk})
response = self.client.delete(url, **self.header)
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
self.assertEqual(Graph.objects.count(), 2)