mirror of
https://github.com/graphql-python/graphene-django.git
synced 2025-07-13 09:42:32 +03:00
Merge branch 'master' into patch-1
This commit is contained in:
commit
472b7a0d6a
34
.travis.yml
34
.travis.yml
|
@ -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"
|
||||||
|
|
|
@ -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))),
|
||||||
|
]
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
|
@ -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:]
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user