Repository: strawberry-graphql/strawberry

Issue:
Add Django Support
I'd love to see strawberry support Django as a first class citizen via a contrib package. To start, it could be as simple as adding a Django view that exposes a GraphQL endpoint and the GraphQL Playground. From there, a lot of other neat features could be added, such as:

- Django authentication integration
- Out of the box subscription support using Django channels
- Automatic type generation from Django models
- Automatic CRUD query / mutation / subscription generation for Django models

Is this something that strawberry would be interested in supporting?


Discussion:
Yes totally! I'd love to see this happen (I'd love to use strawberry for some Django side project I have).

I'm currently focused on finishing ASGI support (mainly adding tests and a better structure of the code), would you like to work on this? :)
I'd love to work on this! I'll throw up some PRs in the next couple of days :)

Resolving patch:
diff --git a/strawberry/contrib/django/__init__.py b/strawberry/contrib/django/__init__.py
new file mode 100644
diff --git a/strawberry/contrib/django/apps.py b/strawberry/contrib/django/apps.py
new file mode 100644
--- /dev/null
+++ b/strawberry/contrib/django/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class StrawberryConfig(AppConfig):
+    name = "strawberry"
diff --git a/strawberry/contrib/django/views.py b/strawberry/contrib/django/views.py
new file mode 100644
--- /dev/null
+++ b/strawberry/contrib/django/views.py
@@ -0,0 +1,67 @@
+import json
+
+from django.http import HttpResponseNotAllowed, JsonResponse
+from django.http.response import HttpResponseBadRequest
+from django.shortcuts import render
+from django.utils.decorators import method_decorator
+from django.views.decorators.csrf import csrf_exempt
+from django.views.generic import View
+
+from graphql import graphql_sync
+from graphql.error import format_error as format_graphql_error
+from graphql.type.schema import GraphQLSchema
+
+
+class GraphQLView(View):
+    schema = None
+
+    def __init__(self, schema=None):
+        if not schema:
+            raise ValueError("You must pass in a schema to GraphQLView")
+
+        if not isinstance(schema, GraphQLSchema):
+            raise ValueError("You must pass in a valid schema to GraphQLView")
+
+        self.schema = schema
+
+    @method_decorator(csrf_exempt)
+    def dispatch(self, request, *args, **kwargs):
+        if request.method.lower() not in ("get", "post"):
+            return HttpResponseNotAllowed(
+                ["GET", "POST"], "GraphQL only supports GET and POST requests."
+            )
+
+        if "text/html" in request.META.get("HTTP_ACCEPT", ""):
+            return render(
+                request,
+                "graphql/playground.html",
+                {"REQUEST_PATH": request.get_full_path()},
+            )
+
+        data = json.loads(request.body)
+
+        try:
+            query = data["query"]
+            variables = data.get("variables")
+            operation_name = data.get("operationName")
+        except KeyError:
+            return HttpResponseBadRequest("No GraphQL query found in the request")
+
+        context = {"request": request}
+
+        result = graphql_sync(
+            self.schema,
+            query,
+            variable_values=variables,
+            context_value=context,
+            operation_name=operation_name,
+        )
+
+        response_data = {"data": result.data}
+
+        if result.errors:
+            response_data["errors"] = [
+                format_graphql_error(err) for err in result.errors
+            ]
+
+        return JsonResponse(response_data, status=400 if result.errors else 200)


Test patch:
diff --git a/strawberry/contrib/django/tests/django_settings.py b/strawberry/contrib/django/tests/django_settings.py
new file mode 100644
--- /dev/null
+++ b/strawberry/contrib/django/tests/django_settings.py
@@ -0,0 +1,15 @@
+SECRET_KEY = 1
+
+INSTALLED_APPS = ["strawberry.contrib.django", "strawberry.contrib.django.tests"]
+
+TEMPLATES = [
+    {
+        "BACKEND": "django.template.backends.django.DjangoTemplates",
+        "DIRS": [],
+        "APP_DIRS": True,
+    }
+]
+
+DATABASES = {
+    "default": {"ENGINE": "django.db.backends.sqlite3", "NAME": "tests/django.sqlite"}
+}
diff --git a/strawberry/contrib/django/tests/models.py b/strawberry/contrib/django/tests/models.py
new file mode 100644
diff --git a/strawberry/contrib/django/tests/test_views.py b/strawberry/contrib/django/tests/test_views.py
new file mode 100644
--- /dev/null
+++ b/strawberry/contrib/django/tests/test_views.py
@@ -0,0 +1,60 @@
+import json
+
+from django.test.client import RequestFactory
+
+import strawberry
+from strawberry.contrib.django.views import GraphQLView
+
+
+@strawberry.type
+class Query:
+    hello: str = "strawberry"
+
+    @strawberry.field
+    async def hello_async(root, info) -> str:
+        return "async strawberry"
+
+
+schema = strawberry.Schema(query=Query)
+
+
+def test_playground_view():
+    factory = RequestFactory()
+
+    request = factory.get("/graphql/", HTTP_ACCEPT="text/html")
+
+    response = GraphQLView.as_view(schema=schema)(request)
+    body = response.content.decode()
+
+    assert "GraphQL Playground" in body
+    assert f'endpoint: "{request.get_full_path()}"' in body
+
+
+def test_graphql_query():
+
+    query = "{ hello }"
+
+    factory = RequestFactory()
+    request = factory.post(
+        "/graphql/", {"query": query}, content_type="application/json"
+    )
+
+    response = GraphQLView.as_view(schema=schema)(request)
+    data = json.loads(response.content.decode())
+
+    assert data["data"]["hello"] == "strawberry"
+
+
+def test_async_graphql_query():
+
+    query = "{ hello_async }"
+
+    factory = RequestFactory()
+    request = factory.post(
+        "/graphql/", {"query": query}, content_type="application/json"
+    )
+
+    response = GraphQLView.as_view(schema=schema)(request)
+    data = json.loads(response.content.decode())
+
+    assert data["data"]["hello"] == "async strawberry"