mirror of
https://github.com/graphql-python/graphene-django.git
synced 2025-07-13 17:52:19 +03:00
Merge remote-tracking branch 'up/master' into enum_conversion_fixes
This commit is contained in:
commit
71ba7fb82b
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -78,3 +78,5 @@ Session.vim
|
|||
*~
|
||||
# auto-generated tag files
|
||||
tags
|
||||
.tox/
|
||||
.pytest_cache/
|
||||
|
|
98
.travis.yml
98
.travis.yml
|
@ -1,62 +1,60 @@
|
|||
language: python
|
||||
sudo: required
|
||||
dist: xenial
|
||||
|
||||
python:
|
||||
- 2.7
|
||||
- 3.4
|
||||
- 3.5
|
||||
- 3.6
|
||||
- 3.7
|
||||
install:
|
||||
- |
|
||||
if [ "$TEST_TYPE" = build ]; then
|
||||
pip install -e .[test]
|
||||
pip install psycopg2==2.8.2 # Required for Django postgres fields testing
|
||||
pip install django==$DJANGO_VERSION
|
||||
python setup.py develop
|
||||
elif [ "$TEST_TYPE" = lint ]; then
|
||||
pip install flake8==3.7.7
|
||||
fi
|
||||
script:
|
||||
- |
|
||||
if [ "$TEST_TYPE" = lint ]; then
|
||||
echo "Checking Python code lint."
|
||||
flake8 graphene_django
|
||||
exit
|
||||
elif [ "$TEST_TYPE" = build ]; then
|
||||
py.test --cov=graphene_django graphene_django examples
|
||||
fi
|
||||
after_success:
|
||||
- |
|
||||
if [ "$TEST_TYPE" = build ]; then
|
||||
coveralls
|
||||
fi
|
||||
- 2.7
|
||||
- 3.4
|
||||
- 3.5
|
||||
- 3.6
|
||||
- 3.7
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- TEST_TYPE=build DJANGO_VERSION=1.11
|
||||
- DJANGO=1.11
|
||||
- DJANGO=2.1
|
||||
- DJANGO=2.2
|
||||
- DJANGO=master
|
||||
|
||||
install:
|
||||
- TOX_ENV=py${TRAVIS_PYTHON_VERSION}-django${DJANGO}
|
||||
- pip install tox
|
||||
- tox -e $TOX_ENV --notest
|
||||
script:
|
||||
- tox -e $TOX_ENV
|
||||
|
||||
after_success:
|
||||
- tox -e $TOX_ENV -- pip install coveralls
|
||||
- tox -e $TOX_ENV -- coveralls $COVERALLS_OPTION
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- python: '3.4'
|
||||
env: TEST_TYPE=build DJANGO_VERSION=2.0
|
||||
- python: '3.5'
|
||||
env: TEST_TYPE=build DJANGO_VERSION=2.0
|
||||
- python: '3.6'
|
||||
env: TEST_TYPE=build DJANGO_VERSION=2.0
|
||||
- python: '3.5'
|
||||
env: TEST_TYPE=build DJANGO_VERSION=2.1
|
||||
- python: '3.6'
|
||||
env: TEST_TYPE=build DJANGO_VERSION=2.1
|
||||
- python: '3.6'
|
||||
env: TEST_TYPE=build DJANGO_VERSION=2.2
|
||||
- python: '3.7'
|
||||
env: TEST_TYPE=build DJANGO_VERSION=2.2
|
||||
- python: '2.7'
|
||||
env: TEST_TYPE=lint
|
||||
- python: '3.6'
|
||||
env: TEST_TYPE=lint
|
||||
- python: '3.7'
|
||||
env: TEST_TYPE=lint
|
||||
- python: 3.5
|
||||
script: tox -e lint
|
||||
exclude:
|
||||
- python: 2.7
|
||||
env: DJANGO=2.1
|
||||
- python: 2.7
|
||||
env: DJANGO=2.2
|
||||
- python: 2.7
|
||||
env: DJANGO=master
|
||||
- python: 3.4
|
||||
env: DJANGO=2.1
|
||||
- python: 3.4
|
||||
env: DJANGO=2.2
|
||||
- python: 3.4
|
||||
env: DJANGO=master
|
||||
- python: 3.5
|
||||
env: DJANGO=master
|
||||
- python: 3.7
|
||||
env: DJANGO=1.10
|
||||
- python: 3.7
|
||||
env: DJANGO=1.11
|
||||
allow_failures:
|
||||
- python: 3.7
|
||||
- env: DJANGO=master
|
||||
|
||||
deploy:
|
||||
provider: pypi
|
||||
user: syrusakbary
|
||||
|
|
|
@ -100,7 +100,7 @@ features of ``django-filter``. This is done by transparently creating a
|
|||
``filter_fields``.
|
||||
|
||||
However, you may find this to be insufficient. In these cases you can
|
||||
create your own ``Filterset`` as follows:
|
||||
create your own ``FilterSet``. You can pass it directly as follows:
|
||||
|
||||
.. code:: python
|
||||
|
||||
|
@ -127,6 +127,33 @@ create your own ``Filterset`` as follows:
|
|||
all_animals = DjangoFilterConnectionField(AnimalNode,
|
||||
filterset_class=AnimalFilter)
|
||||
|
||||
You can also specify the ``FilterSet`` class using the ``filerset_class``
|
||||
parameter when defining your ``DjangoObjectType``, however, this can't be used
|
||||
in unison with the ``filter_fields`` parameter:
|
||||
|
||||
.. code:: python
|
||||
|
||||
class AnimalFilter(django_filters.FilterSet):
|
||||
# Do case-insensitive lookups on 'name'
|
||||
name = django_filters.CharFilter(lookup_expr=['iexact'])
|
||||
|
||||
class Meta:
|
||||
# Assume you have an Animal model defined with the following fields
|
||||
model = Animal
|
||||
fields = ['name', 'genus', 'is_domesticated']
|
||||
|
||||
|
||||
class AnimalNode(DjangoObjectType):
|
||||
class Meta:
|
||||
model = Animal
|
||||
filterset_class = AnimalFilter
|
||||
interfaces = (relay.Node, )
|
||||
|
||||
|
||||
class Query(ObjectType):
|
||||
animal = relay.Node.Field(AnimalNode)
|
||||
all_animals = DjangoFilterConnectionField(AnimalNode)
|
||||
|
||||
The context argument is passed on as the `request argument <http://django-filter.readthedocs.io/en/master/guide/usage.html#request-based-filtering>`__
|
||||
in a ``django_filters.FilterSet`` instance. You can use this to customize your
|
||||
filters to be context-dependent. We could modify the ``AnimalFilter`` above to
|
||||
|
|
|
@ -35,6 +35,8 @@ Advanced Usage
|
|||
The ``--indent`` option can be used to specify the number of indentation spaces to
|
||||
be used in the output. Defaults to `None` which displays all data on a single line.
|
||||
|
||||
The ``--watch`` option can be used to run ``./manage.py graphql_schema`` in watch mode, where it will automatically output a new schema every time there are file changes in your project
|
||||
|
||||
To simplify the command to ``./manage.py graphql_schema``, you can
|
||||
specify the parameters in your settings.py:
|
||||
|
||||
|
|
|
@ -199,7 +199,9 @@ You can use relay with mutations. A Relay mutation must inherit from
|
|||
|
||||
.. code:: python
|
||||
|
||||
import graphene import relay, DjangoObjectType
|
||||
import graphene
|
||||
from graphene import relay
|
||||
from graphene_django import DjangoObjectType
|
||||
from graphql_relay import from_global_id
|
||||
|
||||
from .queries import QuestionType
|
||||
|
@ -214,7 +216,7 @@ You can use relay with mutations. A Relay mutation must inherit from
|
|||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, text, id):
|
||||
question = Question.objects.get(pk=from_global_id(id))
|
||||
question = Question.objects.get(pk=from_global_id(id)[1])
|
||||
question.text = text
|
||||
question.save()
|
||||
return QuestionMutation(question=question)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
graphene
|
||||
graphene-django
|
||||
graphql-core>=2.1rc1
|
||||
django==1.11.19
|
||||
django==1.11.20
|
||||
django-filter>=2
|
||||
|
|
|
@ -180,19 +180,22 @@ def convert_field_to_list_or_connection(field, registry=None):
|
|||
if not _type:
|
||||
return
|
||||
|
||||
description = field.help_text if isinstance(field, models.ManyToManyField) else field.field.help_text
|
||||
|
||||
# If there is a connection, we should transform the field
|
||||
# into a DjangoConnectionField
|
||||
if _type._meta.connection:
|
||||
# Use a DjangoFilterConnectionField if there are
|
||||
# defined filter_fields in the DjangoObjectType Meta
|
||||
if _type._meta.filter_fields:
|
||||
# defined filter_fields or a filterset_class in the
|
||||
# DjangoObjectType Meta
|
||||
if _type._meta.filter_fields or _type._meta.filterset_class:
|
||||
from .filter.fields import DjangoFilterConnectionField
|
||||
|
||||
return DjangoFilterConnectionField(_type)
|
||||
return DjangoFilterConnectionField(_type, description=description)
|
||||
|
||||
return DjangoConnectionField(_type)
|
||||
return DjangoConnectionField(_type, description=description)
|
||||
|
||||
return DjangoListField(_type)
|
||||
return DjangoListField(_type, description=description)
|
||||
|
||||
return Dynamic(dynamic_type)
|
||||
|
||||
|
|
|
@ -50,9 +50,7 @@ def test_should_query_field():
|
|||
"""
|
||||
expected = {
|
||||
"reporter": {"lastName": "ABA"},
|
||||
"_debug": {
|
||||
"sql": [{"rawSql": str(Reporter.objects.order_by("pk")[:1].query)}]
|
||||
},
|
||||
"_debug": {"sql": [{"rawSql": str(Reporter.objects.order_by("pk")[:1].query)}]},
|
||||
}
|
||||
schema = graphene.Schema(query=Query)
|
||||
result = schema.execute(
|
||||
|
|
|
@ -40,8 +40,10 @@ class DjangoFilterConnectionField(DjangoConnectionField):
|
|||
if self._extra_filter_meta:
|
||||
meta.update(self._extra_filter_meta)
|
||||
|
||||
filterset_class = self._provided_filterset_class or (
|
||||
self.node_type._meta.filterset_class)
|
||||
self._filterset_class = get_filterset_class(
|
||||
self._provided_filterset_class, **meta
|
||||
filterset_class, **meta
|
||||
)
|
||||
|
||||
return self._filterset_class
|
||||
|
|
|
@ -227,6 +227,73 @@ def test_filter_filterset_information_on_meta_related():
|
|||
assert_not_orderable(articles_field)
|
||||
|
||||
|
||||
def test_filter_filterset_class_filter_fields_exception():
|
||||
with pytest.raises(Exception):
|
||||
class ReporterFilter(FilterSet):
|
||||
class Meta:
|
||||
model = Reporter
|
||||
fields = ["first_name", "articles"]
|
||||
|
||||
class ReporterFilterNode(DjangoObjectType):
|
||||
class Meta:
|
||||
model = Reporter
|
||||
interfaces = (Node,)
|
||||
filterset_class = ReporterFilter
|
||||
filter_fields = ["first_name", "articles"]
|
||||
|
||||
|
||||
def test_filter_filterset_class_information_on_meta():
|
||||
class ReporterFilter(FilterSet):
|
||||
class Meta:
|
||||
model = Reporter
|
||||
fields = ["first_name", "articles"]
|
||||
|
||||
class ReporterFilterNode(DjangoObjectType):
|
||||
class Meta:
|
||||
model = Reporter
|
||||
interfaces = (Node,)
|
||||
filterset_class = ReporterFilter
|
||||
|
||||
field = DjangoFilterConnectionField(ReporterFilterNode)
|
||||
assert_arguments(field, "first_name", "articles")
|
||||
assert_not_orderable(field)
|
||||
|
||||
|
||||
def test_filter_filterset_class_information_on_meta_related():
|
||||
class ReporterFilter(FilterSet):
|
||||
class Meta:
|
||||
model = Reporter
|
||||
fields = ["first_name", "articles"]
|
||||
|
||||
class ArticleFilter(FilterSet):
|
||||
class Meta:
|
||||
model = Article
|
||||
fields = ["headline", "reporter"]
|
||||
|
||||
class ReporterFilterNode(DjangoObjectType):
|
||||
class Meta:
|
||||
model = Reporter
|
||||
interfaces = (Node,)
|
||||
filterset_class = ReporterFilter
|
||||
|
||||
class ArticleFilterNode(DjangoObjectType):
|
||||
class Meta:
|
||||
model = Article
|
||||
interfaces = (Node,)
|
||||
filterset_class = ArticleFilter
|
||||
|
||||
class Query(ObjectType):
|
||||
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
|
||||
all_articles = DjangoFilterConnectionField(ArticleFilterNode)
|
||||
reporter = Field(ReporterFilterNode)
|
||||
article = Field(ArticleFilterNode)
|
||||
|
||||
schema = Schema(query=Query)
|
||||
articles_field = ReporterFilterNode._meta.fields["articles"].get_type()
|
||||
assert_arguments(articles_field, "headline", "reporter")
|
||||
assert_not_orderable(articles_field)
|
||||
|
||||
|
||||
def test_filter_filterset_related_results():
|
||||
class ReporterFilterNode(DjangoObjectType):
|
||||
class Meta:
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import importlib
|
||||
import json
|
||||
import functools
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.utils import autoreload
|
||||
|
||||
from graphene_django.settings import graphene_settings
|
||||
|
||||
|
@ -32,6 +34,14 @@ class CommandArguments(BaseCommand):
|
|||
help="Output file indent (default: None)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--watch",
|
||||
dest="watch",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Updates the schema on file changes (default: False)",
|
||||
)
|
||||
|
||||
|
||||
class Command(CommandArguments):
|
||||
help = "Dump Graphene schema JSON to file"
|
||||
|
@ -41,6 +51,18 @@ class Command(CommandArguments):
|
|||
with open(out, "w") as outfile:
|
||||
json.dump(schema_dict, outfile, indent=indent, sort_keys=True)
|
||||
|
||||
def get_schema(self, schema, out, indent):
|
||||
schema_dict = {"data": schema.introspect()}
|
||||
if out == "-":
|
||||
self.stdout.write(json.dumps(schema_dict, indent=indent, sort_keys=True))
|
||||
else:
|
||||
self.save_file(out, schema_dict, indent)
|
||||
|
||||
style = getattr(self, "style", None)
|
||||
success = getattr(style, "SUCCESS", lambda x: x)
|
||||
|
||||
self.stdout.write(success("Successfully dumped GraphQL schema to %s" % out))
|
||||
|
||||
def handle(self, *args, **options):
|
||||
options_schema = options.get("schema")
|
||||
|
||||
|
@ -63,13 +85,10 @@ class Command(CommandArguments):
|
|||
)
|
||||
|
||||
indent = options.get("indent")
|
||||
schema_dict = {"data": schema.introspect()}
|
||||
if out == "-":
|
||||
self.stdout.write(json.dumps(schema_dict, indent=indent, sort_keys=True))
|
||||
watch = options.get("watch")
|
||||
if watch:
|
||||
autoreload.run_with_reloader(
|
||||
functools.partial(self.get_schema, schema, out, indent)
|
||||
)
|
||||
else:
|
||||
self.save_file(out, schema_dict, indent)
|
||||
|
||||
style = getattr(self, "style", None)
|
||||
success = getattr(style, "SUCCESS", lambda x: x)
|
||||
|
||||
self.stdout.write(success("Successfully dumped GraphQL schema to %s" % out))
|
||||
self.get_schema(schema, out, indent)
|
||||
|
|
|
@ -4,3 +4,8 @@ from django.db import models
|
|||
class MyFakeModel(models.Model):
|
||||
cool_name = models.CharField(max_length=50)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
|
||||
class MyFakeModelWithPassword(models.Model):
|
||||
cool_name = models.CharField(max_length=50)
|
||||
password = models.CharField(max_length=50)
|
||||
|
|
|
@ -27,6 +27,8 @@ def fields_for_serializer(serializer, only_fields, exclude_fields, is_input=Fals
|
|||
name
|
||||
in exclude_fields # or
|
||||
# name in already_created_fields
|
||||
) or (
|
||||
field.write_only and not is_input # don't show write_only fields in Query
|
||||
)
|
||||
|
||||
if is_not_in_only or is_excluded:
|
||||
|
@ -138,6 +140,7 @@ class SerializerMutation(ClientIDMutation):
|
|||
|
||||
kwargs = {}
|
||||
for f, field in serializer.fields.items():
|
||||
kwargs[f] = field.get_attribute(obj)
|
||||
if not field.write_only:
|
||||
kwargs[f] = field.get_attribute(obj)
|
||||
|
||||
return cls(errors=None, **kwargs)
|
||||
|
|
|
@ -7,7 +7,7 @@ from py.test import mark
|
|||
from rest_framework import serializers
|
||||
|
||||
from ...types import DjangoObjectType
|
||||
from ..models import MyFakeModel
|
||||
from ..models import MyFakeModel, MyFakeModelWithPassword
|
||||
from ..mutation import SerializerMutation
|
||||
|
||||
|
||||
|
@ -86,6 +86,47 @@ def test_exclude_fields():
|
|||
assert "created" not in MyMutation.Input._meta.fields
|
||||
|
||||
|
||||
@mark.django_db
|
||||
def test_write_only_field():
|
||||
class WriteOnlyFieldModelSerializer(serializers.ModelSerializer):
|
||||
password = serializers.CharField(write_only=True)
|
||||
|
||||
class Meta:
|
||||
model = MyFakeModelWithPassword
|
||||
fields = ["cool_name", "password"]
|
||||
|
||||
class MyMutation(SerializerMutation):
|
||||
class Meta:
|
||||
serializer_class = WriteOnlyFieldModelSerializer
|
||||
|
||||
result = MyMutation.mutate_and_get_payload(
|
||||
None, mock_info(), **{"cool_name": "New Narf", "password": "admin"}
|
||||
)
|
||||
|
||||
assert hasattr(result, "cool_name")
|
||||
assert not hasattr(result, "password"), "'password' is write_only field and shouldn't be visible"
|
||||
|
||||
|
||||
@mark.django_db
|
||||
def test_write_only_field_using_extra_kwargs():
|
||||
class WriteOnlyFieldModelSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = MyFakeModelWithPassword
|
||||
fields = ["cool_name", "password"]
|
||||
extra_kwargs = {"password": {"write_only": True}}
|
||||
|
||||
class MyMutation(SerializerMutation):
|
||||
class Meta:
|
||||
serializer_class = WriteOnlyFieldModelSerializer
|
||||
|
||||
result = MyMutation.mutate_and_get_payload(
|
||||
None, mock_info(), **{"cool_name": "New Narf", "password": "admin"}
|
||||
)
|
||||
|
||||
assert hasattr(result, "cool_name")
|
||||
assert not hasattr(result, "password"), "'password' is write_only field and shouldn't be visible"
|
||||
|
||||
|
||||
def test_nested_model():
|
||||
class MyFakeModelGrapheneType(DjangoObjectType):
|
||||
class Meta:
|
||||
|
|
|
@ -66,6 +66,11 @@ class Reporter(models.Model):
|
|||
self.__class__ = CNNReporter
|
||||
|
||||
|
||||
class CNNReporterManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return super(CNNReporterManager, self).get_queryset().filter(reporter_type=2)
|
||||
|
||||
|
||||
class CNNReporter(Reporter):
|
||||
"""
|
||||
This class is a proxy model for Reporter, used for testing
|
||||
|
@ -75,6 +80,8 @@ class CNNReporter(Reporter):
|
|||
class Meta:
|
||||
proxy = True
|
||||
|
||||
objects = CNNReporterManager()
|
||||
|
||||
|
||||
class Article(models.Model):
|
||||
headline = models.CharField(max_length=100)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import base64
|
||||
import datetime
|
||||
|
||||
import pytest
|
||||
|
@ -7,6 +8,7 @@ from py.test import raises
|
|||
|
||||
from django.db.models import Q
|
||||
|
||||
from graphql_relay import to_global_id
|
||||
import graphene
|
||||
from graphene.relay import Node
|
||||
|
||||
|
@ -226,6 +228,62 @@ def test_should_node():
|
|||
assert result.data == expected
|
||||
|
||||
|
||||
def test_should_query_onetoone_fields():
|
||||
film = Film(id=1)
|
||||
film_details = FilmDetails(id=1, film=film)
|
||||
|
||||
class FilmNode(DjangoObjectType):
|
||||
class Meta:
|
||||
model = Film
|
||||
interfaces = (Node,)
|
||||
|
||||
class FilmDetailsNode(DjangoObjectType):
|
||||
class Meta:
|
||||
model = FilmDetails
|
||||
interfaces = (Node,)
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
film = graphene.Field(FilmNode)
|
||||
film_details = graphene.Field(FilmDetailsNode)
|
||||
|
||||
def resolve_film(root, info):
|
||||
return film
|
||||
|
||||
def resolve_film_details(root, info):
|
||||
return film_details
|
||||
|
||||
query = """
|
||||
query FilmQuery {
|
||||
filmDetails {
|
||||
id
|
||||
film {
|
||||
id
|
||||
}
|
||||
}
|
||||
film {
|
||||
id
|
||||
details {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
expected = {
|
||||
"filmDetails": {
|
||||
"id": "RmlsbURldGFpbHNOb2RlOjE=",
|
||||
"film": {"id": "RmlsbU5vZGU6MQ=="},
|
||||
},
|
||||
"film": {
|
||||
"id": "RmlsbU5vZGU6MQ==",
|
||||
"details": {"id": "RmlsbURldGFpbHNOb2RlOjE="},
|
||||
},
|
||||
}
|
||||
schema = graphene.Schema(query=Query)
|
||||
result = schema.execute(query)
|
||||
assert not result.errors
|
||||
assert result.data == expected
|
||||
|
||||
|
||||
def test_should_query_connectionfields():
|
||||
class ReporterType(DjangoObjectType):
|
||||
class Meta:
|
||||
|
@ -895,8 +953,7 @@ def test_should_handle_inherited_choices():
|
|||
|
||||
def test_proxy_model_support():
|
||||
"""
|
||||
This test asserts that we can query for all Reporters,
|
||||
even if some are of a proxy model type at runtime.
|
||||
This test asserts that we can query for all Reporters and proxied Reporters.
|
||||
"""
|
||||
|
||||
class ReporterType(DjangoObjectType):
|
||||
|
@ -905,11 +962,17 @@ def test_proxy_model_support():
|
|||
interfaces = (Node,)
|
||||
use_connection = True
|
||||
|
||||
reporter_1 = Reporter.objects.create(
|
||||
class CNNReporterType(DjangoObjectType):
|
||||
class Meta:
|
||||
model = CNNReporter
|
||||
interfaces = (Node,)
|
||||
use_connection = True
|
||||
|
||||
reporter = Reporter.objects.create(
|
||||
first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
|
||||
)
|
||||
|
||||
reporter_2 = CNNReporter.objects.create(
|
||||
cnn_reporter = CNNReporter.objects.create(
|
||||
first_name="Some",
|
||||
last_name="Guy",
|
||||
email="someguy@cnn.com",
|
||||
|
@ -919,6 +982,7 @@ def test_proxy_model_support():
|
|||
|
||||
class Query(graphene.ObjectType):
|
||||
all_reporters = DjangoConnectionField(ReporterType)
|
||||
cnn_reporters = DjangoConnectionField(CNNReporterType)
|
||||
|
||||
schema = graphene.Schema(query=Query)
|
||||
query = """
|
||||
|
@ -930,14 +994,26 @@ def test_proxy_model_support():
|
|||
}
|
||||
}
|
||||
}
|
||||
cnnReporters {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
expected = {
|
||||
"allReporters": {
|
||||
"edges": [
|
||||
{"node": {"id": "UmVwb3J0ZXJUeXBlOjE="}},
|
||||
{"node": {"id": "UmVwb3J0ZXJUeXBlOjI="}},
|
||||
{"node": {"id": to_global_id("ReporterType", reporter.id)}},
|
||||
{"node": {"id": to_global_id("ReporterType", cnn_reporter.id)}},
|
||||
]
|
||||
},
|
||||
"cnnReporters": {
|
||||
"edges": [
|
||||
{"node": {"id": to_global_id("CNNReporterType", cnn_reporter.id)}}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -947,68 +1023,6 @@ def test_proxy_model_support():
|
|||
assert result.data == expected
|
||||
|
||||
|
||||
def test_proxy_model_fails():
|
||||
"""
|
||||
This test asserts that if you try to query for a proxy model,
|
||||
that query will fail with:
|
||||
GraphQLError('Expected value of type "CNNReporterType" but got:
|
||||
CNNReporter.',)
|
||||
|
||||
This is because a proxy model has the identical model definition
|
||||
to its superclass, and defines its behavior at runtime, rather than
|
||||
at the database level. Currently, filtering objects of the proxy models'
|
||||
type isn't supported. It would require a field on the model that would
|
||||
represent the type, and it doesn't seem like there is a clear way to
|
||||
enforce this pattern across all projects
|
||||
"""
|
||||
|
||||
class CNNReporterType(DjangoObjectType):
|
||||
class Meta:
|
||||
model = CNNReporter
|
||||
interfaces = (Node,)
|
||||
use_connection = True
|
||||
|
||||
reporter_1 = Reporter.objects.create(
|
||||
first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
|
||||
)
|
||||
|
||||
reporter_2 = CNNReporter.objects.create(
|
||||
first_name="Some",
|
||||
last_name="Guy",
|
||||
email="someguy@cnn.com",
|
||||
a_choice=1,
|
||||
reporter_type=2, # set this guy to be CNN
|
||||
)
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
all_reporters = DjangoConnectionField(CNNReporterType)
|
||||
|
||||
schema = graphene.Schema(query=Query)
|
||||
query = """
|
||||
query ProxyModelQuery {
|
||||
allReporters {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
expected = {
|
||||
"allReporters": {
|
||||
"edges": [
|
||||
{"node": {"id": "UmVwb3J0ZXJUeXBlOjE="}},
|
||||
{"node": {"id": "UmVwb3J0ZXJUeXBlOjI="}},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
result = schema.execute(query)
|
||||
assert result.errors
|
||||
|
||||
|
||||
def test_should_resolve_get_queryset_connectionfields():
|
||||
reporter_1 = Reporter.objects.create(
|
||||
first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
|
||||
|
|
|
@ -45,6 +45,7 @@ class DjangoObjectTypeOptions(ObjectTypeOptions):
|
|||
connection = None # type: Type[Connection]
|
||||
|
||||
filter_fields = ()
|
||||
filterset_class = None
|
||||
|
||||
|
||||
class DjangoObjectType(ObjectType):
|
||||
|
@ -57,6 +58,7 @@ class DjangoObjectType(ObjectType):
|
|||
only_fields=(),
|
||||
exclude_fields=(),
|
||||
filter_fields=None,
|
||||
filterset_class=None,
|
||||
connection=None,
|
||||
connection_class=None,
|
||||
use_connection=None,
|
||||
|
@ -76,8 +78,14 @@ class DjangoObjectType(ObjectType):
|
|||
'Registry, received "{}".'
|
||||
).format(cls.__name__, registry)
|
||||
|
||||
if not DJANGO_FILTER_INSTALLED and filter_fields:
|
||||
raise Exception("Can only set filter_fields if Django-Filter is installed")
|
||||
if filter_fields and filterset_class:
|
||||
raise Exception("Can't set both filter_fields and filterset_class")
|
||||
|
||||
if not DJANGO_FILTER_INSTALLED and (filter_fields or filterset_class):
|
||||
raise Exception((
|
||||
"Can only set filter_fields or filterset_class if "
|
||||
"Django-Filter is installed"
|
||||
))
|
||||
|
||||
django_fields = yank_fields_from_attrs(
|
||||
construct_fields(model, registry, only_fields, exclude_fields), _as=Field
|
||||
|
@ -108,6 +116,7 @@ class DjangoObjectType(ObjectType):
|
|||
_meta.model = model
|
||||
_meta.registry = registry
|
||||
_meta.filter_fields = filter_fields
|
||||
_meta.filterset_class = filterset_class
|
||||
_meta.fields = django_fields
|
||||
_meta.connection = connection
|
||||
|
||||
|
@ -131,7 +140,11 @@ class DjangoObjectType(ObjectType):
|
|||
if not is_valid_django_model(type(root)):
|
||||
raise Exception(('Received incompatible instance "{}".').format(root))
|
||||
|
||||
model = root._meta.model._meta.concrete_model
|
||||
if cls._meta.model._meta.proxy:
|
||||
model = root._meta.model
|
||||
else:
|
||||
model = root._meta.model._meta.concrete_model
|
||||
|
||||
return model == cls._meta.model
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -22,7 +22,7 @@ class GraphQLTestCase(TestCase):
|
|||
"Variable GRAPHQL_SCHEMA not defined in GraphQLTestCase."
|
||||
)
|
||||
|
||||
cls._client = Client(cls.GRAPHQL_SCHEMA)
|
||||
cls._client = Client()
|
||||
|
||||
def query(self, query, op_name=None, input_data=None):
|
||||
"""
|
||||
|
|
|
@ -18,7 +18,8 @@ def get_reverse_fields(model, local_field_names):
|
|||
if name in local_field_names:
|
||||
continue
|
||||
|
||||
related = getattr(attr, "rel", None)
|
||||
# "rel" for FK and M2M relations and "related" for O2O Relations
|
||||
related = getattr(attr, "rel", None) or getattr(attr, "related", None)
|
||||
if isinstance(related, models.ManyToOneRel):
|
||||
yield (name, related)
|
||||
elif isinstance(related, models.ManyToManyRel) and not related.symmetrical:
|
||||
|
|
31
tox.ini
Normal file
31
tox.ini
Normal file
|
@ -0,0 +1,31 @@
|
|||
[tox]
|
||||
envlist = py{2.7,3.4,3.5,3.6,3.7,pypy,pypy3}-django{1.10,1.11,2.0,2.1,2.2,master},lint
|
||||
|
||||
[testenv]
|
||||
passenv = *
|
||||
usedevelop = True
|
||||
setenv =
|
||||
DJANGO_SETTINGS_MODULE=django_test_settings
|
||||
basepython =
|
||||
py2.7: python2.7
|
||||
py3.4: python3.4
|
||||
py3.5: python3.5
|
||||
py3.6: python3.6
|
||||
py3.7: python3.7
|
||||
pypypy: pypy
|
||||
pypypy3: pypy3
|
||||
deps =
|
||||
-e.[test]
|
||||
psycopg2
|
||||
django1.10: Django>=1.10,<1.11
|
||||
django1.11: Django>=1.11,<1.12
|
||||
django2.0: Django>=2.0
|
||||
django2.1: Django>=2.1
|
||||
djangomaster: https://github.com/django/django/archive/master.zip
|
||||
commands = {posargs:py.test --cov=graphene_django graphene_django examples}
|
||||
|
||||
[testenv:lint]
|
||||
basepython = python
|
||||
deps =
|
||||
prospector
|
||||
commands = prospector graphene_django -0
|
Loading…
Reference in New Issue
Block a user