mirror of
https://github.com/graphql-python/graphene-django.git
synced 2025-07-12 17:22:20 +03:00
Merge branch 'master' into export-django-list-field-docs
This commit is contained in:
commit
c00e9368bc
25
.github/workflows/deploy.yml
vendored
Normal file
25
.github/workflows/deploy.yml
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
name: 🚀 Deploy to PyPI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: Set up Python 3.8
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: 3.8
|
||||||
|
- name: Build wheel and source tarball
|
||||||
|
run: |
|
||||||
|
python setup.py sdist bdist_wheel
|
||||||
|
- name: Publish a Python distribution to PyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@v1.1.0
|
||||||
|
with:
|
||||||
|
user: __token__
|
||||||
|
password: ${{ secrets.pypi_password }}
|
22
.github/workflows/lint.yml
vendored
Normal file
22
.github/workflows/lint.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
name: Lint
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: Set up Python 3.8
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: 3.8
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install tox
|
||||||
|
- name: Run lint 💅
|
||||||
|
run: tox
|
||||||
|
env:
|
||||||
|
TOXENV: flake8
|
31
.github/workflows/tests.yml
vendored
Normal file
31
.github/workflows/tests.yml
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
name: Tests
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
max-parallel: 4
|
||||||
|
matrix:
|
||||||
|
django: ["1.11", "2.2", "3.0"]
|
||||||
|
python-version: ["3.6", "3.7", "3.8"]
|
||||||
|
include:
|
||||||
|
- django: "1.11"
|
||||||
|
python-version: "2.7"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install tox tox-gh-actions
|
||||||
|
- name: Test with tox
|
||||||
|
run: tox
|
||||||
|
env:
|
||||||
|
DJANGO: ${{ matrix.django }}
|
||||||
|
TOXENV: ${{ matrix.toxenv }}
|
92
.travis.yml
92
.travis.yml
|
@ -1,92 +0,0 @@
|
||||||
language: python
|
|
||||||
cache: pip
|
|
||||||
dist: xenial
|
|
||||||
|
|
||||||
install:
|
|
||||||
- pip install tox tox-travis
|
|
||||||
|
|
||||||
script:
|
|
||||||
- tox
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- pip install coveralls
|
|
||||||
- coveralls
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
- python: 3.5
|
|
||||||
env: DJANGO=1.11
|
|
||||||
- python: 3.5
|
|
||||||
env: DJANGO=2.0
|
|
||||||
- python: 3.5
|
|
||||||
env: DJANGO=2.1
|
|
||||||
- python: 3.5
|
|
||||||
env: DJANGO=2.2
|
|
||||||
|
|
||||||
- python: 3.6
|
|
||||||
env: DJANGO=1.11
|
|
||||||
- python: 3.6
|
|
||||||
env: DJANGO=2.0
|
|
||||||
- python: 3.6
|
|
||||||
env: DJANGO=2.1
|
|
||||||
- python: 3.6
|
|
||||||
env: DJANGO=2.2
|
|
||||||
- python: 3.6
|
|
||||||
env: DJANGO=3.0
|
|
||||||
- python: 3.6
|
|
||||||
env: DJANGO=master
|
|
||||||
|
|
||||||
- python: 3.7
|
|
||||||
env: DJANGO=1.11
|
|
||||||
- python: 3.7
|
|
||||||
env: DJANGO=2.0
|
|
||||||
- python: 3.7
|
|
||||||
env: DJANGO=2.1
|
|
||||||
- python: 3.7
|
|
||||||
env: DJANGO=2.2
|
|
||||||
- python: 3.7
|
|
||||||
env: DJANGO=3.0
|
|
||||||
- python: 3.7
|
|
||||||
env: DJANGO=master
|
|
||||||
|
|
||||||
- python: 3.8
|
|
||||||
env: DJANGO=1.11
|
|
||||||
- python: 3.8
|
|
||||||
env: DJANGO=2.0
|
|
||||||
- python: 3.8
|
|
||||||
env: DJANGO=2.1
|
|
||||||
- python: 3.8
|
|
||||||
env: DJANGO=2.2
|
|
||||||
- python: 3.8
|
|
||||||
env: DJANGO=3.0
|
|
||||||
- python: 3.8
|
|
||||||
env: DJANGO=master
|
|
||||||
|
|
||||||
- python: 3.7
|
|
||||||
env: TOXENV=black,flake8
|
|
||||||
|
|
||||||
- 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"
|
|
10
README.md
10
README.md
|
@ -1,7 +1,3 @@
|
||||||
Please read [UPGRADE-v2.0.md](https://github.com/graphql-python/graphene/blob/master/UPGRADE-v2.0.md) to learn how to upgrade to Graphene `2.0`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#  Graphene-Django
|
#  Graphene-Django
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,11 +8,11 @@ A [Django](https://www.djangoproject.com/) integration for [Graphene](http://gra
|
||||||
[![Anaconda-Server Badge][conda-image]][conda-url]
|
[![Anaconda-Server Badge][conda-image]][conda-url]
|
||||||
[![coveralls][coveralls-image]][coveralls-url]
|
[![coveralls][coveralls-image]][coveralls-url]
|
||||||
|
|
||||||
[travis-image]: https://travis-ci.org/graphql-python/graphene-django.svg?style=flat
|
[travis-image]: https://travis-ci.org/graphql-python/graphene-django.svg?branch=master&style=flat
|
||||||
[travis-url]: https://travis-ci.org/graphql-python/graphene-django
|
[travis-url]: https://travis-ci.org/graphql-python/graphene-django
|
||||||
[pypi-image]: https://img.shields.io/pypi/v/graphene-django.svg?style=flat
|
[pypi-image]: https://img.shields.io/pypi/v/graphene-django.svg?style=flat
|
||||||
[pypi-url]: https://pypi.org/project/graphene-django/
|
[pypi-url]: https://pypi.org/project/graphene-django/
|
||||||
[coveralls-image]: https://coveralls.io/repos/graphql-python/graphene-django/badge.svg?branch=master&service=github
|
[coveralls-image]: https://coveralls.io/repos/github/graphql-python/graphene-django/badge.svg?branch=master
|
||||||
[coveralls-url]: https://coveralls.io/github/graphql-python/graphene-django?branch=master
|
[coveralls-url]: https://coveralls.io/github/graphql-python/graphene-django?branch=master
|
||||||
[conda-image]: https://img.shields.io/conda/vn/conda-forge/graphene-django.svg
|
[conda-image]: https://img.shields.io/conda/vn/conda-forge/graphene-django.svg
|
||||||
[conda-url]: https://anaconda.org/conda-forge/graphene-django
|
[conda-url]: https://anaconda.org/conda-forge/graphene-django
|
||||||
|
@ -94,7 +90,7 @@ class Query(graphene.ObjectType):
|
||||||
schema = graphene.Schema(query=Query)
|
schema = graphene.Schema(query=Query)
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you can simply query the schema:
|
Then you can query the schema:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
query = '''
|
query = '''
|
||||||
|
|
12
docs/extra-types.rst
Normal file
12
docs/extra-types.rst
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
Extra Types
|
||||||
|
===========
|
||||||
|
|
||||||
|
Here are some libraries that provide common types for Django specific fields.
|
||||||
|
|
||||||
|
|
||||||
|
GeoDjango
|
||||||
|
---------
|
||||||
|
|
||||||
|
Use the graphene-gis_ library to add GeoDjango types to your Schema.
|
||||||
|
|
||||||
|
.. _graphene-gis: https://github.com/EverWinter23/graphene-gis
|
|
@ -26,6 +26,7 @@ For more advanced use, check out the Relay tutorial.
|
||||||
schema
|
schema
|
||||||
queries
|
queries
|
||||||
fields
|
fields
|
||||||
|
extra-types
|
||||||
mutations
|
mutations
|
||||||
filtering
|
filtering
|
||||||
authorization
|
authorization
|
||||||
|
|
18
graphene_django/conftest.py
Normal file
18
graphene_django/conftest.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from graphene_django.settings import graphene_settings as gsettings
|
||||||
|
|
||||||
|
from .registry import reset_global_registry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def reset_registry_fixture(db):
|
||||||
|
yield None
|
||||||
|
reset_global_registry()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def graphene_settings():
|
||||||
|
settings = dict(gsettings.__dict__)
|
||||||
|
yield gsettings
|
||||||
|
gsettings.__dict__ = settings
|
|
@ -152,13 +152,9 @@ def convert_field_to_int(field, registry=None):
|
||||||
return Int(description=field.help_text, required=not field.null)
|
return Int(description=field.help_text, required=not field.null)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_django_field.register(models.NullBooleanField)
|
||||||
@convert_django_field.register(models.BooleanField)
|
@convert_django_field.register(models.BooleanField)
|
||||||
def convert_field_to_boolean(field, registry=None):
|
def convert_field_to_boolean(field, registry=None):
|
||||||
return NonNull(Boolean, description=field.help_text)
|
|
||||||
|
|
||||||
|
|
||||||
@convert_django_field.register(models.NullBooleanField)
|
|
||||||
def convert_field_to_nullboolean(field, registry=None):
|
|
||||||
return Boolean(description=field.help_text, required=not field.null)
|
return Boolean(description=field.help_text, required=not field.null)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
from graphene.relay import Node
|
from graphene.relay import Node
|
||||||
from graphene_django import DjangoConnectionField, DjangoObjectType
|
from graphene_django import DjangoConnectionField, DjangoObjectType
|
||||||
|
@ -13,11 +11,6 @@ class context(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# from examples.starwars_django.models import Character
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_query_field():
|
def test_should_query_field():
|
||||||
r1 = Reporter(last_name="ABA")
|
r1 = Reporter(last_name="ABA")
|
||||||
r1.save()
|
r1.save()
|
||||||
|
|
|
@ -35,9 +35,6 @@ else:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
pytestmark.append(pytest.mark.django_db)
|
|
||||||
|
|
||||||
|
|
||||||
if DJANGO_FILTER_INSTALLED:
|
if DJANGO_FILTER_INSTALLED:
|
||||||
|
|
||||||
class ArticleNode(DjangoObjectType):
|
class ArticleNode(DjangoObjectType):
|
||||||
|
|
|
@ -1,16 +1,25 @@
|
||||||
|
import pytest
|
||||||
from django import forms
|
from django import forms
|
||||||
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, Schema, String, Field
|
from graphene import Field, ObjectType, Schema, String
|
||||||
from graphene_django import DjangoObjectType
|
from graphene_django import DjangoObjectType
|
||||||
from graphene_django.tests.models import Film, Pet
|
from graphene_django.tests.models import Pet
|
||||||
|
|
||||||
from ...settings import graphene_settings
|
|
||||||
from ..mutation import DjangoFormMutation, DjangoModelFormMutation
|
from ..mutation import DjangoFormMutation, DjangoModelFormMutation
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def pet_type():
|
||||||
|
class PetType(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = Pet
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
return PetType
|
||||||
|
|
||||||
|
|
||||||
class MyForm(forms.Form):
|
class MyForm(forms.Form):
|
||||||
text = forms.CharField()
|
text = forms.CharField()
|
||||||
|
|
||||||
|
@ -36,18 +45,6 @@ class PetForm(forms.ModelForm):
|
||||||
return age
|
return age
|
||||||
|
|
||||||
|
|
||||||
class PetType(DjangoObjectType):
|
|
||||||
class Meta:
|
|
||||||
model = Pet
|
|
||||||
fields = "__all__"
|
|
||||||
|
|
||||||
|
|
||||||
class FilmType(DjangoObjectType):
|
|
||||||
class Meta:
|
|
||||||
model = Film
|
|
||||||
fields = "__all__"
|
|
||||||
|
|
||||||
|
|
||||||
def test_needs_form_class():
|
def test_needs_form_class():
|
||||||
with raises(Exception) as exc:
|
with raises(Exception) as exc:
|
||||||
|
|
||||||
|
@ -73,7 +70,7 @@ def test_has_input_fields():
|
||||||
assert "text" in MyMutation.Input._meta.fields
|
assert "text" in MyMutation.Input._meta.fields
|
||||||
|
|
||||||
|
|
||||||
def test_mutation_error_camelcased():
|
def test_mutation_error_camelcased(pet_type, graphene_settings):
|
||||||
class ExtraPetForm(PetForm):
|
class ExtraPetForm(PetForm):
|
||||||
test_field = forms.CharField(required=True)
|
test_field = forms.CharField(required=True)
|
||||||
|
|
||||||
|
@ -86,234 +83,237 @@ def test_mutation_error_camelcased():
|
||||||
graphene_settings.CAMELCASE_ERRORS = True
|
graphene_settings.CAMELCASE_ERRORS = True
|
||||||
result = PetMutation.mutate_and_get_payload(None, None)
|
result = PetMutation.mutate_and_get_payload(None, None)
|
||||||
assert {f.field for f in result.errors} == {"name", "age", "testField"}
|
assert {f.field for f in result.errors} == {"name", "age", "testField"}
|
||||||
graphene_settings.CAMELCASE_ERRORS = False
|
|
||||||
|
|
||||||
|
|
||||||
class MockQuery(ObjectType):
|
class MockQuery(ObjectType):
|
||||||
a = String()
|
a = String()
|
||||||
|
|
||||||
|
|
||||||
class FormMutationTests(TestCase):
|
def test_form_invalid_form():
|
||||||
def test_form_invalid_form(self):
|
class MyMutation(DjangoFormMutation):
|
||||||
class MyMutation(DjangoFormMutation):
|
class Meta:
|
||||||
class Meta:
|
form_class = MyForm
|
||||||
form_class = MyForm
|
|
||||||
|
|
||||||
class Mutation(ObjectType):
|
class Mutation(ObjectType):
|
||||||
my_mutation = MyMutation.Field()
|
my_mutation = MyMutation.Field()
|
||||||
|
|
||||||
schema = Schema(query=MockQuery, mutation=Mutation)
|
schema = Schema(query=MockQuery, mutation=Mutation)
|
||||||
|
|
||||||
result = schema.execute(
|
result = schema.execute(
|
||||||
""" mutation MyMutation {
|
""" mutation MyMutation {
|
||||||
myMutation(input: { text: "INVALID_INPUT" }) {
|
myMutation(input: { text: "INVALID_INPUT" }) {
|
||||||
errors {
|
errors {
|
||||||
field
|
field
|
||||||
messages
|
messages
|
||||||
}
|
}
|
||||||
text
|
text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.errors is None
|
||||||
|
assert result.data["myMutation"]["errors"] == [
|
||||||
|
{"field": "text", "messages": ["Invalid input"]}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_form_valid_input():
|
||||||
|
class MyMutation(DjangoFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = MyForm
|
||||||
|
|
||||||
|
class Mutation(ObjectType):
|
||||||
|
my_mutation = MyMutation.Field()
|
||||||
|
|
||||||
|
schema = Schema(query=MockQuery, mutation=Mutation)
|
||||||
|
|
||||||
|
result = schema.execute(
|
||||||
|
""" mutation MyMutation {
|
||||||
|
myMutation(input: { text: "VALID_INPUT" }) {
|
||||||
|
errors {
|
||||||
|
field
|
||||||
|
messages
|
||||||
|
}
|
||||||
|
text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.errors is None
|
||||||
|
assert result.data["myMutation"]["errors"] == []
|
||||||
|
assert result.data["myMutation"]["text"] == "VALID_INPUT"
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_meta_fields(pet_type):
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
|
||||||
|
assert PetMutation._meta.model is Pet
|
||||||
|
assert PetMutation._meta.return_field_name == "pet"
|
||||||
|
assert "pet" in PetMutation._meta.fields
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_input_meta_fields(pet_type):
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
|
||||||
|
assert PetMutation._meta.model is Pet
|
||||||
|
assert PetMutation._meta.return_field_name == "pet"
|
||||||
|
assert "name" in PetMutation.Input._meta.fields
|
||||||
|
assert "client_mutation_id" in PetMutation.Input._meta.fields
|
||||||
|
assert "id" in PetMutation.Input._meta.fields
|
||||||
|
|
||||||
|
|
||||||
|
def test_exclude_fields_input_meta_fields(pet_type):
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
exclude_fields = ["id"]
|
||||||
|
|
||||||
|
assert PetMutation._meta.model is Pet
|
||||||
|
assert PetMutation._meta.return_field_name == "pet"
|
||||||
|
assert "name" in PetMutation.Input._meta.fields
|
||||||
|
assert "age" in PetMutation.Input._meta.fields
|
||||||
|
assert "client_mutation_id" in PetMutation.Input._meta.fields
|
||||||
|
assert "id" not in PetMutation.Input._meta.fields
|
||||||
|
|
||||||
|
|
||||||
|
def test_custom_return_field_name(pet_type):
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
model = Pet
|
||||||
|
return_field_name = "animal"
|
||||||
|
|
||||||
|
assert PetMutation._meta.model is Pet
|
||||||
|
assert PetMutation._meta.return_field_name == "animal"
|
||||||
|
assert "animal" in PetMutation._meta.fields
|
||||||
|
|
||||||
|
|
||||||
|
def test_model_form_mutation_mutate_existing(pet_type):
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
pet = Field(pet_type)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
|
||||||
|
class Mutation(ObjectType):
|
||||||
|
pet_mutation = PetMutation.Field()
|
||||||
|
|
||||||
|
schema = Schema(query=MockQuery, mutation=Mutation)
|
||||||
|
|
||||||
|
pet = Pet.objects.create(name="Axel", age=10)
|
||||||
|
|
||||||
|
result = schema.execute(
|
||||||
|
""" mutation PetMutation($pk: ID!) {
|
||||||
|
petMutation(input: { id: $pk, name: "Mia", age: 10 }) {
|
||||||
|
pet {
|
||||||
|
name
|
||||||
|
age
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
}
|
||||||
)
|
""",
|
||||||
|
variable_values={"pk": pet.pk},
|
||||||
|
)
|
||||||
|
|
||||||
self.assertIs(result.errors, None)
|
assert result.errors is None
|
||||||
self.assertEqual(
|
assert result.data["petMutation"]["pet"] == {"name": "Mia", "age": 10}
|
||||||
result.data["myMutation"]["errors"],
|
|
||||||
[{"field": "text", "messages": ["Invalid input"]}],
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_form_valid_input(self):
|
assert Pet.objects.count() == 1
|
||||||
class MyMutation(DjangoFormMutation):
|
pet.refresh_from_db()
|
||||||
class Meta:
|
assert pet.name == "Mia"
|
||||||
form_class = MyForm
|
|
||||||
|
|
||||||
class Mutation(ObjectType):
|
|
||||||
my_mutation = MyMutation.Field()
|
|
||||||
|
|
||||||
schema = Schema(query=MockQuery, mutation=Mutation)
|
def test_model_form_mutation_creates_new(pet_type):
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
pet = Field(pet_type)
|
||||||
|
|
||||||
result = schema.execute(
|
class Meta:
|
||||||
""" mutation MyMutation {
|
form_class = PetForm
|
||||||
myMutation(input: { text: "VALID_INPUT" }) {
|
|
||||||
errors {
|
class Mutation(ObjectType):
|
||||||
field
|
pet_mutation = PetMutation.Field()
|
||||||
messages
|
|
||||||
}
|
schema = Schema(query=MockQuery, mutation=Mutation)
|
||||||
text
|
|
||||||
|
result = schema.execute(
|
||||||
|
""" mutation PetMutation {
|
||||||
|
petMutation(input: { name: "Mia", age: 10 }) {
|
||||||
|
pet {
|
||||||
|
name
|
||||||
|
age
|
||||||
|
}
|
||||||
|
errors {
|
||||||
|
field
|
||||||
|
messages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
}
|
||||||
)
|
"""
|
||||||
|
)
|
||||||
|
assert result.errors is None
|
||||||
|
assert result.data["petMutation"]["pet"] == {"name": "Mia", "age": 10}
|
||||||
|
|
||||||
self.assertIs(result.errors, None)
|
assert Pet.objects.count() == 1
|
||||||
self.assertEqual(result.data["myMutation"]["errors"], [])
|
pet = Pet.objects.get()
|
||||||
self.assertEqual(result.data["myMutation"]["text"], "VALID_INPUT")
|
assert pet.name == "Mia"
|
||||||
|
assert pet.age == 10
|
||||||
|
|
||||||
|
|
||||||
class ModelFormMutationTests(TestCase):
|
def test_model_form_mutation_invalid_input(pet_type):
|
||||||
def test_default_meta_fields(self):
|
class PetMutation(DjangoModelFormMutation):
|
||||||
class PetMutation(DjangoModelFormMutation):
|
pet = Field(pet_type)
|
||||||
class Meta:
|
|
||||||
form_class = PetForm
|
|
||||||
|
|
||||||
self.assertEqual(PetMutation._meta.model, Pet)
|
class Meta:
|
||||||
self.assertEqual(PetMutation._meta.return_field_name, "pet")
|
form_class = PetForm
|
||||||
self.assertIn("pet", PetMutation._meta.fields)
|
|
||||||
|
|
||||||
def test_default_input_meta_fields(self):
|
class Mutation(ObjectType):
|
||||||
class PetMutation(DjangoModelFormMutation):
|
pet_mutation = PetMutation.Field()
|
||||||
class Meta:
|
|
||||||
form_class = PetForm
|
|
||||||
|
|
||||||
self.assertEqual(PetMutation._meta.model, Pet)
|
schema = Schema(query=MockQuery, mutation=Mutation)
|
||||||
self.assertEqual(PetMutation._meta.return_field_name, "pet")
|
|
||||||
self.assertIn("name", PetMutation.Input._meta.fields)
|
|
||||||
self.assertIn("client_mutation_id", PetMutation.Input._meta.fields)
|
|
||||||
self.assertIn("id", PetMutation.Input._meta.fields)
|
|
||||||
|
|
||||||
def test_exclude_fields_input_meta_fields(self):
|
result = schema.execute(
|
||||||
class PetMutation(DjangoModelFormMutation):
|
""" mutation PetMutation {
|
||||||
class Meta:
|
petMutation(input: { name: "Mia", age: 99 }) {
|
||||||
form_class = PetForm
|
pet {
|
||||||
exclude_fields = ["id"]
|
name
|
||||||
|
age
|
||||||
self.assertEqual(PetMutation._meta.model, Pet)
|
}
|
||||||
self.assertEqual(PetMutation._meta.return_field_name, "pet")
|
errors {
|
||||||
self.assertIn("name", PetMutation.Input._meta.fields)
|
field
|
||||||
self.assertIn("age", PetMutation.Input._meta.fields)
|
messages
|
||||||
self.assertIn("client_mutation_id", PetMutation.Input._meta.fields)
|
|
||||||
self.assertNotIn("id", PetMutation.Input._meta.fields)
|
|
||||||
|
|
||||||
def test_custom_return_field_name(self):
|
|
||||||
class PetMutation(DjangoModelFormMutation):
|
|
||||||
class Meta:
|
|
||||||
form_class = PetForm
|
|
||||||
model = Pet
|
|
||||||
return_field_name = "animal"
|
|
||||||
|
|
||||||
self.assertEqual(PetMutation._meta.model, Pet)
|
|
||||||
self.assertEqual(PetMutation._meta.return_field_name, "animal")
|
|
||||||
self.assertIn("animal", PetMutation._meta.fields)
|
|
||||||
|
|
||||||
def test_model_form_mutation_mutate_existing(self):
|
|
||||||
class PetMutation(DjangoModelFormMutation):
|
|
||||||
pet = Field(PetType)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
form_class = PetForm
|
|
||||||
|
|
||||||
class Mutation(ObjectType):
|
|
||||||
pet_mutation = PetMutation.Field()
|
|
||||||
|
|
||||||
schema = Schema(query=MockQuery, mutation=Mutation)
|
|
||||||
|
|
||||||
pet = Pet.objects.create(name="Axel", age=10)
|
|
||||||
|
|
||||||
result = schema.execute(
|
|
||||||
""" mutation PetMutation($pk: ID!) {
|
|
||||||
petMutation(input: { id: $pk, name: "Mia", age: 10 }) {
|
|
||||||
pet {
|
|
||||||
name
|
|
||||||
age
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
""",
|
}
|
||||||
variable_values={"pk": pet.pk},
|
"""
|
||||||
)
|
)
|
||||||
|
assert result.errors is None
|
||||||
|
assert result.data["petMutation"]["pet"] is None
|
||||||
|
assert result.data["petMutation"]["errors"] == [
|
||||||
|
{"field": "age", "messages": ["Too old"]}
|
||||||
|
]
|
||||||
|
|
||||||
self.assertIs(result.errors, None)
|
assert Pet.objects.count() == 0
|
||||||
self.assertEqual(result.data["petMutation"]["pet"], {"name": "Mia", "age": 10})
|
|
||||||
|
|
||||||
self.assertEqual(Pet.objects.count(), 1)
|
|
||||||
pet.refresh_from_db()
|
|
||||||
self.assertEqual(pet.name, "Mia")
|
|
||||||
|
|
||||||
def test_model_form_mutation_creates_new(self):
|
def test_model_form_mutation_mutate_invalid_form(pet_type):
|
||||||
class PetMutation(DjangoModelFormMutation):
|
class PetMutation(DjangoModelFormMutation):
|
||||||
pet = Field(PetType)
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
|
||||||
class Meta:
|
result = PetMutation.mutate_and_get_payload(None, None)
|
||||||
form_class = PetForm
|
|
||||||
|
|
||||||
class Mutation(ObjectType):
|
# A pet was not created
|
||||||
pet_mutation = PetMutation.Field()
|
Pet.objects.count() == 0
|
||||||
|
|
||||||
schema = Schema(query=MockQuery, mutation=Mutation)
|
fields_w_error = [e.field for e in result.errors]
|
||||||
|
assert len(result.errors) == 2
|
||||||
result = schema.execute(
|
assert result.errors[0].messages == ["This field is required."]
|
||||||
""" mutation PetMutation {
|
assert result.errors[1].messages == ["This field is required."]
|
||||||
petMutation(input: { name: "Mia", age: 10 }) {
|
assert "age" in fields_w_error
|
||||||
pet {
|
assert "name" in fields_w_error
|
||||||
name
|
|
||||||
age
|
|
||||||
}
|
|
||||||
errors {
|
|
||||||
field
|
|
||||||
messages
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
self.assertIs(result.errors, None)
|
|
||||||
self.assertEqual(result.data["petMutation"]["pet"], {"name": "Mia", "age": 10})
|
|
||||||
|
|
||||||
self.assertEqual(Pet.objects.count(), 1)
|
|
||||||
pet = Pet.objects.get()
|
|
||||||
self.assertEqual(pet.name, "Mia")
|
|
||||||
self.assertEqual(pet.age, 10)
|
|
||||||
|
|
||||||
def test_model_form_mutation_invalid_input(self):
|
|
||||||
class PetMutation(DjangoModelFormMutation):
|
|
||||||
pet = Field(PetType)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
form_class = PetForm
|
|
||||||
|
|
||||||
class Mutation(ObjectType):
|
|
||||||
pet_mutation = PetMutation.Field()
|
|
||||||
|
|
||||||
schema = Schema(query=MockQuery, mutation=Mutation)
|
|
||||||
|
|
||||||
result = schema.execute(
|
|
||||||
""" mutation PetMutation {
|
|
||||||
petMutation(input: { name: "Mia", age: 99 }) {
|
|
||||||
pet {
|
|
||||||
name
|
|
||||||
age
|
|
||||||
}
|
|
||||||
errors {
|
|
||||||
field
|
|
||||||
messages
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
self.assertIs(result.errors, None)
|
|
||||||
self.assertEqual(result.data["petMutation"]["pet"], None)
|
|
||||||
self.assertEqual(
|
|
||||||
result.data["petMutation"]["errors"],
|
|
||||||
[{"field": "age", "messages": ["Too old"],}],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(Pet.objects.count(), 0)
|
|
||||||
|
|
||||||
def test_model_form_mutation_mutate_invalid_form(self):
|
|
||||||
class PetMutation(DjangoModelFormMutation):
|
|
||||||
class Meta:
|
|
||||||
form_class = PetForm
|
|
||||||
|
|
||||||
result = PetMutation.mutate_and_get_payload(None, None)
|
|
||||||
|
|
||||||
# A pet was not created
|
|
||||||
self.assertEqual(Pet.objects.count(), 0)
|
|
||||||
|
|
||||||
fields_w_error = [e.field for e in result.errors]
|
|
||||||
self.assertEqual(len(result.errors), 2)
|
|
||||||
self.assertIn("name", fields_w_error)
|
|
||||||
self.assertEqual(result.errors[0].messages, ["This field is required."])
|
|
||||||
self.assertIn("age", fields_w_error)
|
|
||||||
self.assertEqual(result.errors[1].messages, ["This field is required."])
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ class CommandArguments(BaseCommand):
|
||||||
class Command(CommandArguments):
|
class Command(CommandArguments):
|
||||||
help = "Dump Graphene schema as a JSON or GraphQL file"
|
help = "Dump Graphene schema as a JSON or GraphQL file"
|
||||||
can_import_settings = True
|
can_import_settings = True
|
||||||
|
requires_system_checks = False
|
||||||
|
|
||||||
def save_json_file(self, out, schema_dict, indent):
|
def save_json_file(self, out, schema_dict, indent):
|
||||||
with open(out, "w") as outfile:
|
with open(out, "w") as outfile:
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
import graphene
|
|
||||||
import pytest
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from graphene import Schema
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
import graphene
|
||||||
|
from graphene import Schema
|
||||||
from graphene_django import DjangoObjectType
|
from graphene_django import DjangoObjectType
|
||||||
from graphene_django.rest_framework.mutation import SerializerMutation
|
from graphene_django.rest_framework.mutation import SerializerMutation
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
|
||||||
|
|
||||||
|
|
||||||
class MyFakeChildModel(models.Model):
|
class MyFakeChildModel(models.Model):
|
||||||
name = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from py.test import mark, raises
|
from py.test import raises
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from graphene import Field, ResolveInfo
|
from graphene import Field, ResolveInfo
|
||||||
from graphene.types.inputobjecttype import InputObjectType
|
from graphene.types.inputobjecttype import InputObjectType
|
||||||
|
|
||||||
from ...settings import graphene_settings
|
|
||||||
from ...types import DjangoObjectType
|
from ...types import DjangoObjectType
|
||||||
from ..models import MyFakeModel, MyFakeModelWithPassword, MyFakeModelWithDate
|
from ..models import MyFakeModel, MyFakeModelWithDate, MyFakeModelWithPassword
|
||||||
from ..mutation import SerializerMutation
|
from ..mutation import SerializerMutation
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,7 +98,6 @@ def test_exclude_fields():
|
||||||
assert "created" not in MyMutation.Input._meta.fields
|
assert "created" not in MyMutation.Input._meta.fields
|
||||||
|
|
||||||
|
|
||||||
@mark.django_db
|
|
||||||
def test_write_only_field():
|
def test_write_only_field():
|
||||||
class WriteOnlyFieldModelSerializer(serializers.ModelSerializer):
|
class WriteOnlyFieldModelSerializer(serializers.ModelSerializer):
|
||||||
password = serializers.CharField(write_only=True)
|
password = serializers.CharField(write_only=True)
|
||||||
|
@ -122,7 +120,6 @@ def test_write_only_field():
|
||||||
), "'password' is write_only field and shouldn't be visible"
|
), "'password' is write_only field and shouldn't be visible"
|
||||||
|
|
||||||
|
|
||||||
@mark.django_db
|
|
||||||
def test_write_only_field_using_extra_kwargs():
|
def test_write_only_field_using_extra_kwargs():
|
||||||
class WriteOnlyFieldModelSerializer(serializers.ModelSerializer):
|
class WriteOnlyFieldModelSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -144,7 +141,6 @@ def test_write_only_field_using_extra_kwargs():
|
||||||
), "'password' is write_only field and shouldn't be visible"
|
), "'password' is write_only field and shouldn't be visible"
|
||||||
|
|
||||||
|
|
||||||
@mark.django_db
|
|
||||||
def test_read_only_fields():
|
def test_read_only_fields():
|
||||||
class ReadOnlyFieldModelSerializer(serializers.ModelSerializer):
|
class ReadOnlyFieldModelSerializer(serializers.ModelSerializer):
|
||||||
cool_name = serializers.CharField(read_only=True)
|
cool_name = serializers.CharField(read_only=True)
|
||||||
|
@ -194,7 +190,6 @@ def test_mutate_and_get_payload_success():
|
||||||
assert result.errors is None
|
assert result.errors is None
|
||||||
|
|
||||||
|
|
||||||
@mark.django_db
|
|
||||||
def test_model_add_mutate_and_get_payload_success():
|
def test_model_add_mutate_and_get_payload_success():
|
||||||
result = MyModelMutation.mutate_and_get_payload(
|
result = MyModelMutation.mutate_and_get_payload(
|
||||||
None, mock_info(), **{"cool_name": "Narf"}
|
None, mock_info(), **{"cool_name": "Narf"}
|
||||||
|
@ -204,7 +199,6 @@ def test_model_add_mutate_and_get_payload_success():
|
||||||
assert isinstance(result.created, datetime.datetime)
|
assert isinstance(result.created, datetime.datetime)
|
||||||
|
|
||||||
|
|
||||||
@mark.django_db
|
|
||||||
def test_model_update_mutate_and_get_payload_success():
|
def test_model_update_mutate_and_get_payload_success():
|
||||||
instance = MyFakeModel.objects.create(cool_name="Narf")
|
instance = MyFakeModel.objects.create(cool_name="Narf")
|
||||||
result = MyModelMutation.mutate_and_get_payload(
|
result = MyModelMutation.mutate_and_get_payload(
|
||||||
|
@ -214,7 +208,6 @@ def test_model_update_mutate_and_get_payload_success():
|
||||||
assert result.cool_name == "New Narf"
|
assert result.cool_name == "New Narf"
|
||||||
|
|
||||||
|
|
||||||
@mark.django_db
|
|
||||||
def test_model_partial_update_mutate_and_get_payload_success():
|
def test_model_partial_update_mutate_and_get_payload_success():
|
||||||
instance = MyFakeModel.objects.create(cool_name="Narf")
|
instance = MyFakeModel.objects.create(cool_name="Narf")
|
||||||
result = MyModelMutation.mutate_and_get_payload(
|
result = MyModelMutation.mutate_and_get_payload(
|
||||||
|
@ -224,7 +217,6 @@ def test_model_partial_update_mutate_and_get_payload_success():
|
||||||
assert result.cool_name == "Narf"
|
assert result.cool_name == "Narf"
|
||||||
|
|
||||||
|
|
||||||
@mark.django_db
|
|
||||||
def test_model_invalid_update_mutate_and_get_payload_success():
|
def test_model_invalid_update_mutate_and_get_payload_success():
|
||||||
class InvalidModelMutation(SerializerMutation):
|
class InvalidModelMutation(SerializerMutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -239,7 +231,6 @@ def test_model_invalid_update_mutate_and_get_payload_success():
|
||||||
assert '"id" required' in str(exc.value)
|
assert '"id" required' in str(exc.value)
|
||||||
|
|
||||||
|
|
||||||
@mark.django_db
|
|
||||||
def test_perform_mutate_success():
|
def test_perform_mutate_success():
|
||||||
class MyMethodMutation(SerializerMutation):
|
class MyMethodMutation(SerializerMutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -272,11 +263,10 @@ def test_model_mutate_and_get_payload_error():
|
||||||
assert len(result.errors) > 0
|
assert len(result.errors) > 0
|
||||||
|
|
||||||
|
|
||||||
def test_mutation_error_camelcased():
|
def test_mutation_error_camelcased(graphene_settings):
|
||||||
graphene_settings.CAMELCASE_ERRORS = True
|
graphene_settings.CAMELCASE_ERRORS = True
|
||||||
result = MyModelMutation.mutate_and_get_payload(None, mock_info(), **{})
|
result = MyModelMutation.mutate_and_get_payload(None, mock_info(), **{})
|
||||||
assert result.errors[0].field == "coolName"
|
assert result.errors[0].field == "coolName"
|
||||||
graphene_settings.CAMELCASE_ERRORS = False
|
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_serializer_operations():
|
def test_invalid_serializer_operations():
|
||||||
|
|
|
@ -8,7 +8,7 @@ from graphene import ObjectType, Schema, String
|
||||||
|
|
||||||
|
|
||||||
@patch("graphene_django.management.commands.graphql_schema.Command.save_json_file")
|
@patch("graphene_django.management.commands.graphql_schema.Command.save_json_file")
|
||||||
def test_generate_json_file_on_call_graphql_schema(savefile_mock, settings):
|
def test_generate_json_file_on_call_graphql_schema(savefile_mock):
|
||||||
out = StringIO()
|
out = StringIO()
|
||||||
management.call_command("graphql_schema", schema="", stdout=out)
|
management.call_command("graphql_schema", schema="", stdout=out)
|
||||||
assert "Successfully dumped GraphQL schema to schema.json" in out.getvalue()
|
assert "Successfully dumped GraphQL schema to schema.json" in out.getvalue()
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
import pytest
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
|
import pytest
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from graphene import NonNull
|
|
||||||
from py.test import raises
|
from py.test import raises
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
|
from graphene import NonNull
|
||||||
from graphene.relay import ConnectionField, Node
|
from graphene.relay import ConnectionField, Node
|
||||||
from graphene.types.datetime import DateTime, Date, Time
|
from graphene.types.datetime import Date, DateTime, Time
|
||||||
from graphene.types.json import JSONString
|
from graphene.types.json import JSONString
|
||||||
|
|
||||||
from ..compat import JSONField, ArrayField, HStoreField, RangeField, MissingType
|
from ..compat import ArrayField, HStoreField, JSONField, MissingType, RangeField
|
||||||
from ..converter import (
|
from ..converter import (
|
||||||
convert_django_field,
|
convert_django_field,
|
||||||
convert_django_field_with_choices,
|
convert_django_field_with_choices,
|
||||||
|
@ -18,20 +19,25 @@ from ..converter import (
|
||||||
)
|
)
|
||||||
from ..registry import Registry
|
from ..registry import Registry
|
||||||
from ..types import DjangoObjectType
|
from ..types import DjangoObjectType
|
||||||
from ..settings import graphene_settings
|
|
||||||
from .models import Article, Film, FilmDetails, Reporter
|
from .models import Article, Film, FilmDetails, Reporter
|
||||||
|
|
||||||
|
|
||||||
# from graphene.core.types.custom_scalars import DateTime, Time, JSONString
|
# from graphene.core.types.custom_scalars import DateTime, Time, JSONString
|
||||||
|
|
||||||
|
|
||||||
def assert_conversion(django_field, graphene_field, *args, **kwargs):
|
def assert_conversion(django_field, graphene_field, *args, **kwargs):
|
||||||
field = django_field(help_text="Custom Help Text", null=True, *args, **kwargs)
|
_kwargs = kwargs.copy()
|
||||||
|
if "null" not in kwargs:
|
||||||
|
_kwargs["null"] = True
|
||||||
|
field = django_field(help_text="Custom Help Text", *args, **_kwargs)
|
||||||
graphene_type = convert_django_field(field)
|
graphene_type = convert_django_field(field)
|
||||||
assert isinstance(graphene_type, graphene_field)
|
assert isinstance(graphene_type, graphene_field)
|
||||||
field = graphene_type.Field()
|
field = graphene_type.Field()
|
||||||
assert field.description == "Custom Help Text"
|
assert field.description == "Custom Help Text"
|
||||||
nonnull_field = django_field(null=False, *args, **kwargs)
|
|
||||||
|
_kwargs = kwargs.copy()
|
||||||
|
if "null" not in kwargs:
|
||||||
|
_kwargs["null"] = False
|
||||||
|
nonnull_field = django_field(*args, **_kwargs)
|
||||||
if not nonnull_field.null:
|
if not nonnull_field.null:
|
||||||
nonnull_graphene_type = convert_django_field(nonnull_field)
|
nonnull_graphene_type = convert_django_field(nonnull_field)
|
||||||
nonnull_field = nonnull_graphene_type.Field()
|
nonnull_field = nonnull_graphene_type.Field()
|
||||||
|
@ -127,7 +133,12 @@ def test_should_integer_convert_int():
|
||||||
|
|
||||||
|
|
||||||
def test_should_boolean_convert_boolean():
|
def test_should_boolean_convert_boolean():
|
||||||
field = assert_conversion(models.BooleanField, graphene.NonNull)
|
assert_conversion(models.BooleanField, graphene.Boolean, null=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_should_boolean_convert_non_null_boolean():
|
||||||
|
field = assert_conversion(models.BooleanField, graphene.Boolean, null=False)
|
||||||
|
assert isinstance(field.type, graphene.NonNull)
|
||||||
assert field.type.of_type == graphene.Boolean
|
assert field.type.of_type == graphene.Boolean
|
||||||
|
|
||||||
|
|
||||||
|
@ -333,7 +344,7 @@ def test_should_postgres_range_convert_list():
|
||||||
assert field.type.of_type.of_type == graphene.Int
|
assert field.type.of_type.of_type == graphene.Int
|
||||||
|
|
||||||
|
|
||||||
def test_generate_enum_name():
|
def test_generate_enum_name(graphene_settings):
|
||||||
MockDjangoModelMeta = namedtuple("DjangoMeta", ["app_label", "object_name"])
|
MockDjangoModelMeta = namedtuple("DjangoMeta", ["app_label", "object_name"])
|
||||||
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V3_NAMING = True
|
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V3_NAMING = True
|
||||||
|
|
||||||
|
@ -351,5 +362,3 @@ def test_generate_enum_name():
|
||||||
generate_enum_name(model_meta, field)
|
generate_enum_name(model_meta, field)
|
||||||
== "SomeLongAppNameSomeObjectFizzBuzzChoices"
|
== "SomeLongAppNameSomeObjectFizzBuzzChoices"
|
||||||
)
|
)
|
||||||
|
|
||||||
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V3_NAMING = False
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ from .models import Article as ArticleModel
|
||||||
from .models import Reporter as ReporterModel
|
from .models import Reporter as ReporterModel
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
class TestDjangoListField:
|
class TestDjangoListField:
|
||||||
def test_only_django_object_types(self):
|
def test_only_django_object_types(self):
|
||||||
class TestType(ObjectType):
|
class TestType(ObjectType):
|
||||||
|
|
|
@ -1,25 +1,20 @@
|
||||||
import base64
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
from django.utils.functional import SimpleLazyObject
|
from django.utils.functional import SimpleLazyObject
|
||||||
|
from graphql_relay import to_global_id
|
||||||
from py.test import raises
|
from py.test import raises
|
||||||
|
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
from graphql_relay import to_global_id
|
|
||||||
import graphene
|
import graphene
|
||||||
from graphene.relay import Node
|
from graphene.relay import Node
|
||||||
|
|
||||||
from ..utils import DJANGO_FILTER_INSTALLED
|
from ..compat import JSONField, MissingType
|
||||||
from ..compat import MissingType, JSONField
|
|
||||||
from ..fields import DjangoConnectionField
|
from ..fields import DjangoConnectionField
|
||||||
from ..types import DjangoObjectType
|
from ..types import DjangoObjectType
|
||||||
from ..settings import graphene_settings
|
from ..utils import DJANGO_FILTER_INSTALLED
|
||||||
from .models import Article, CNNReporter, Reporter, Film, FilmDetails
|
from .models import Article, CNNReporter, Film, FilmDetails, Reporter
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_query_only_fields():
|
def test_should_query_only_fields():
|
||||||
|
@ -147,9 +142,6 @@ def test_should_query_postgres_fields():
|
||||||
|
|
||||||
|
|
||||||
def test_should_node():
|
def test_should_node():
|
||||||
# reset_global_registry()
|
|
||||||
# Node._meta.registry = get_global_registry()
|
|
||||||
|
|
||||||
class ReporterNode(DjangoObjectType):
|
class ReporterNode(DjangoObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Reporter
|
model = Reporter
|
||||||
|
@ -588,7 +580,7 @@ def test_should_query_node_multiple_filtering():
|
||||||
assert result.data == expected
|
assert result.data == expected
|
||||||
|
|
||||||
|
|
||||||
def test_should_enforce_first_or_last():
|
def test_should_enforce_first_or_last(graphene_settings):
|
||||||
graphene_settings.RELAY_CONNECTION_ENFORCE_FIRST_OR_LAST = True
|
graphene_settings.RELAY_CONNECTION_ENFORCE_FIRST_OR_LAST = True
|
||||||
|
|
||||||
class ReporterType(DjangoObjectType):
|
class ReporterType(DjangoObjectType):
|
||||||
|
@ -627,7 +619,7 @@ def test_should_enforce_first_or_last():
|
||||||
assert result.data == expected
|
assert result.data == expected
|
||||||
|
|
||||||
|
|
||||||
def test_should_error_if_first_is_greater_than_max():
|
def test_should_error_if_first_is_greater_than_max(graphene_settings):
|
||||||
graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 100
|
graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 100
|
||||||
|
|
||||||
class ReporterType(DjangoObjectType):
|
class ReporterType(DjangoObjectType):
|
||||||
|
@ -667,10 +659,8 @@ def test_should_error_if_first_is_greater_than_max():
|
||||||
)
|
)
|
||||||
assert result.data == expected
|
assert result.data == expected
|
||||||
|
|
||||||
graphene_settings.RELAY_CONNECTION_ENFORCE_FIRST_OR_LAST = False
|
|
||||||
|
|
||||||
|
def test_should_error_if_last_is_greater_than_max(graphene_settings):
|
||||||
def test_should_error_if_last_is_greater_than_max():
|
|
||||||
graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 100
|
graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 100
|
||||||
|
|
||||||
class ReporterType(DjangoObjectType):
|
class ReporterType(DjangoObjectType):
|
||||||
|
@ -710,8 +700,6 @@ def test_should_error_if_last_is_greater_than_max():
|
||||||
)
|
)
|
||||||
assert result.data == expected
|
assert result.data == expected
|
||||||
|
|
||||||
graphene_settings.RELAY_CONNECTION_ENFORCE_FIRST_OR_LAST = False
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_query_promise_connectionfields():
|
def test_should_query_promise_connectionfields():
|
||||||
from promise import Promise
|
from promise import Promise
|
||||||
|
|
|
@ -9,14 +9,10 @@ from graphene import Connection, Field, Interface, ObjectType, Schema, String
|
||||||
from graphene.relay import Node
|
from graphene.relay import Node
|
||||||
|
|
||||||
from .. import registry
|
from .. import registry
|
||||||
from ..settings import graphene_settings
|
|
||||||
from ..types import DjangoObjectType, DjangoObjectTypeOptions
|
from ..types import DjangoObjectType, DjangoObjectTypeOptions
|
||||||
from ..converter import convert_choice_field_to_enum
|
|
||||||
from .models import Article as ArticleModel
|
from .models import Article as ArticleModel
|
||||||
from .models import Reporter as ReporterModel
|
from .models import Reporter as ReporterModel
|
||||||
|
|
||||||
registry.reset_global_registry()
|
|
||||||
|
|
||||||
|
|
||||||
class Reporter(DjangoObjectType):
|
class Reporter(DjangoObjectType):
|
||||||
"""Reporter description"""
|
"""Reporter description"""
|
||||||
|
@ -198,7 +194,6 @@ type RootQuery {
|
||||||
def with_local_registry(func):
|
def with_local_registry(func):
|
||||||
def inner(*args, **kwargs):
|
def inner(*args, **kwargs):
|
||||||
old = registry.get_global_registry()
|
old = registry.get_global_registry()
|
||||||
registry.reset_global_registry()
|
|
||||||
try:
|
try:
|
||||||
retval = func(*args, **kwargs)
|
retval = func(*args, **kwargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -499,7 +494,9 @@ class TestDjangoObjectType:
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_django_objecttype_convert_choices_enum_naming_collisions(self, PetModel):
|
def test_django_objecttype_convert_choices_enum_naming_collisions(
|
||||||
|
self, PetModel, graphene_settings
|
||||||
|
):
|
||||||
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V3_NAMING = True
|
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V3_NAMING = True
|
||||||
|
|
||||||
class PetModelKind(DjangoObjectType):
|
class PetModelKind(DjangoObjectType):
|
||||||
|
@ -533,9 +530,10 @@ class TestDjangoObjectType:
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V3_NAMING = False
|
|
||||||
|
|
||||||
def test_django_objecttype_choices_custom_enum_name(self, PetModel):
|
def test_django_objecttype_choices_custom_enum_name(
|
||||||
|
self, PetModel, graphene_settings
|
||||||
|
):
|
||||||
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME = (
|
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME = (
|
||||||
"graphene_django.tests.test_types.custom_enum_name"
|
"graphene_django.tests.test_types.custom_enum_name"
|
||||||
)
|
)
|
||||||
|
@ -571,5 +569,3 @@ class TestDjangoObjectType:
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME = None
|
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
from django.utils.translation import gettext_lazy
|
import json
|
||||||
|
|
||||||
from ..utils import camelize, get_model_fields
|
import pytest
|
||||||
|
from django.utils.translation import gettext_lazy
|
||||||
|
from mock import patch
|
||||||
|
|
||||||
|
from ..utils import camelize, get_model_fields, GraphQLTestCase
|
||||||
from .models import Film, Reporter
|
from .models import Film, Reporter
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,3 +34,27 @@ def test_camelize():
|
||||||
"valueA": "value_b"
|
"valueA": "value_b"
|
||||||
}
|
}
|
||||||
assert camelize({0: {"field_a": ["errors"]}}) == {0: {"fieldA": ["errors"]}}
|
assert camelize({0: {"field_a": ["errors"]}}) == {0: {"fieldA": ["errors"]}}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@patch("graphene_django.utils.testing.Client.post")
|
||||||
|
def test_graphql_test_case_op_name(post_mock):
|
||||||
|
"""
|
||||||
|
Test that `GraphQLTestCase.query()`'s `op_name` argument produces an `operationName` field.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class TestClass(GraphQLTestCase):
|
||||||
|
GRAPHQL_SCHEMA = True
|
||||||
|
|
||||||
|
def runTest(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
tc = TestClass()
|
||||||
|
tc.setUpClass()
|
||||||
|
tc.query("query { }", op_name="QueryName")
|
||||||
|
body = json.loads(post_mock.call_args.args[1])
|
||||||
|
# `operationName` field from https://graphql.org/learn/serving-over-http/#post-request
|
||||||
|
assert (
|
||||||
|
"operationName",
|
||||||
|
"QueryName",
|
||||||
|
) in body.items(), "Field 'operationName' is not present in the final request."
|
||||||
|
|
|
@ -45,7 +45,7 @@ class GraphQLTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
body = {"query": query}
|
body = {"query": query}
|
||||||
if op_name:
|
if op_name:
|
||||||
body["operation_name"] = op_name
|
body["operationName"] = op_name
|
||||||
if variables:
|
if variables:
|
||||||
body["variables"] = variables
|
body["variables"] = variables
|
||||||
if input_data:
|
if input_data:
|
||||||
|
|
|
@ -15,6 +15,7 @@ from graphql.error import format_error as format_graphql_error
|
||||||
from graphql.error import GraphQLError
|
from graphql.error import GraphQLError
|
||||||
from graphql.execution import ExecutionResult
|
from graphql.execution import ExecutionResult
|
||||||
from graphql.type.schema import GraphQLSchema
|
from graphql.type.schema import GraphQLSchema
|
||||||
|
from graphql.execution.middleware import MiddlewareManager
|
||||||
|
|
||||||
from .settings import graphene_settings
|
from .settings import graphene_settings
|
||||||
|
|
||||||
|
@ -86,7 +87,10 @@ class GraphQLView(View):
|
||||||
|
|
||||||
self.schema = self.schema or schema
|
self.schema = self.schema or schema
|
||||||
if middleware is not None:
|
if middleware is not None:
|
||||||
self.middleware = list(instantiate_middleware(middleware))
|
if isinstance(middleware, MiddlewareManager):
|
||||||
|
self.middleware = middleware
|
||||||
|
else:
|
||||||
|
self.middleware = list(instantiate_middleware(middleware))
|
||||||
self.executor = executor
|
self.executor = executor
|
||||||
self.root_value = root_value
|
self.root_value = root_value
|
||||||
self.pretty = self.pretty or pretty
|
self.pretty = self.pretty or pretty
|
||||||
|
|
15
tox.ini
15
tox.ini
|
@ -4,7 +4,14 @@ envlist =
|
||||||
py{36,37,38}-django30,
|
py{36,37,38}-django30,
|
||||||
black,flake8
|
black,flake8
|
||||||
|
|
||||||
[travis:env]
|
[gh-actions]
|
||||||
|
python =
|
||||||
|
2.7: py27
|
||||||
|
3.6: py36
|
||||||
|
3.7: py37
|
||||||
|
3.8: py38
|
||||||
|
|
||||||
|
[gh-actions:env]
|
||||||
DJANGO =
|
DJANGO =
|
||||||
1.11: django111
|
1.11: django111
|
||||||
2.0: django20
|
2.0: django20
|
||||||
|
@ -30,13 +37,13 @@ deps =
|
||||||
commands = {posargs:py.test --cov=graphene_django graphene_django examples}
|
commands = {posargs:py.test --cov=graphene_django graphene_django examples}
|
||||||
|
|
||||||
[testenv:black]
|
[testenv:black]
|
||||||
basepython = python3.7
|
basepython = python3.8
|
||||||
deps = -e.[dev]
|
deps = -e.[dev]
|
||||||
commands =
|
commands =
|
||||||
black --exclude "/migrations/" graphene_django examples setup.py --check
|
black --exclude "/migrations/" graphene_django examples setup.py --check
|
||||||
|
|
||||||
[testenv:flake8]
|
[testenv:flake8]
|
||||||
basepython = python3.7
|
basepython = python3.8
|
||||||
deps = -e.[dev]
|
deps = -e.[dev]
|
||||||
commands =
|
commands =
|
||||||
flake8 graphene_django examples
|
flake8 graphene_django examples setup.py
|
||||||
|
|
Loading…
Reference in New Issue
Block a user