Repository: graphql-python/graphene-django

Issue:
Add GraphiQL Explorer
Reference from https://github.com/strawberry-graphql/strawberry/blob/main/strawberry/static/graphiql.html
Queries and mutations work normally, subscriptions are not tested

![image](https://user-images.githubusercontent.com/42126168/119257134-8739e780-bbf6-11eb-9123-618780161a89.png)

```html
<html>
  <head>
    <title>GraphiQL</title>
    <style>
      html,
      body,
      #editor {
        height: 100%;
        width: 100%;
        margin: 0;
      }
    </style>

    <link
      href="https://cdn.jsdelivr.net/npm/graphiql-with-extensions@0.14.3/graphiqlWithExtensions.css"
      rel="stylesheet"
    />
  </head>

  <body>
    <div id="editor"></div>

    <script
      src="https://cdn.jsdelivr.net/npm/whatwg-fetch@{{whatwg_fetch_version}}/dist/fetch.umd.js"
      integrity="{{whatwg_fetch_sri}}"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/react@{{react_version}}/umd/react.production.min.js"
      integrity="{{react_sri}}"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/react-dom@{{react_version}}/umd/react-dom.production.min.js"
      integrity="{{react_dom_sri}}"
      crossorigin="anonymous"
    ></script>
    <script src="https://cdn.jsdelivr.net/npm/graphiql-with-extensions@0.14/graphiqlWithExtensions.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/js-cookie@rc/dist/js.cookie.min.js"></script>
    <script
      src="https://cdn.jsdelivr.net/npm/subscriptions-transport-ws@{{subscriptions_transport_ws_version}}/browser/client.js"
      integrity="{{subscriptions_transport_ws_sri}}"
      crossorigin="anonymous"
    ></script>
    <script src="https://cdn.jsdelivr.net/npm/graphiql-subscriptions-fetcher@0.0.2/browser/client.js"></script>
    <script>
      function httpUrlToWebSockeUrl(url) {
        return url.replace(/(http)(s)?\:\/\//, "ws$2://");
      }

      function graphQLFetcher(graphQLParams) {
        let headers = {
          Accept: "application/json",
          "Content-Type": "application/json",
        };

        let csrfToken = Cookies.get("csrftoken");
        if (csrfToken) {
          headers["x-csrftoken"] = csrfToken;
        }

        return fetch(window.location.href, {
          method: "post",
          headers: headers,
          body: JSON.stringify(graphQLParams),
        })
          .then((response) => {
            return response.text();
          })
          .then((responseBody) => {
            try {
              return JSON.parse(responseBody);
            } catch (error) {
              return responseBody;
            }
          });
      }

      const subscriptionsClient =
        "{{subscription_path}}" != "None"
          ? new window.SubscriptionsTransportWs.SubscriptionClient(
              httpUrlToWebSockeUrl("{{subscription_path}}"),
              {
                reconnect: true,
              }
            )
          : null;

      const graphQLFetcherWithSubscriptions =
        window.GraphiQLSubscriptionsFetcher.graphQLFetcher(
          subscriptionsClient,
          graphQLFetcher
        );

      ReactDOM.render(
        React.createElement(GraphiQLWithExtensions.GraphiQLWithExtensions, {
          fetcher: graphQLFetcherWithSubscriptions,
          headerEditorEnabled: "{{graphiql_header_editor_enabled}}" == "True",
        }),
        document.getElementById("editor")
      );
    </script>
  </body>
</html>
```


Discussion:
Some of the CDN packages in OP's template were returning 404s (specifically `whatwg-fetch` & `subscriptions-transport-ws`), in addition to an issue with an undefined subscription URL (it was `""` instead of `"None"`). Here is my modified version (working as of the day this is posted):
```html
<!--
The request to this GraphQL server provided the header "Accept: text/html"
and as a result has been presented GraphiQL - an in-browser IDE for
exploring GraphQL.
If you wish to receive JSON, provide the header "Accept: application/json" or
add "&raw" to the end of the URL within a browser.
-->
{% load static %}
<!DOCTYPE html>
<html>
  <head>
    <title>GraphiQL</title>
    <style>
      html,
      body,
      #editor {
        height: 100%;
        width: 100%;
        margin: 0;
      }
    </style>

    <link
      href="https://cdn.jsdelivr.net/npm/graphiql-with-extensions@0.14.3/graphiqlWithExtensions.css"
      rel="stylesheet"
    />
  </head>

  <body>
    <div id="editor"></div>

    <script src="https://cdn.jsdelivr.net/npm/whatwg-fetch@3.6.2/dist/fetch.umd.js" integrity="sha256-+pQdxwAcHJdQ3e/9S4RK6g8ZkwdMgFQuHvLuN5uyk5c=" crossorigin="anonymous"></script>
    <script
      src="https://cdn.jsdelivr.net/npm/react@{{react_version}}/umd/react.production.min.js"
      integrity="{{react_sri}}"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/react-dom@{{react_version}}/umd/react-dom.production.min.js"
      integrity="{{react_dom_sri}}"
      crossorigin="anonymous"
    ></script>
    <script src="https://cdn.jsdelivr.net/npm/graphiql-with-extensions@0.14/graphiqlWithExtensions.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/js-cookie@rc/dist/js.cookie.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/subscriptions-transport-ws@0.9.19/browser/client.js" integrity="sha256-BKMbTbqUpeRuFBA9qYWYe8TGy2uBpCoxnJPX1n8Vxo4=" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/graphiql-subscriptions-fetcher@0.0.2/browser/client.js"></script>
    <script>
      function httpUrlToWebSockeUrl(url) {
        return url.replace(/(http)(s)?\:\/\//, "ws$2://");
      }

      function graphQLFetcher(graphQLParams) {
        let headers = {
          Accept: "application/json",
          "Content-Type": "application/json",
        };

        let csrfToken = Cookies.get("csrftoken");
        if (csrfToken) {
          headers["x-csrftoken"] = csrfToken;
        }

        return fetch(window.location.href, {
          method: "post",
          headers: headers,
          body: JSON.stringify(graphQLParams),
        })
          .then((response) => {
            return response.text();
          })
          .then((responseBody) => {
            try {
              return JSON.parse(responseBody);
            } catch (error) {
              return responseBody;
            }
          });
      }

      const subscriptionsClient =
        "{{subscription_path}}" != "None" && "{{subscription_path}}" != ""
          ? new window.SubscriptionsTransportWs.SubscriptionClient(
              httpUrlToWebSockeUrl("{{subscription_path}}"),
              {
                reconnect: true,
              }
            )
          : null;

      const graphQLFetcherWithSubscriptions =
        window.GraphiQLSubscriptionsFetcher.graphQLFetcher(
          subscriptionsClient,
          graphQLFetcher
        );

      ReactDOM.render(
        React.createElement(GraphiQLWithExtensions.GraphiQLWithExtensions, {
          fetcher: graphQLFetcherWithSubscriptions,
          headerEditorEnabled: "{{graphiql_header_editor_enabled}}" == "True",
        }),
        document.getElementById("editor")
      );
    </script>
  </body>
</html>

```
@jkimbo would this be something you're open to a PR for? Explorer is a really useful tool that simplifies constructing queries versus writing them manually.  It really helps developers new to graphql learn the syntax.

Resolving patch:
diff --git a/graphene_django/views.py b/graphene_django/views.py
--- a/graphene_django/views.py
+++ b/graphene_django/views.py
@@ -76,6 +76,9 @@ class GraphQLView(View):
         "sha256-EZhvg6ANJrBsgLvLAa0uuHNLepLJVCFYS+xlb5U/bqw="
     )
 
+    graphiql_plugin_explorer_version = "0.1.15"
+    graphiql_plugin_explorer_sri = "sha256-3hUuhBXdXlfCj6RTeEkJFtEh/kUG+TCDASFpFPLrzvE="
+
     schema = None
     graphiql = False
     middleware = None
@@ -158,6 +161,8 @@ def dispatch(self, request, *args, **kwargs):
                     graphiql_css_sri=self.graphiql_css_sri,
                     subscriptions_transport_ws_version=self.subscriptions_transport_ws_version,
                     subscriptions_transport_ws_sri=self.subscriptions_transport_ws_sri,
+                    graphiql_plugin_explorer_version=self.graphiql_plugin_explorer_version,
+                    graphiql_plugin_explorer_sri=self.graphiql_plugin_explorer_sri,
                     # The SUBSCRIPTION_PATH setting.
                     subscription_path=self.subscription_path,
                     # GraphiQL headers tab,


Test patch: