Repository: graphql-python/graphene-django

Issue:
Please document using csrf_exempt for read-only POST APIs
I have a ReactJS application that is hosted on a separate domain from my Django app.  But the library (Apollo) currently only makes requests with POST.  Since I have no mutations in my Graphene implementations, it's pretty safe for me to use ``csrf_exempt`` for my GraphQL view.

Would you be able to add that to your documentation somewhere so that people know this is an option.  Would you also be able to confirm that I'm correct in my deduction?  :)  If it's still possible to write to GraphQL without mutations, I'll need to look for a different solution.

Thanks!!

Discussion:

Patch:
diff --git a/.travis.yml b/.travis.yml
index 3531b56..bbeeb80 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,8 +12,17 @@ after_success:
   - pip install coveralls
   - coveralls
 
-matrix:
+stages:
+  - test
+  - name: deploy
+    if: tag IS present
+
+jobs:
   fast_finish: true
+
+  allow_failures:
+    - env: DJANGO=master
+
   include:
     - python: 2.7
       env: DJANGO=1.11
@@ -56,14 +65,15 @@ matrix:
     - python: 3.7
       env: TOXENV=black,flake8
 
-  allow_failures:
-    - env: DJANGO=master
-
-deploy:
-  provider: pypi
-  user: syrusakbary
-  on:
-    tags: true
-  password:
-    secure: kymIFCEPUbkgRqe2NAXkWfxMmGRfWvWBOP6LIXdVdkOOkm91fU7bndPGrAjos+/7gN0Org609ZmHSlVXNMJUWcsL2or/x5LcADJ4cZDe+79qynuoRb9xs1Ri4O4SBAuVMZxuVJvs8oUzT2R11ql5vASSMtXgbX+ZDGpmPRVZStkCuXgOc4LBhbPKyl3OFy7UQFPgAEmy3Yjh4ZSKzlXheK+S6mmr60+DCIjpaA0BWPxYK9FUE0qm7JJbHLUbwsUP/QMp5MmGjwFisXCNsIe686B7QKRaiOw62eJc2R7He8AuEC8T9OM4kRwDlecSn8mMpkoSB7QWtlJ+6XdLrJFPNvtrOfgfzS9/96Qrw9WlOslk68hMlhJeRb0s2YUD8tiV3UUkvbL1mfFoS4SI9U+rojS55KhUEJWHg1w7DjoOPoZmaIL2ChRupmvrFYNAGae1cxwG3Urh+t3wYlN3gpKsRDe5GOT7Wm2tr0ad3McCpDGUwSChX59BAJXe/MoLxkKScTrMyR8yMxHOF0b4zpVn5l7xB/o2Ik4zavx5q/0rGBMK2D+5d+gpQogKShoquTPsZUwO7sB5hYeH2hqGqpeGzZtb76E2zZYd18pJ0FsBudm5+KWjYdZ+vbtGrLxdTXJ1EEtzVXm0lscykTpqUucbXSa51dhStJvW2xEEz6p3rHo=
-  distributions: "sdist bdist_wheel"
+    - stage: deploy
+      script: skip
+      python: 3.7
+      after_success: true
+      deploy:
+        provider: pypi
+        user: syrusakbary
+        on:
+          tags: true
+        password:
+          secure: kymIFCEPUbkgRqe2NAXkWfxMmGRfWvWBOP6LIXdVdkOOkm91fU7bndPGrAjos+/7gN0Org609ZmHSlVXNMJUWcsL2or/x5LcADJ4cZDe+79qynuoRb9xs1Ri4O4SBAuVMZxuVJvs8oUzT2R11ql5vASSMtXgbX+ZDGpmPRVZStkCuXgOc4LBhbPKyl3OFy7UQFPgAEmy3Yjh4ZSKzlXheK+S6mmr60+DCIjpaA0BWPxYK9FUE0qm7JJbHLUbwsUP/QMp5MmGjwFisXCNsIe686B7QKRaiOw62eJc2R7He8AuEC8T9OM4kRwDlecSn8mMpkoSB7QWtlJ+6XdLrJFPNvtrOfgfzS9/96Qrw9WlOslk68hMlhJeRb0s2YUD8tiV3UUkvbL1mfFoS4SI9U+rojS55KhUEJWHg1w7DjoOPoZmaIL2ChRupmvrFYNAGae1cxwG3Urh+t3wYlN3gpKsRDe5GOT7Wm2tr0ad3McCpDGUwSChX59BAJXe/MoLxkKScTrMyR8yMxHOF0b4zpVn5l7xB/o2Ik4zavx5q/0rGBMK2D+5d+gpQogKShoquTPsZUwO7sB5hYeH2hqGqpeGzZtb76E2zZYd18pJ0FsBudm5+KWjYdZ+vbtGrLxdTXJ1EEtzVXm0lscykTpqUucbXSa51dhStJvW2xEEz6p3rHo=
+        distributions: "sdist bdist_wheel"
diff --git a/docs/installation.rst b/docs/installation.rst
index a2dc665..52f2520 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -66,4 +66,26 @@ The most basic ``schema.py`` looks like this:
     schema = graphene.Schema(query=Query)
 
 
-To learn how to extend the schema object for your project, read the basic tutorial.
\ No newline at end of file
+To learn how to extend the schema object for your project, read the basic tutorial.
+
+CSRF exempt
+-----------
+
+If have enabled `CSRF protection <https://docs.djangoproject.com/en/3.0/ref/csrf/>`_ in your Django app
+you will find that it prevents your API clients from POSTing to the ``graphql`` endpoint. You can either
+update your API client to pass the CSRF token with each request (the Django docs have a guide on how to do that: https://docs.djangoproject.com/en/3.0/ref/csrf/#ajax) or you can exempt your Graphql endpoint from CSRF protection by wrapping the ``GraphQLView`` with the ``csrf_exempt``
+decorator:
+
+.. code:: python
+
+    # urls.py
+
+    from django.urls import path
+    from django.views.decorators.csrf import csrf_exempt
+
+    from graphene_django.views import GraphQLView
+
+    urlpatterns = [
+        # ...
+        path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))),
+    ]
diff --git a/graphene_django/__init__.py b/graphene_django/__init__.py
index df58a5a..1ddc2cb 100644
--- a/graphene_django/__init__.py
+++ b/graphene_django/__init__.py
@@ -1,6 +1,6 @@
 from .types import DjangoObjectType
 from .fields import DjangoConnectionField
 
-__version__ = "2.7.1"
+__version__ = "2.8.0"
 
 __all__ = ["__version__", "DjangoObjectType", "DjangoConnectionField"]
diff --git a/graphene_django/types.py b/graphene_django/types.py
index ec426f1..4824c45 100644
--- a/graphene_django/types.py
+++ b/graphene_django/types.py
@@ -33,6 +33,24 @@ def construct_fields(
 ):
     _model_fields = get_model_fields(model)
 
+    # Validate the given fields against the model's fields.
+    model_field_names = set(field[0] for field in _model_fields)
+    for fields_list in (only_fields, exclude_fields):
+        if not fields_list:
+            continue
+        for name in fields_list:
+            if name in model_field_names:
+                continue
+
+            if hasattr(model, name):
+                raise Exception(
+                    '"{}" exists on model {} but it\'s not a field.'.format(name, model)
+                )
+            else:
+                raise Exception(
+                    'Field "{}" doesn\'t exist on model {}.'.format(name, model)
+                )
+
     fields = OrderedDict()
     for name, field in _model_fields:
         is_not_in_only = only_fields and name not in only_fields


Test patch:
diff --git a/graphene_django/tests/models.py b/graphene_django/tests/models.py
index 14a8367..44a5d8a 100644
--- a/graphene_django/tests/models.py
+++ b/graphene_django/tests/models.py
@@ -64,6 +64,9 @@ class Reporter(models.Model):
         if self.reporter_type == 2:  # quick and dirty way without enums
             self.__class__ = CNNReporter
 
+    def some_method(self):
+        return 123
+
 
 class CNNReporterManager(models.Manager):
     def get_queryset(self):
diff --git a/graphene_django/tests/test_types.py b/graphene_django/tests/test_types.py
index 5e9d1c2..cb31a9c 100644
--- a/graphene_django/tests/test_types.py
+++ b/graphene_django/tests/test_types.py
@@ -315,7 +315,31 @@ def test_django_objecttype_fields_exclude_type_checking():
         class Reporter2(DjangoObjectType):
             class Meta:
                 model = ReporterModel
-                fields = "foo"
+                exclude = "foo"
+
+
+@with_local_registry
+def test_django_objecttype_fields_exclude_exist_on_model():
+    with pytest.raises(Exception, match=r"Field .* doesn't exist"):
+
+        class Reporter(DjangoObjectType):
+            class Meta:
+                model = ReporterModel
+                fields = ["first_name", "foo", "email"]
+
+    with pytest.raises(Exception, match=r"Field .* doesn't exist"):
+
+        class Reporter2(DjangoObjectType):
+            class Meta:
+                model = ReporterModel
+                exclude = ["first_name", "foo", "email"]
+
+    with pytest.raises(Exception, match=r".* exists on model .* but it's not a field"):
+
+        class Reporter3(DjangoObjectType):
+            class Meta:
+                model = ReporterModel
+                fields = ["first_name", "some_method", "email"]
 
 
 class TestDjangoObjectType: