mirror of
				https://github.com/netbox-community/netbox.git
				synced 2024-05-10 07:54:54 +00:00 
			
		
		
		
	Added API endpoint, tests for Graphs
This commit is contained in:
		@@ -7,7 +7,7 @@ from rest_framework.viewsets import ModelViewSet
 | 
			
		||||
from circuits import filters
 | 
			
		||||
from circuits.models import Provider, CircuitTermination, CircuitType, Circuit
 | 
			
		||||
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 utilities.api import WritableSerializerMixin
 | 
			
		||||
from . import serializers
 | 
			
		||||
@@ -25,9 +25,12 @@ class ProviderViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
 | 
			
		||||
 | 
			
		||||
    @detail_route()
 | 
			
		||||
    def graphs(self, request, pk=None):
 | 
			
		||||
        """
 | 
			
		||||
        A convenience method for rendering graphs for a particular provider.
 | 
			
		||||
        """
 | 
			
		||||
        provider = get_object_or_404(Provider, pk=pk)
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ from django.contrib.auth.models import User
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
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 users.models import Token
 | 
			
		||||
from utilities.tests import HttpStatusMixin
 | 
			
		||||
@@ -29,6 +30,27 @@ class ProviderTest(HttpStatusMixin, APITestCase):
 | 
			
		||||
 | 
			
		||||
        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):
 | 
			
		||||
 | 
			
		||||
        url = reverse('circuits-api:provider-list')
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ from dcim.models import (
 | 
			
		||||
)
 | 
			
		||||
from dcim import filters
 | 
			
		||||
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.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
 | 
			
		||||
from utilities.api import ServiceUnavailable, WritableSerializerMixin
 | 
			
		||||
@@ -45,9 +45,12 @@ class SiteViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
 | 
			
		||||
 | 
			
		||||
    @detail_route()
 | 
			
		||||
    def graphs(self, request, pk=None):
 | 
			
		||||
        """
 | 
			
		||||
        A convenience method for rendering graphs for a particular site.
 | 
			
		||||
        """
 | 
			
		||||
        site = get_object_or_404(Site, pk=pk)
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -278,9 +281,12 @@ class InterfaceViewSet(WritableSerializerMixin, ModelViewSet):
 | 
			
		||||
 | 
			
		||||
    @detail_route()
 | 
			
		||||
    def graphs(self, request, pk=None):
 | 
			
		||||
        """
 | 
			
		||||
        A convenience method for rendering graphs for a particular interface.
 | 
			
		||||
        """
 | 
			
		||||
        interface = get_object_or_404(Interface, pk=pk)
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ from dcim.models import (
 | 
			
		||||
    Manufacturer, Module, Platform, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, Rack, RackGroup,
 | 
			
		||||
    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 utilities.tests import HttpStatusMixin
 | 
			
		||||
 | 
			
		||||
@@ -102,6 +103,27 @@ class SiteTest(HttpStatusMixin, APITestCase):
 | 
			
		||||
 | 
			
		||||
        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):
 | 
			
		||||
 | 
			
		||||
        url = reverse('dcim-api:site-list')
 | 
			
		||||
@@ -1655,6 +1677,27 @@ class InterfaceTest(HttpStatusMixin, APITestCase):
 | 
			
		||||
 | 
			
		||||
        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):
 | 
			
		||||
 | 
			
		||||
        url = reverse('dcim-api:interface-list')
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
from rest_framework import serializers
 | 
			
		||||
 | 
			
		||||
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 utilities.api import ChoiceFieldSerializer
 | 
			
		||||
 | 
			
		||||
@@ -11,12 +11,28 @@ from utilities.api import ChoiceFieldSerializer
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
class GraphSerializer(serializers.ModelSerializer):
 | 
			
		||||
    embed_url = serializers.SerializerMethodField()
 | 
			
		||||
    embed_link = serializers.SerializerMethodField()
 | 
			
		||||
    type = ChoiceFieldSerializer(choices=GRAPH_TYPE_CHOICES)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        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):
 | 
			
		||||
        return obj.embed_url(self.context['graphed_object'])
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,9 @@ from . import views
 | 
			
		||||
 | 
			
		||||
router = routers.DefaultRouter()
 | 
			
		||||
 | 
			
		||||
# Graphs
 | 
			
		||||
router.register(r'graphs', views.GraphViewSet)
 | 
			
		||||
 | 
			
		||||
# Topology maps
 | 
			
		||||
router.register(r'topology-maps', views.TopologyMapViewSet)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ from django.http import HttpResponse
 | 
			
		||||
from django.shortcuts import get_object_or_404
 | 
			
		||||
 | 
			
		||||
from extras import filters
 | 
			
		||||
from extras.models import TopologyMap, UserAction
 | 
			
		||||
from extras.models import Graph, TopologyMap, UserAction
 | 
			
		||||
from utilities.api import WritableSerializerMixin
 | 
			
		||||
from . import serializers
 | 
			
		||||
 | 
			
		||||
@@ -41,6 +41,13 @@ class CustomFieldModelViewSet(ModelViewSet):
 | 
			
		||||
        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):
 | 
			
		||||
    queryset = TopologyMap.objects.select_related('site')
 | 
			
		||||
    serializer_class = serializers.TopologyMapSerializer
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ from django.contrib.auth.models import User
 | 
			
		||||
from django.contrib.contenttypes.models import ContentType
 | 
			
		||||
 | 
			
		||||
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):
 | 
			
		||||
@@ -48,6 +48,13 @@ class CustomFieldFilterSet(django_filters.FilterSet):
 | 
			
		||||
            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):
 | 
			
		||||
    site_id = django_filters.ModelMultipleChoiceFilter(
 | 
			
		||||
        name='site',
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										86
									
								
								netbox/extras/tests/test_api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								netbox/extras/tests/test_api.py
									
									
									
									
									
										Normal 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)
 | 
			
		||||
		Reference in New Issue
	
	Block a user