mirror of
https://github.com/cookiecutter/cookiecutter-django.git
synced 2025-08-14 08:54:52 +03:00
Added GraphQL backend using Django Graphene
This commit is contained in:
parent
ebfb742aef
commit
f01e3bfd16
|
@ -85,6 +85,7 @@ THIRD_PARTY_APPS = [
|
||||||
{%- if cookiecutter.use_celery == 'y' -%}
|
{%- if cookiecutter.use_celery == 'y' -%}
|
||||||
"django_celery_beat",
|
"django_celery_beat",
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
"graphene_django",
|
||||||
]
|
]
|
||||||
|
|
||||||
LOCAL_APPS = [
|
LOCAL_APPS = [
|
||||||
|
@ -325,5 +326,47 @@ STATICFILES_FINDERS += ["compressor.finders.CompressorFinder"]
|
||||||
# https://github.com/ottoyiu/django-cors-headers#cors_origin_allow_all
|
# https://github.com/ottoyiu/django-cors-headers#cors_origin_allow_all
|
||||||
CORS_ORIGIN_ALLOW_ALL = True
|
CORS_ORIGIN_ALLOW_ALL = True
|
||||||
{%- endif %}
|
{%- 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...
|
# Your stuff...
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -2,9 +2,12 @@ from django.conf import settings
|
||||||
from django.urls import include, path, re_path
|
from django.urls import include, path, re_path
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
from django.views import defaults as default_views
|
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
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,16 +17,22 @@ router = DefaultRouter(trailing_slash=False)
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", TemplateView.as_view(template_name="pages/home.html"), name="home"),
|
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'),
|
re_path(r'^app/(?P<route>.*)$', TemplateView.as_view(template_name="index.html"), name='app'),
|
||||||
|
|
||||||
|
# APIs
|
||||||
path("api/", include(router.urls)),
|
path("api/", include(router.urls)),
|
||||||
path(
|
path("api-docs/", include_docs_urls(title="{{ cookiecutter.project_name }} REST API", public=False)),
|
||||||
"about/", TemplateView.as_view(template_name="pages/about.html"), name="about"
|
path("graphql/", csrf_exempt(FileUploadGraphQLView.as_view(graphiql=True, pretty=True))),
|
||||||
),
|
|
||||||
# Django Admin, use {% raw %}{% url 'admin:index' %}{% endraw %}
|
# User management from django-all-auth
|
||||||
path(settings.ADMIN_URL, admin.site.urls),
|
path("about/", TemplateView.as_view(template_name="pages/about.html"), name="about"),
|
||||||
# User management
|
|
||||||
path("users/", include("{{ cookiecutter.project_slug }}.users.urls", namespace="users")),
|
path("users/", include("{{ cookiecutter.project_slug }}.users.urls", namespace="users")),
|
||||||
path("accounts/", include("allauth.urls")),
|
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
|
# Your stuff: custom urls includes go here
|
||||||
|
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
|
|
@ -29,6 +29,11 @@ django-compressor==2.3 # https://github.com/django-compressor/django-compressor
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
django-redis==4.10.0 # https://github.com/niwinz/django-redis
|
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
|
# Django REST Framework
|
||||||
djangorestframework==3.10.3 # https://github.com/encode/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
|
coreapi==2.3.3 # https://github.com/core-api/python-client
|
||||||
|
|
|
@ -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)
|
|
@ -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')
|
|
@ -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')
|
|
@ -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)
|
Loading…
Reference in New Issue
Block a user