Merge branch 'master' into patch-1

This commit is contained in:
Jonathan Kim 2020-01-11 14:51:20 +01:00 committed by GitHub
commit 472b7a0d6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 158 additions and 46 deletions

View File

@ -12,8 +12,17 @@ after_success:
- pip install coveralls - pip install coveralls
- coveralls - coveralls
matrix: stages:
- test
- name: deploy
if: tag IS present
jobs:
fast_finish: true fast_finish: true
allow_failures:
- env: DJANGO=master
include: include:
- python: 2.7 - python: 2.7
env: DJANGO=1.11 env: DJANGO=1.11
@ -56,14 +65,15 @@ matrix:
- python: 3.7 - python: 3.7
env: TOXENV=black,flake8 env: TOXENV=black,flake8
allow_failures: - stage: deploy
- env: DJANGO=master script: skip
python: 3.7
deploy: after_success: true
provider: pypi deploy:
user: syrusakbary provider: pypi
on: user: syrusakbary
tags: true on:
password: tags: true
secure: kymIFCEPUbkgRqe2NAXkWfxMmGRfWvWBOP6LIXdVdkOOkm91fU7bndPGrAjos+/7gN0Org609ZmHSlVXNMJUWcsL2or/x5LcADJ4cZDe+79qynuoRb9xs1Ri4O4SBAuVMZxuVJvs8oUzT2R11ql5vASSMtXgbX+ZDGpmPRVZStkCuXgOc4LBhbPKyl3OFy7UQFPgAEmy3Yjh4ZSKzlXheK+S6mmr60+DCIjpaA0BWPxYK9FUE0qm7JJbHLUbwsUP/QMp5MmGjwFisXCNsIe686B7QKRaiOw62eJc2R7He8AuEC8T9OM4kRwDlecSn8mMpkoSB7QWtlJ+6XdLrJFPNvtrOfgfzS9/96Qrw9WlOslk68hMlhJeRb0s2YUD8tiV3UUkvbL1mfFoS4SI9U+rojS55KhUEJWHg1w7DjoOPoZmaIL2ChRupmvrFYNAGae1cxwG3Urh+t3wYlN3gpKsRDe5GOT7Wm2tr0ad3McCpDGUwSChX59BAJXe/MoLxkKScTrMyR8yMxHOF0b4zpVn5l7xB/o2Ik4zavx5q/0rGBMK2D+5d+gpQogKShoquTPsZUwO7sB5hYeH2hqGqpeGzZtb76E2zZYd18pJ0FsBudm5+KWjYdZ+vbtGrLxdTXJ1EEtzVXm0lscykTpqUucbXSa51dhStJvW2xEEz6p3rHo= password:
distributions: "sdist bdist_wheel" secure: kymIFCEPUbkgRqe2NAXkWfxMmGRfWvWBOP6LIXdVdkOOkm91fU7bndPGrAjos+/7gN0Org609ZmHSlVXNMJUWcsL2or/x5LcADJ4cZDe+79qynuoRb9xs1Ri4O4SBAuVMZxuVJvs8oUzT2R11ql5vASSMtXgbX+ZDGpmPRVZStkCuXgOc4LBhbPKyl3OFy7UQFPgAEmy3Yjh4ZSKzlXheK+S6mmr60+DCIjpaA0BWPxYK9FUE0qm7JJbHLUbwsUP/QMp5MmGjwFisXCNsIe686B7QKRaiOw62eJc2R7He8AuEC8T9OM4kRwDlecSn8mMpkoSB7QWtlJ+6XdLrJFPNvtrOfgfzS9/96Qrw9WlOslk68hMlhJeRb0s2YUD8tiV3UUkvbL1mfFoS4SI9U+rojS55KhUEJWHg1w7DjoOPoZmaIL2ChRupmvrFYNAGae1cxwG3Urh+t3wYlN3gpKsRDe5GOT7Wm2tr0ad3McCpDGUwSChX59BAJXe/MoLxkKScTrMyR8yMxHOF0b4zpVn5l7xB/o2Ik4zavx5q/0rGBMK2D+5d+gpQogKShoquTPsZUwO7sB5hYeH2hqGqpeGzZtb76E2zZYd18pJ0FsBudm5+KWjYdZ+vbtGrLxdTXJ1EEtzVXm0lscykTpqUucbXSa51dhStJvW2xEEz6p3rHo=
distributions: "sdist bdist_wheel"

View File

@ -67,3 +67,25 @@ The most basic ``schema.py`` looks like this:
To learn how to extend the schema object for your project, read the basic tutorial. 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))),
]

View File

@ -1,6 +1,6 @@
from .types import DjangoObjectType from .types import DjangoObjectType
from .fields import DjangoConnectionField from .fields import DjangoConnectionField
__version__ = "2.7.1" __version__ = "2.8.0"
__all__ = ["__version__", "DjangoObjectType", "DjangoConnectionField"] __all__ = ["__version__", "DjangoObjectType", "DjangoConnectionField"]

View File

@ -66,28 +66,6 @@ class BaseDjangoFormMutation(ClientIDMutation):
return kwargs return kwargs
# class DjangoFormInputObjectTypeOptions(InputObjectTypeOptions):
# form_class = None
# class DjangoFormInputObjectType(InputObjectType):
# class Meta:
# abstract = True
# @classmethod
# def __init_subclass_with_meta__(cls, form_class=None,
# only_fields=(), exclude_fields=(), _meta=None, **options):
# if not _meta:
# _meta = DjangoFormInputObjectTypeOptions(cls)
# assert isinstance(form_class, forms.Form), (
# 'form_class must be an instance of django.forms.Form'
# )
# _meta.form_class = form_class
# form = form_class()
# fields = fields_for_form(form, only_fields, exclude_fields)
# super(DjangoFormInputObjectType, cls).__init_subclass_with_meta__(_meta=_meta, fields=fields, **options)
class DjangoFormMutationOptions(MutationOptions): class DjangoFormMutationOptions(MutationOptions):
form_class = None form_class = None
@ -163,7 +141,9 @@ class DjangoModelFormMutation(BaseDjangoFormMutation):
registry = get_global_registry() registry = get_global_registry()
model_type = registry.get_type_for_model(model) model_type = registry.get_type_for_model(model)
return_field_name = return_field_name if not model_type:
raise Exception("No type registered for model: {}".format(model.__name__))
if not return_field_name: if not return_field_name:
model_name = model.__name__ model_name = model.__name__
return_field_name = model_name[:1].lower() + model_name[1:] return_field_name = model_name[:1].lower() + model_name[1:]

View File

@ -3,7 +3,8 @@ from django.test import TestCase
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from py.test import raises from py.test import raises
from graphene import ObjectType, String, Schema from graphene import ObjectType, Schema, String, Field
from graphene_django import DjangoObjectType
from graphene_django.tests.models import Film, FilmDetails, Pet from graphene_django.tests.models import Film, FilmDetails, Pet
from ...settings import graphene_settings from ...settings import graphene_settings
@ -29,6 +30,24 @@ class PetForm(forms.ModelForm):
fields = "__all__" fields = "__all__"
class PetType(DjangoObjectType):
class Meta:
model = Pet
fields = "__all__"
class FilmType(DjangoObjectType):
class Meta:
model = Film
fields = "__all__"
class FilmDetailsType(DjangoObjectType):
class Meta:
model = FilmDetails
fields = "__all__"
def test_needs_form_class(): def test_needs_form_class():
with raises(Exception) as exc: with raises(Exception) as exc:
@ -186,34 +205,70 @@ class ModelFormMutationTests(TestCase):
self.assertEqual(PetMutation._meta.return_field_name, "animal") self.assertEqual(PetMutation._meta.return_field_name, "animal")
self.assertIn("animal", PetMutation._meta.fields) self.assertIn("animal", PetMutation._meta.fields)
def test_model_form_mutation_mutate(self): def test_model_form_mutation_mutate_existing(self):
class PetMutation(DjangoModelFormMutation): class PetMutation(DjangoModelFormMutation):
pet = Field(PetType)
class Meta: class Meta:
form_class = PetForm form_class = PetForm
class Mutation(ObjectType):
pet_mutation = PetMutation.Field()
schema = Schema(query=MockQuery, mutation=Mutation)
pet = Pet.objects.create(name="Axel", age=10) pet = Pet.objects.create(name="Axel", age=10)
result = PetMutation.mutate_and_get_payload( result = schema.execute(
None, None, id=pet.pk, name="Mia", age=10 """ mutation PetMutation($pk: ID!) {
petMutation(input: { id: $pk, name: "Mia", age: 10 }) {
pet {
name
age
}
}
}
""",
variables={"pk": pet.pk},
) )
self.assertIs(result.errors, None)
self.assertEqual(result.data["petMutation"]["pet"], {"name": "Mia", "age": 10})
self.assertEqual(Pet.objects.count(), 1) self.assertEqual(Pet.objects.count(), 1)
pet.refresh_from_db() pet.refresh_from_db()
self.assertEqual(pet.name, "Mia") self.assertEqual(pet.name, "Mia")
self.assertEqual(result.errors, [])
def test_model_form_mutation_updates_existing_(self): def test_model_form_mutation_creates_new(self):
class PetMutation(DjangoModelFormMutation): class PetMutation(DjangoModelFormMutation):
pet = Field(PetType)
class Meta: class Meta:
form_class = PetForm form_class = PetForm
result = PetMutation.mutate_and_get_payload(None, None, name="Mia", age=10) class Mutation(ObjectType):
pet_mutation = PetMutation.Field()
schema = Schema(query=MockQuery, mutation=Mutation)
result = schema.execute(
""" mutation PetMutation {
petMutation(input: { name: "Mia", age: 10 }) {
pet {
name
age
}
}
}
"""
)
self.assertIs(result.errors, None)
self.assertEqual(result.data["petMutation"]["pet"], {"name": "Mia", "age": 10})
self.assertEqual(Pet.objects.count(), 1) self.assertEqual(Pet.objects.count(), 1)
pet = Pet.objects.get() pet = Pet.objects.get()
self.assertEqual(pet.name, "Mia") self.assertEqual(pet.name, "Mia")
self.assertEqual(pet.age, 10) self.assertEqual(pet.age, 10)
self.assertEqual(result.errors, [])
def test_model_form_mutation_mutate_invalid_form(self): def test_model_form_mutation_mutate_invalid_form(self):
class PetMutation(DjangoModelFormMutation): class PetMutation(DjangoModelFormMutation):

View File

@ -64,6 +64,9 @@ class Reporter(models.Model):
if self.reporter_type == 2: # quick and dirty way without enums if self.reporter_type == 2: # quick and dirty way without enums
self.__class__ = CNNReporter self.__class__ = CNNReporter
def some_method(self):
return 123
class CNNReporterManager(models.Manager): class CNNReporterManager(models.Manager):
def get_queryset(self): def get_queryset(self):

View File

@ -315,7 +315,31 @@ def test_django_objecttype_fields_exclude_type_checking():
class Reporter2(DjangoObjectType): class Reporter2(DjangoObjectType):
class Meta: class Meta:
model = ReporterModel 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: class TestDjangoObjectType:

View File

@ -33,6 +33,24 @@ def construct_fields(
): ):
_model_fields = get_model_fields(model) _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() fields = OrderedDict()
for name, field in _model_fields: for name, field in _model_fields:
is_not_in_only = only_fields and name not in only_fields is_not_in_only = only_fields and name not in only_fields