Added GraphQL backend using Django Graphene

This commit is contained in:
genomics-geek 2019-10-05 11:18:01 -04:00
parent ebfb742aef
commit f01e3bfd16
8 changed files with 161 additions and 6 deletions

View File

@ -85,6 +85,7 @@ THIRD_PARTY_APPS = [
{%- if cookiecutter.use_celery == 'y' -%}
"django_celery_beat",
{%- endif %}
"graphene_django",
]
LOCAL_APPS = [
@ -325,5 +326,47 @@ STATICFILES_FINDERS += ["compressor.finders.CompressorFinder"]
# https://github.com/ottoyiu/django-cors-headers#cors_origin_allow_all
CORS_ORIGIN_ALLOW_ALL = True
{%- endif %}
# DJANGO REST FRAMEWORK
# ------------------------------------------------------------------------------
# http://www.django-rest-framework.org/
REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES": (
"rest_framework.permissions.IsAuthenticated",
),
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.BasicAuthentication",
"oauth2_provider.contrib.rest_framework.OAuth2Authentication",
"rest_framework_jwt.authentication.JSONWebTokenAuthentication",
),
"DEFAULT_RENDERER_CLASSES": (
"rest_framework.renderers.JSONRenderer",
"rest_framework.renderers.BrowsableAPIRenderer",
"rest_framework.renderers.AdminRenderer",
),
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
"PAGE_SIZE": 100,
"COERCE_DECIMAL_TO_STRING": False,
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
# NOTE: See: https://www.django-rest-framework.org/community/3.10-announcement/#continuing-to-use-coreapi
"DEFAULT_SCHEMA_CLASS": "rest_framework.schemas.coreapi.AutoSchema",
}
# Graphene Setup
# ------------------------------------------------------------------------------
# See: http://docs.graphene-python.org/projects/django/en/latest/tutorial-plain/#update-settings
GRAPHENE = {
"SCHEMA": "{{ cookiecutter.project_slug }}.graphql.schema.schema",
'SCHEMA_OUTPUT': 'frontend/src/apollo/schema.json',
'SCHEMA_INDENT': 2,
"MIDDLEWARE": [
"graphene_django.debug.DjangoDebugMiddleware",
]
}
# NOTE: As Graphene schema gets larger, it needs more room to run the recursive graphql queries
# See: https://github.com/graphql-python/graphene/issues/663
GRAPHENE_RECURSION_LIMIT = env.int("GRAPHENE_RECURSION_LIMIT", default=3500)
# Your stuff...
# ------------------------------------------------------------------------------

View File

@ -2,9 +2,12 @@ from django.conf import settings
from django.urls import include, path, re_path
from django.conf.urls.static import static
from django.contrib import admin
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import TemplateView
from django.views import defaults as default_views
from graphene_file_upload.django import FileUploadGraphQLView
from rest_framework.documentation import include_docs_urls
from rest_framework.routers import DefaultRouter
@ -14,16 +17,22 @@ router = DefaultRouter(trailing_slash=False)
urlpatterns = [
path("", TemplateView.as_view(template_name="pages/home.html"), name="home"),
re_path(r'^app/(?P<route>.*)$', TemplateView.as_view(template_name="index.html"), name='app'),
# APIs
path("api/", include(router.urls)),
path(
"about/", TemplateView.as_view(template_name="pages/about.html"), name="about"
),
# Django Admin, use {% raw %}{% url 'admin:index' %}{% endraw %}
path(settings.ADMIN_URL, admin.site.urls),
# User management
path("api-docs/", include_docs_urls(title="{{ cookiecutter.project_name }} REST API", public=False)),
path("graphql/", csrf_exempt(FileUploadGraphQLView.as_view(graphiql=True, pretty=True))),
# User management from django-all-auth
path("about/", TemplateView.as_view(template_name="pages/about.html"), name="about"),
path("users/", include("{{ cookiecutter.project_slug }}.users.urls", namespace="users")),
path("accounts/", include("allauth.urls")),
# Django Admin, use {% raw %}{% url 'admin:index' %}{% endraw %}
path(settings.ADMIN_URL, admin.site.urls),
# Your stuff: custom urls includes go here
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if settings.DEBUG:

View File

@ -29,6 +29,11 @@ django-compressor==2.3 # https://github.com/django-compressor/django-compressor
{%- endif %}
django-redis==4.10.0 # https://github.com/niwinz/django-redis
# GraphQL
graphene-django==2.6.0 # http://docs.graphene-python.org/projects/django/en/latest/
graphene-django-optimizer==0.6.0 # https://github.com/tfoxy/graphene-django-optimizer
graphene-file-upload==1.2.2 # https://github.com/lmcgartland/graphene-file-upload
# Django REST Framework
djangorestframework==3.10.3 # https://github.com/encode/django-rest-framework
coreapi==2.3.3 # https://github.com/core-api/python-client

View File

@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
from django.contrib.postgres.fields import ArrayField, JSONField
from django.db.models import FileField
from django.forms import Field
from django_filters import Filter
from graphene import Float, Int, JSONString, List, String
from graphene_django.converter import convert_django_field
from graphene_django.forms.converter import convert_form_field
# NOTE: This needs to be done before importing from queries
# SEE: https://github.com/graphql-python/graphene-django/issues/18
@convert_django_field.register(ArrayField)
def convert_array_to_list(field, registry=None):
return List(of_type=String, description=field.help_text, required=not field.null)
@convert_django_field.register(JSONField)
def convert_jsonb_to_string(field, registry=None):
return JSONString(description=field.help_text, required=not field.null)
@convert_django_field.register(FileField)
def convert_file_to_string(field, registry=None):
return String(description=field.help_text, required=not field.null)
def generate_list_filter_class(inner_type):
"""
Returns a Filter class that will resolve into a List(`inner_type`) graphene
type.
This allows us to do things like use `__in` filters that accept graphene
lists instead of a comma delimited value string that's interpolated into
a list by django_filters.BaseCSVFilter (which is used to define
django_filters.BaseInFilter)
"""
form_field = type(
"List{}FormField".format(inner_type.__name__),
(Field,),
{},
)
filter_class = type(
"{}ListFilter".format(inner_type.__name__),
(Filter,),
{
"field_class": form_field,
"__doc__": (
"{0}ListFilter is a small extension of a raw django_filters.Filter "
"that allows us to express graphql List({0}) arguments using FilterSets."
"Note that the given values are passed directly into queryset filters."
).format(inner_type.__name__),
},
)
convert_form_field.register(form_field)(
lambda x: List(inner_type, required=x.required)
)
return filter_class
FloatListFilter = generate_list_filter_class(Float)
IntListFilter = generate_list_filter_class(Int)
StringListFilter = generate_list_filter_class(String)

View File

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from graphene import Field, ObjectType
from graphene_django.debug import DjangoDebug
class Mutation(ObjectType):
debug = Field(DjangoDebug, name='__debug')

View File

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from graphene import Field, ObjectType
from graphene_django.debug import DjangoDebug
class Query(ObjectType):
debug = Field(DjangoDebug, name='__debug')

View File

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
import sys
from django.conf import settings
from graphene import Schema
# NOTE: Conversions need to happen before importing from queries/mutations
from . import conversions # NOQA
from . import mutation, query
# NOTE: As Graphene schema gets larger, it needs more room to run the recursive graphql queries
# See: https://github.com/graphql-python/graphene/issues/663
sys.setrecursionlimit(settings.GRAPHENE_RECURSION_LIMIT)
schema = Schema(query=query.Query, mutation=mutation.Mutation)