diff --git a/.travis.yml b/.travis.yml index 93f4550f..3dbb00e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -73,13 +73,20 @@ after_success: fi env: matrix: - - TEST_TYPE=build DJANGO_VERSION=1.8 - - TEST_TYPE=build DJANGO_VERSION=1.9 + - TEST_TYPE=build global: secure: SQC0eCWCWw8bZxbLE8vQn+UjJOp3Z1m779s9SMK3lCLwJxro/VCLBZ7hj4xsrq1MtcFO2U2Kqf068symw4Hr/0amYI3HFTCFiwXAC3PAKXeURca03eNO2heku+FtnQcOjBanExTsIBQRLDXMOaUkf3MIztpLJ4LHqMfUupKmw9YSB0v40jDbSN8khBnndFykmOnVVHznFp8USoN5F0CiPpnfEvHnJkaX76lNf7Kc9XNShBTTtJsnsHMhuYQeInt0vg9HSjoIYC38Tv2hmMj1myNdzyrHF+LgRjI6ceGi50ApAnGepXC/DNRhXROfECKez+LON/ZSqBGdJhUILqC8A4WmWmIjNcwitVFp3JGBqO7LULS0BI96EtSLe8rD1rkkdTbjivajkbykM1Q0Tnmg1adzGwLxRUbTq9tJQlTTkHBCuXIkpKb1mAtb/TY7A6BqfnPi2xTc/++qEawUG7ePhscdTj0IBrUfZsUNUYZqD8E8XbSWKIuS3SHE+cZ+s/kdAsm4q+FFAlpZKOYGxIkwvgyfu4/Plfol4b7X6iAP9J3r1Kv0DgBVFst5CXEwzZs19/g0CgokQbCXf1N+xeNnUELl6/fImaR3RKP22EaABoil4z8vzl4EqxqVoH1nfhE+WlpryXsuSaF/1R+WklR7aQ1FwoCk8V8HxM2zrj4tI8k= matrix: fast_finish: true include: + - python: '2.7' + env: DJANGO_VERSION=1.6 + - python: '2.7' + env: DJANGO_VERSION=1.7 + - python: '2.7' + env: DJANGO_VERSION=1.8 + - python: '2.7' + env: DJANGO_VERSION=1.9 - python: '2.7' env: TEST_TYPE=build_website - python: '2.7' diff --git a/graphene/contrib/django/compat.py b/graphene/contrib/django/compat.py new file mode 100644 index 00000000..a5b444c7 --- /dev/null +++ b/graphene/contrib/django/compat.py @@ -0,0 +1,15 @@ +from django.db import models + +try: + UUIDField = models.UUIDField +except AttributeError: + # Improved compatibility for Django 1.6 + class UUIDField(object): + pass + +try: + from django.db.models.related import RelatedObject +except: + # Improved compatibility for Django 1.6 + class RelatedObject(object): + pass diff --git a/graphene/contrib/django/converter.py b/graphene/contrib/django/converter.py index 0722643b..ef1265ac 100644 --- a/graphene/contrib/django/converter.py +++ b/graphene/contrib/django/converter.py @@ -1,17 +1,11 @@ from django.db import models -from .utils import import_single_dispatch from ...core.types.scalars import ID, Boolean, Float, Int, String +from .compat import RelatedObject, UUIDField +from .utils import get_related_model, import_single_dispatch singledispatch = import_single_dispatch() -try: - UUIDField = models.UUIDField -except AttributeError: - # Improved compatibility for Django 1.6 - class UUIDField(object): - pass - @singledispatch def convert_django_field(field): @@ -65,7 +59,15 @@ def convert_field_to_float(field): @convert_django_field.register(models.ManyToOneRel) def convert_field_to_list_or_connection(field): from .fields import DjangoModelField, ConnectionOrListField - model_field = DjangoModelField(field.related_model) + model_field = DjangoModelField(get_related_model(field)) + return ConnectionOrListField(model_field) + + +# For Django 1.6 +@convert_django_field.register(RelatedObject) +def convert_relatedfield_to_djangomodel(field): + from .fields import DjangoModelField, ConnectionOrListField + model_field = DjangoModelField(field.model) return ConnectionOrListField(model_field) @@ -73,4 +75,4 @@ def convert_field_to_list_or_connection(field): @convert_django_field.register(models.ForeignKey) def convert_field_to_djangomodel(field): from .fields import DjangoModelField - return DjangoModelField(field.related_model, description=field.help_text) + return DjangoModelField(get_related_model(field), description=field.help_text) diff --git a/graphene/contrib/django/debug/plugin.py b/graphene/contrib/django/debug/plugin.py index 86f8da58..70cd6741 100644 --- a/graphene/contrib/django/debug/plugin.py +++ b/graphene/contrib/django/debug/plugin.py @@ -2,8 +2,8 @@ from contextlib import contextmanager from django.db import connections -from ....core.types import Field from ....core.schema import GraphQLSchema +from ....core.types import Field from ....plugins import Plugin from .sql.tracking import unwrap_cursor, wrap_cursor from .sql.types import DjangoDebugSQL diff --git a/graphene/contrib/django/debug/sql/types.py b/graphene/contrib/django/debug/sql/types.py index 5df5e9d8..995aeaa2 100644 --- a/graphene/contrib/django/debug/sql/types.py +++ b/graphene/contrib/django/debug/sql/types.py @@ -1,4 +1,4 @@ -from .....core import Float, ObjectType, String, Boolean +from .....core import Boolean, Float, ObjectType, String class DjangoDebugSQL(ObjectType): diff --git a/graphene/contrib/django/fields.py b/graphene/contrib/django/fields.py index f33cd77e..d9d6f3da 100644 --- a/graphene/contrib/django/fields.py +++ b/graphene/contrib/django/fields.py @@ -1,13 +1,13 @@ import warnings -from .utils import get_type_for_model, DJANGO_FILTER_INSTALLED -from .filter.fields import DjangoFilterConnectionField from ...core.exceptions import SkipField from ...core.fields import Field from ...core.types.base import FieldType from ...core.types.definitions import List from ...relay import ConnectionField from ...relay.utils import is_node +from .filter.fields import DjangoFilterConnectionField +from .utils import get_type_for_model class DjangoConnectionField(ConnectionField): diff --git a/graphene/contrib/django/filter/fields.py b/graphene/contrib/django/filter/fields.py index 012ae00a..43196f6e 100644 --- a/graphene/contrib/django/filter/fields.py +++ b/graphene/contrib/django/filter/fields.py @@ -1,6 +1,6 @@ -from graphene.relay import ConnectionField from graphene.contrib.django.filter.resolvers import FilterConnectionResolver from graphene.contrib.django.utils import get_filtering_args_from_filterset +from graphene.relay import ConnectionField class DjangoFilterConnectionField(ConnectionField): diff --git a/graphene/contrib/django/filter/filterset.py b/graphene/contrib/django/filter/filterset.py index 4755eac2..3ecd9680 100644 --- a/graphene/contrib/django/filter/filterset.py +++ b/graphene/contrib/django/filter/filterset.py @@ -2,11 +2,12 @@ import six from django.conf import settings from django.db import models from django.utils.text import capfirst -from django_filters import Filter, MultipleChoiceFilter -from django_filters.filterset import FilterSetMetaclass, FilterSet -from graphql_relay.node.node import from_global_id -from graphene.contrib.django.forms import GlobalIDFormField, GlobalIDMultipleChoiceField +from django_filters import Filter, MultipleChoiceFilter +from django_filters.filterset import FilterSet, FilterSetMetaclass +from graphene.contrib.django.forms import (GlobalIDFormField, + GlobalIDMultipleChoiceField) +from graphql_relay.node.node import from_global_id class GlobalIDFilter(Filter): @@ -45,6 +46,7 @@ GRAPHENE_FILTER_SET_OVERRIDES = { class GrapheneFilterSetMetaclass(FilterSetMetaclass): + def __new__(cls, name, bases, attrs): new_class = super(GrapheneFilterSetMetaclass, cls).__new__(cls, name, bases, attrs) # Customise the filter_overrides for Graphene @@ -84,7 +86,6 @@ class GrapheneFilterSet(six.with_metaclass(GrapheneFilterSetMetaclass, GrapheneF DjangoFilterConnectionField will wrap FilterSets with this class as necessary """ - pass def setup_filterset(filterset_class): diff --git a/graphene/contrib/django/filter/resolvers.py b/graphene/contrib/django/filter/resolvers.py index c2204d6c..76b3e7ad 100644 --- a/graphene/contrib/django/filter/resolvers.py +++ b/graphene/contrib/django/filter/resolvers.py @@ -1,6 +1,7 @@ from django.core.exceptions import ImproperlyConfigured -from graphene.contrib.django.filter.filterset import setup_filterset, custom_filterset_factory +from graphene.contrib.django.filter.filterset import (custom_filterset_factory, + setup_filterset) from graphene.contrib.django.resolvers import BaseQuerySetConnectionResolver diff --git a/graphene/contrib/django/form_converter.py b/graphene/contrib/django/form_converter.py index 826c8c69..de2a40d8 100644 --- a/graphene/contrib/django/form_converter.py +++ b/graphene/contrib/django/form_converter.py @@ -1,9 +1,12 @@ from django import forms from django.forms.fields import BaseTemporalField -from graphene import String, Int, Boolean, Float, ID -from graphene.contrib.django.forms import GlobalIDFormField, GlobalIDMultipleChoiceField + +from graphene import ID, Boolean, Float, Int, String +from graphene.contrib.django.forms import (GlobalIDFormField, + GlobalIDMultipleChoiceField) from graphene.contrib.django.utils import import_single_dispatch from graphene.core.types.definitions import List + singledispatch = import_single_dispatch() try: diff --git a/graphene/contrib/django/forms.py b/graphene/contrib/django/forms.py index f971897b..88f1665e 100644 --- a/graphene/contrib/django/forms.py +++ b/graphene/contrib/django/forms.py @@ -1,7 +1,7 @@ import binascii from django.core.exceptions import ValidationError -from django.forms import Field, IntegerField, CharField, MultipleChoiceField +from django.forms import CharField, Field, IntegerField, MultipleChoiceField from django.utils.translation import ugettext_lazy as _ from graphql_relay import from_global_id diff --git a/graphene/contrib/django/options.py b/graphene/contrib/django/options.py index 55868dd7..dbd88aca 100644 --- a/graphene/contrib/django/options.py +++ b/graphene/contrib/django/options.py @@ -1,7 +1,7 @@ -from .utils import DJANGO_FILTER_INSTALLED from ...core.classtypes.objecttype import ObjectTypeOptions from ...relay.types import Node from ...relay.utils import is_node +from .utils import DJANGO_FILTER_INSTALLED VALID_ATTRS = ('model', 'only_fields', 'exclude_fields') diff --git a/graphene/contrib/django/tests/filter/filters.py b/graphene/contrib/django/tests/filter/filters.py index 4549a83e..94c0dffe 100644 --- a/graphene/contrib/django/tests/filter/filters.py +++ b/graphene/contrib/django/tests/filter/filters.py @@ -1,7 +1,5 @@ import django_filters - -from graphene.contrib.django.tests.models import Reporter -from graphene.contrib.django.tests.models import Article, Pet +from graphene.contrib.django.tests.models import Article, Pet, Reporter class ArticleFilter(django_filters.FilterSet): diff --git a/graphene/contrib/django/tests/filter/test_fields.py b/graphene/contrib/django/tests/filter/test_fields.py index 4c93ca1e..efa1757f 100644 --- a/graphene/contrib/django/tests/filter/test_fields.py +++ b/graphene/contrib/django/tests/filter/test_fields.py @@ -1,14 +1,13 @@ import pytest from graphene import ObjectType, Schema +from graphene.contrib.django import DjangoNode +from graphene.contrib.django.forms import (GlobalIDFormField, + GlobalIDMultipleChoiceField) +from graphene.contrib.django.tests.models import Article, Pet, Reporter from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED from graphene.relay import NodeField - -from graphene.contrib.django import DjangoNode -from graphene.contrib.django.forms import GlobalIDFormField, GlobalIDMultipleChoiceField -from graphene.contrib.django.tests.models import Article, Pet, Reporter - pytestmark = [] if DJANGO_FILTER_INSTALLED: import django_filters @@ -22,21 +21,25 @@ pytestmark.append(pytest.mark.django_db) class ArticleNode(DjangoNode): + class Meta: model = Article class ReporterNode(DjangoNode): + class Meta: model = Reporter class PetNode(DjangoNode): + class Meta: model = Pet schema = Schema() + def assert_arguments(field, *arguments): ignore = ('after', 'before', 'first', 'last', 'orderBy') actual = [ @@ -48,7 +51,7 @@ def assert_arguments(field, *arguments): 'Expected arguments ({}) did not match actual ({})'.format( arguments, actual - ) + ) def assert_orderable(field): @@ -118,6 +121,7 @@ def test_filter_shortcut_filterset_extra_meta(): def test_filter_filterset_information_on_meta(): class ReporterFilterNode(DjangoNode): + class Meta: model = Reporter filter_fields = ['first_name', 'articles'] @@ -130,12 +134,14 @@ def test_filter_filterset_information_on_meta(): def test_filter_filterset_information_on_meta_related(): class ReporterFilterNode(DjangoNode): + class Meta: model = Reporter filter_fields = ['first_name', 'articles'] filter_order_by = True class ArticleFilterNode(DjangoNode): + class Meta: model = Article filter_fields = ['headline', 'reporter'] @@ -164,6 +170,7 @@ def test_global_id_field_implicit(): def test_global_id_field_explicit(): class ArticleIdFilter(django_filters.FilterSet): + class Meta: model = Article fields = ['id'] @@ -193,6 +200,7 @@ def test_global_id_multiple_field_implicit(): def test_global_id_multiple_field_explicit(): class ReporterPetsFilter(django_filters.FilterSet): + class Meta: model = Reporter fields = ['pets'] @@ -214,6 +222,7 @@ def test_global_id_multiple_field_implicit_reverse(): def test_global_id_multiple_field_explicit_reverse(): class ReporterPetsFilter(django_filters.FilterSet): + class Meta: model = Reporter fields = ['articles'] diff --git a/graphene/contrib/django/tests/filter/test_resolvers.py b/graphene/contrib/django/tests/filter/test_resolvers.py index a336cddf..dd9940f0 100644 --- a/graphene/contrib/django/tests/filter/test_resolvers.py +++ b/graphene/contrib/django/tests/filter/test_resolvers.py @@ -1,6 +1,9 @@ import pytest from django.core.exceptions import ImproperlyConfigured +from graphene.contrib.django.tests.models import Article, Reporter +from graphene.contrib.django.tests.test_resolvers import (ArticleNode, + ReporterNode) from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED if DJANGO_FILTER_INSTALLED: @@ -9,9 +12,6 @@ if DJANGO_FILTER_INSTALLED: else: pytestmark = pytest.mark.skipif(True, reason='django_filters not installed') -from graphene.contrib.django.tests.models import Reporter, Article -from graphene.contrib.django.tests.test_resolvers import ReporterNode, ArticleNode - def test_filter_get_filterset_class_explicit(): reporter = Reporter(id=1, first_name='Cookie Monster') diff --git a/graphene/contrib/django/tests/test_converter.py b/graphene/contrib/django/tests/test_converter.py index dcbb3e30..3a02b03a 100644 --- a/graphene/contrib/django/tests/test_converter.py +++ b/graphene/contrib/django/tests/test_converter.py @@ -9,8 +9,8 @@ from graphene.contrib.django.fields import (ConnectionOrListField, from .models import Article, Reporter -def assert_conversion(django_field, graphene_field, *args): - field = django_field(*args, help_text='Custom Help Text') +def assert_conversion(django_field, graphene_field, *args, **kwargs): + field = django_field(help_text='Custom Help Text', *args, **kwargs) graphene_type = convert_django_field(field) assert isinstance(graphene_type, graphene_field) field = graphene_type.as_field() @@ -49,7 +49,7 @@ def test_should_url_convert_string(): def test_should_auto_convert_id(): - assert_conversion(models.AutoField, graphene.ID) + assert_conversion(models.AutoField, graphene.ID, primary_key=True) def test_should_positive_integer_convert_int(): diff --git a/graphene/contrib/django/tests/test_form_converter.py b/graphene/contrib/django/tests/test_form_converter.py index 7492fc51..44d9bec3 100644 --- a/graphene/contrib/django/tests/test_form_converter.py +++ b/graphene/contrib/django/tests/test_form_converter.py @@ -1,10 +1,9 @@ from django import forms -from graphene.core.types import List, ID from py.test import raises import graphene from graphene.contrib.django.form_converter import convert_form_field - +from graphene.core.types import ID, List from .models import Reporter diff --git a/graphene/contrib/django/tests/test_query.py b/graphene/contrib/django/tests/test_query.py index 4b37d517..460c8e22 100644 --- a/graphene/contrib/django/tests/test_query.py +++ b/graphene/contrib/django/tests/test_query.py @@ -7,7 +7,6 @@ from graphene.contrib.django import DjangoNode, DjangoObjectType from .models import Article, Reporter - pytestmark = pytest.mark.django_db diff --git a/graphene/contrib/django/tests/test_resolvers.py b/graphene/contrib/django/tests/test_resolvers.py index fe617666..db1610c9 100644 --- a/graphene/contrib/django/tests/test_resolvers.py +++ b/graphene/contrib/django/tests/test_resolvers.py @@ -3,15 +3,17 @@ from django.db.models.query import QuerySet from graphene.contrib.django import DjangoNode from graphene.contrib.django.resolvers import SimpleQuerySetConnectionResolver -from graphene.contrib.django.tests.models import Reporter, Article +from graphene.contrib.django.tests.models import Article, Reporter class ReporterNode(DjangoNode): + class Meta: model = Reporter class ArticleNode(DjangoNode): + class Meta: model = Article @@ -34,7 +36,7 @@ def test_simple_get_manager_all(): reporter = Reporter(id=1, first_name='Cookie Monster') resolver = SimpleQuerySetConnectionResolver(ReporterNode) resolver(inst=reporter, args={}, info=None) - assert type(resolver.get_manager()) == Manager, 'Resolver did not return a Manager' + assert isinstance(resolver.get_manager(), Manager), 'Resolver did not return a Manager' def test_simple_filter(): diff --git a/graphene/contrib/django/types.py b/graphene/contrib/django/types.py index b961ceed..5b68ebbb 100644 --- a/graphene/contrib/django/types.py +++ b/graphene/contrib/django/types.py @@ -5,7 +5,6 @@ from django.db import models from ...core.classtypes.objecttype import ObjectType, ObjectTypeMeta from ...relay.types import Connection, Node, NodeMeta -from .utils import DJANGO_FILTER_INSTALLED from .converter import convert_django_field from .options import DjangoOptions from .utils import get_reverse_fields, maybe_queryset diff --git a/graphene/contrib/django/utils.py b/graphene/contrib/django/utils.py index 4be3c55f..76f4477c 100644 --- a/graphene/contrib/django/utils.py +++ b/graphene/contrib/django/utils.py @@ -3,9 +3,10 @@ from django.db import models from django.db.models.manager import Manager from django.db.models.query import QuerySet +from graphene import Argument, String from graphene.utils import LazyList -from graphene import Argument, String +from .compat import RelatedObject try: import django_filters # noqa @@ -29,7 +30,12 @@ def get_reverse_fields(model): # Django =>1.9 uses 'rel', django <1.9 uses 'related' related = getattr(attr, 'rel', None) or \ getattr(attr, 'related', None) - if isinstance(related, models.ManyToOneRel): + if isinstance(related, RelatedObject): + # Hack for making it compatible with Django 1.6 + new_related = RelatedObject(related.parent_model, related.model, related.field) + new_related.name = name + yield new_related + elif isinstance(related, models.ManyToOneRel): yield related @@ -70,6 +76,13 @@ def get_filtering_args_from_filterset(filterset_class, type): return args +def get_related_model(field): + if hasattr(field, 'rel'): + # Django 1.6, 1.7 + return field.rel.to + return field.related_model + + def import_single_dispatch(): try: from functools import singledispatch diff --git a/graphene/core/types/field.py b/graphene/core/types/field.py index 17fc9fa2..6cbfff96 100644 --- a/graphene/core/types/field.py +++ b/graphene/core/types/field.py @@ -9,7 +9,8 @@ from ..classtypes.inputobjecttype import InputObjectType from ..classtypes.mutation import Mutation from ..exceptions import SkipField from .argument import Argument, ArgumentsGroup, snake_case_args -from .base import GroupNamedType, LazyType, MountType, NamedType, ArgumentType, OrderedType +from .base import (ArgumentType, GroupNamedType, LazyType, MountType, + NamedType, OrderedType) from .definitions import NonNull diff --git a/setup.py b/setup.py index 74865f3f..ac0e3d0f 100644 --- a/setup.py +++ b/setup.py @@ -66,7 +66,7 @@ setup( ], extras_require={ 'django': [ - 'Django>=1.8.0', + 'Django>=1.6.0', 'singledispatch>=3.4.0.3', 'graphql-django-view>=1.1.0', ],