1
0
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:
jeremystretch
2021-06-08 13:52:39 -04:00
parent 442b3fcc48
commit 6a07f66cfc
12 changed files with 249 additions and 1 deletions

View File

@ -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

View 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)

View 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

View 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)

View 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

View 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)

View 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()

View 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

View File

@ -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',

View File

@ -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'),

Submodule netbox/project-static/volt added at 942aa8c7bd

View File

@ -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