mirror of
				https://github.com/netbox-community/netbox.git
				synced 2024-05-10 07:54:54 +00:00 
			
		
		
		
	Initial work on GraphQL
This commit is contained in:
		@@ -18,6 +18,10 @@ django-debug-toolbar
 | 
			
		||||
# https://github.com/carltongibson/django-filter
 | 
			
		||||
django-filter
 | 
			
		||||
 | 
			
		||||
# Django debug toolbar extension with support for GraphiQL
 | 
			
		||||
# https://github.com/flavors/django-graphiql-debug-toolbar/
 | 
			
		||||
django-graphiql-debug-toolbar
 | 
			
		||||
 | 
			
		||||
# Modified Preorder Tree Traversal (recursive nesting of objects)
 | 
			
		||||
# https://github.com/django-mptt/django-mptt
 | 
			
		||||
django-mptt
 | 
			
		||||
@@ -54,6 +58,10 @@ djangorestframework
 | 
			
		||||
# https://github.com/axnsan12/drf-yasg
 | 
			
		||||
drf-yasg[validation]
 | 
			
		||||
 | 
			
		||||
# Django wrapper for Graphene (GraphQL support)
 | 
			
		||||
# https://github.com/graphql-python/graphene-django
 | 
			
		||||
graphene_django
 | 
			
		||||
 | 
			
		||||
# WSGI HTTP server
 | 
			
		||||
# https://gunicorn.org/
 | 
			
		||||
gunicorn
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								netbox/circuits/graphql/schema.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								netbox/circuits/graphql/schema.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
import graphene
 | 
			
		||||
 | 
			
		||||
from netbox.graphql.fields import ObjectField, ObjectListField
 | 
			
		||||
from .types import *
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CircuitsQuery(graphene.ObjectType):
 | 
			
		||||
    circuit = ObjectField(CircuitType)
 | 
			
		||||
    circuits = ObjectListField(CircuitType)
 | 
			
		||||
 | 
			
		||||
    circuit_termination = ObjectField(CircuitTerminationType)
 | 
			
		||||
    circuit_terminations = ObjectListField(CircuitTerminationType)
 | 
			
		||||
 | 
			
		||||
    circuit_type = ObjectField(CircuitTypeType)
 | 
			
		||||
    circuit_types = ObjectListField(CircuitTypeType)
 | 
			
		||||
 | 
			
		||||
    provider = ObjectField(ProviderType)
 | 
			
		||||
    providers = ObjectListField(ProviderType)
 | 
			
		||||
 | 
			
		||||
    provider_network = ObjectField(ProviderNetworkType)
 | 
			
		||||
    provider_networks = ObjectListField(ProviderNetworkType)
 | 
			
		||||
							
								
								
									
										54
									
								
								netbox/circuits/graphql/types.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								netbox/circuits/graphql/types.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
from circuits import filtersets, models
 | 
			
		||||
from netbox.graphql.types import *
 | 
			
		||||
 | 
			
		||||
__all__ = (
 | 
			
		||||
    'CircuitType',
 | 
			
		||||
    'CircuitTerminationType',
 | 
			
		||||
    'CircuitTypeType',
 | 
			
		||||
    'ProviderType',
 | 
			
		||||
    'ProviderNetworkType',
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Object types
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
class ProviderType(TaggedObjectType):
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = models.Provider
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
        filterset_class = filtersets.ProviderFilterSet
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProviderNetworkType(TaggedObjectType):
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = models.ProviderNetwork
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
        filterset_class = filtersets.ProviderNetworkFilterSet
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CircuitType(TaggedObjectType):
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = models.Circuit
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
        filterset_class = filtersets.CircuitFilterSet
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CircuitTypeType(ObjectType):
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = models.CircuitType
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
        filterset_class = filtersets.CircuitTypeFilterSet
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CircuitTerminationType(BaseObjectType):
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = models.CircuitTermination
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
        filterset_class = filtersets.CircuitTerminationFilterSet
 | 
			
		||||
							
								
								
									
										11
									
								
								netbox/netbox/graphql/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								netbox/netbox/graphql/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
import graphene
 | 
			
		||||
from graphene_django.converter import convert_django_field
 | 
			
		||||
from taggit.managers import TaggableManager
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@convert_django_field.register(TaggableManager)
 | 
			
		||||
def convert_field_to_tags_list(field, registry=None):
 | 
			
		||||
    """
 | 
			
		||||
    Register conversion handler for django-taggit's TaggableManager
 | 
			
		||||
    """
 | 
			
		||||
    return graphene.List(graphene.String)
 | 
			
		||||
							
								
								
									
										65
									
								
								netbox/netbox/graphql/fields.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								netbox/netbox/graphql/fields.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
from functools import partial
 | 
			
		||||
 | 
			
		||||
import graphene
 | 
			
		||||
from graphene_django import DjangoListField
 | 
			
		||||
 | 
			
		||||
from .utils import get_graphene_type
 | 
			
		||||
 | 
			
		||||
__all__ = (
 | 
			
		||||
    'ObjectField',
 | 
			
		||||
    'ObjectListField',
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ObjectField(graphene.Field):
 | 
			
		||||
    """
 | 
			
		||||
    Retrieve a single object, identified by its numeric ID.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
 | 
			
		||||
        if 'id' not in kwargs:
 | 
			
		||||
            kwargs['id'] = graphene.Int(required=True)
 | 
			
		||||
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def object_resolver(django_object_type, root, info, **args):
 | 
			
		||||
        """
 | 
			
		||||
        Return an object given its numeric ID.
 | 
			
		||||
        """
 | 
			
		||||
        manager = django_object_type._meta.model._default_manager
 | 
			
		||||
        queryset = django_object_type.get_queryset(manager, info)
 | 
			
		||||
 | 
			
		||||
        return queryset.get(**args)
 | 
			
		||||
 | 
			
		||||
    def get_resolver(self, parent_resolver):
 | 
			
		||||
        return partial(self.object_resolver, self._type)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ObjectListField(DjangoListField):
 | 
			
		||||
    """
 | 
			
		||||
    Retrieve a list of objects, optionally filtered by one or more FilterSet filters.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, _type, *args, **kwargs):
 | 
			
		||||
 | 
			
		||||
        assert hasattr(_type._meta, 'filterset_class'), "DjangoFilterListField must define filterset_class under Meta"
 | 
			
		||||
        filterset_class = _type._meta.filterset_class
 | 
			
		||||
 | 
			
		||||
        # Get FilterSet kwargs
 | 
			
		||||
        filter_kwargs = {}
 | 
			
		||||
        for filter_name, filter_field in filterset_class.get_filters().items():
 | 
			
		||||
            field_type = get_graphene_type(type(filter_field))
 | 
			
		||||
            filter_kwargs[filter_name] = graphene.Argument(field_type)
 | 
			
		||||
 | 
			
		||||
        super().__init__(_type, args=filter_kwargs, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def list_resolver(django_object_type, resolver, default_manager, root, info, **args):
 | 
			
		||||
        # Get the QuerySet from the object type
 | 
			
		||||
        queryset = django_object_type.get_queryset(default_manager, info)
 | 
			
		||||
 | 
			
		||||
        # Instantiate and apply the FilterSet
 | 
			
		||||
        filterset_class = django_object_type._meta.filterset_class
 | 
			
		||||
        filterset = filterset_class(data=args, queryset=queryset, request=info.context)
 | 
			
		||||
 | 
			
		||||
        return filterset.qs
 | 
			
		||||
							
								
								
									
										13
									
								
								netbox/netbox/graphql/schema.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								netbox/netbox/graphql/schema.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import graphene
 | 
			
		||||
 | 
			
		||||
from circuits.graphql.schema import CircuitsQuery
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Query(
 | 
			
		||||
    CircuitsQuery,
 | 
			
		||||
    graphene.ObjectType
 | 
			
		||||
):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
schema = graphene.Schema(query=Query, auto_camelcase=False)
 | 
			
		||||
							
								
								
									
										41
									
								
								netbox/netbox/graphql/types.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								netbox/netbox/graphql/types.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
import graphene
 | 
			
		||||
from graphene_django import DjangoObjectType
 | 
			
		||||
 | 
			
		||||
__all__ = (
 | 
			
		||||
    'BaseObjectType',
 | 
			
		||||
    'ObjectType',
 | 
			
		||||
    'TaggedObjectType',
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseObjectType(DjangoObjectType):
 | 
			
		||||
    """
 | 
			
		||||
    Base GraphQL object type for all NetBox objects
 | 
			
		||||
    """
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = True
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_queryset(cls, queryset, info):
 | 
			
		||||
        # Enforce object permissions on the queryset
 | 
			
		||||
        return queryset.restrict(info.context.user, 'view')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ObjectType(BaseObjectType):
 | 
			
		||||
    # TODO: Custom fields support
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TaggedObjectType(ObjectType):
 | 
			
		||||
    """
 | 
			
		||||
    Extends ObjectType with support for Tags
 | 
			
		||||
    """
 | 
			
		||||
    tags = graphene.List(graphene.String)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = True
 | 
			
		||||
 | 
			
		||||
    def resolve_tags(self, info):
 | 
			
		||||
        return self.tags.all()
 | 
			
		||||
							
								
								
									
										25
									
								
								netbox/netbox/graphql/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								netbox/netbox/graphql/utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
import graphene
 | 
			
		||||
from django_filters import filters
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_graphene_type(filter_cls):
 | 
			
		||||
    """
 | 
			
		||||
    Return the appropriate Graphene scalar type for a django_filters Filter
 | 
			
		||||
    """
 | 
			
		||||
    if issubclass(filter_cls, filters.BooleanFilter):
 | 
			
		||||
        field_type = graphene.Boolean
 | 
			
		||||
    elif issubclass(filter_cls, filters.NumberFilter):
 | 
			
		||||
        # TODO: Floats? BigInts?
 | 
			
		||||
        field_type = graphene.Int
 | 
			
		||||
    elif issubclass(filter_cls, filters.DateFilter):
 | 
			
		||||
        field_type = graphene.Date
 | 
			
		||||
    elif issubclass(filter_cls, filters.DateTimeFilter):
 | 
			
		||||
        field_type = graphene.DateTime
 | 
			
		||||
    else:
 | 
			
		||||
        field_type = graphene.String
 | 
			
		||||
 | 
			
		||||
    # Multi-value filters should be handled as lists
 | 
			
		||||
    if issubclass(filter_cls, filters.MultipleChoiceFilter):
 | 
			
		||||
        return graphene.List(field_type)
 | 
			
		||||
 | 
			
		||||
    return field_type
 | 
			
		||||
@@ -282,9 +282,11 @@ INSTALLED_APPS = [
 | 
			
		||||
    'cacheops',
 | 
			
		||||
    'corsheaders',
 | 
			
		||||
    'debug_toolbar',
 | 
			
		||||
    'graphiql_debug_toolbar',
 | 
			
		||||
    'django_filters',
 | 
			
		||||
    'django_tables2',
 | 
			
		||||
    'django_prometheus',
 | 
			
		||||
    'graphene_django',
 | 
			
		||||
    'mptt',
 | 
			
		||||
    'rest_framework',
 | 
			
		||||
    'taggit',
 | 
			
		||||
@@ -303,7 +305,7 @@ INSTALLED_APPS = [
 | 
			
		||||
 | 
			
		||||
# Middleware
 | 
			
		||||
MIDDLEWARE = [
 | 
			
		||||
    'debug_toolbar.middleware.DebugToolbarMiddleware',
 | 
			
		||||
    'graphiql_debug_toolbar.middleware.DebugToolbarMiddleware',
 | 
			
		||||
    'django_prometheus.middleware.PrometheusBeforeMiddleware',
 | 
			
		||||
    'corsheaders.middleware.CorsMiddleware',
 | 
			
		||||
    'django.contrib.sessions.middleware.SessionMiddleware',
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,11 @@ from django.urls import path, re_path
 | 
			
		||||
from django.views.static import serve
 | 
			
		||||
from drf_yasg import openapi
 | 
			
		||||
from drf_yasg.views import get_schema_view
 | 
			
		||||
from graphene_django.views import GraphQLView
 | 
			
		||||
 | 
			
		||||
from extras.plugins.urls import plugin_admin_patterns, plugin_patterns, plugin_api_patterns
 | 
			
		||||
from netbox.api.views import APIRootView, StatusView
 | 
			
		||||
from netbox.graphql.schema import schema
 | 
			
		||||
from netbox.views import HomeView, StaticMediaFailureView, SearchView
 | 
			
		||||
from users.views import LoginView, LogoutView
 | 
			
		||||
from .admin import admin_site
 | 
			
		||||
@@ -60,6 +62,9 @@ _patterns = [
 | 
			
		||||
    path('api/redoc/', schema_view.with_ui('redoc'), name='api_redocs'),
 | 
			
		||||
    re_path(r'^api/swagger(?P<format>.json|.yaml)$', schema_view.without_ui(), name='schema_swagger'),
 | 
			
		||||
 | 
			
		||||
    # GraphQL
 | 
			
		||||
    path('graphql/', GraphQLView.as_view(graphiql=True, schema=schema)),
 | 
			
		||||
 | 
			
		||||
    # Serving static media in Django to pipe it through LoginRequiredMiddleware
 | 
			
		||||
    path('media/<path:path>', serve, {'document_root': settings.MEDIA_ROOT}),
 | 
			
		||||
    path('media-failure/', StaticMediaFailureView.as_view(), name='media_failure'),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								netbox/project-static/volt
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								netbox/project-static/volt
									
									
									
									
									
										Submodule
									
								
							 Submodule netbox/project-static/volt added at 942aa8c7bd
									
								
							@@ -3,6 +3,7 @@ django-cacheops==6.0
 | 
			
		||||
django-cors-headers==3.7.0
 | 
			
		||||
django-debug-toolbar==3.2.1
 | 
			
		||||
django-filter==2.4.0
 | 
			
		||||
django-graphiql-debug-toolbar==0.1.4
 | 
			
		||||
django-mptt==0.12.0
 | 
			
		||||
django-pglocks==1.0.4
 | 
			
		||||
django-prometheus==2.1.0
 | 
			
		||||
@@ -12,6 +13,7 @@ django-taggit==1.4.0
 | 
			
		||||
django-timezone-field==4.1.2
 | 
			
		||||
djangorestframework==3.12.4
 | 
			
		||||
drf-yasg[validation]==1.20.0
 | 
			
		||||
graphene_django==2.15.0
 | 
			
		||||
gunicorn==20.1.0
 | 
			
		||||
Jinja2==3.0.1
 | 
			
		||||
Markdown==3.3.4
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user