Fixed compatibility with Django 1.6, 1.7, 1.8 and 1.9

This commit is contained in:
Syrus Akbary 2015-12-12 00:40:26 -08:00
parent 35d78320e8
commit 8eaa2cfc49
23 changed files with 101 additions and 52 deletions

View File

@ -73,13 +73,20 @@ after_success:
fi fi
env: env:
matrix: matrix:
- TEST_TYPE=build DJANGO_VERSION=1.8 - TEST_TYPE=build
- TEST_TYPE=build DJANGO_VERSION=1.9
global: global:
secure: SQC0eCWCWw8bZxbLE8vQn+UjJOp3Z1m779s9SMK3lCLwJxro/VCLBZ7hj4xsrq1MtcFO2U2Kqf068symw4Hr/0amYI3HFTCFiwXAC3PAKXeURca03eNO2heku+FtnQcOjBanExTsIBQRLDXMOaUkf3MIztpLJ4LHqMfUupKmw9YSB0v40jDbSN8khBnndFykmOnVVHznFp8USoN5F0CiPpnfEvHnJkaX76lNf7Kc9XNShBTTtJsnsHMhuYQeInt0vg9HSjoIYC38Tv2hmMj1myNdzyrHF+LgRjI6ceGi50ApAnGepXC/DNRhXROfECKez+LON/ZSqBGdJhUILqC8A4WmWmIjNcwitVFp3JGBqO7LULS0BI96EtSLe8rD1rkkdTbjivajkbykM1Q0Tnmg1adzGwLxRUbTq9tJQlTTkHBCuXIkpKb1mAtb/TY7A6BqfnPi2xTc/++qEawUG7ePhscdTj0IBrUfZsUNUYZqD8E8XbSWKIuS3SHE+cZ+s/kdAsm4q+FFAlpZKOYGxIkwvgyfu4/Plfol4b7X6iAP9J3r1Kv0DgBVFst5CXEwzZs19/g0CgokQbCXf1N+xeNnUELl6/fImaR3RKP22EaABoil4z8vzl4EqxqVoH1nfhE+WlpryXsuSaF/1R+WklR7aQ1FwoCk8V8HxM2zrj4tI8k= secure: SQC0eCWCWw8bZxbLE8vQn+UjJOp3Z1m779s9SMK3lCLwJxro/VCLBZ7hj4xsrq1MtcFO2U2Kqf068symw4Hr/0amYI3HFTCFiwXAC3PAKXeURca03eNO2heku+FtnQcOjBanExTsIBQRLDXMOaUkf3MIztpLJ4LHqMfUupKmw9YSB0v40jDbSN8khBnndFykmOnVVHznFp8USoN5F0CiPpnfEvHnJkaX76lNf7Kc9XNShBTTtJsnsHMhuYQeInt0vg9HSjoIYC38Tv2hmMj1myNdzyrHF+LgRjI6ceGi50ApAnGepXC/DNRhXROfECKez+LON/ZSqBGdJhUILqC8A4WmWmIjNcwitVFp3JGBqO7LULS0BI96EtSLe8rD1rkkdTbjivajkbykM1Q0Tnmg1adzGwLxRUbTq9tJQlTTkHBCuXIkpKb1mAtb/TY7A6BqfnPi2xTc/++qEawUG7ePhscdTj0IBrUfZsUNUYZqD8E8XbSWKIuS3SHE+cZ+s/kdAsm4q+FFAlpZKOYGxIkwvgyfu4/Plfol4b7X6iAP9J3r1Kv0DgBVFst5CXEwzZs19/g0CgokQbCXf1N+xeNnUELl6/fImaR3RKP22EaABoil4z8vzl4EqxqVoH1nfhE+WlpryXsuSaF/1R+WklR7aQ1FwoCk8V8HxM2zrj4tI8k=
matrix: matrix:
fast_finish: true fast_finish: true
include: 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' - python: '2.7'
env: TEST_TYPE=build_website env: TEST_TYPE=build_website
- python: '2.7' - python: '2.7'

View File

@ -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

View File

@ -1,17 +1,11 @@
from django.db import models from django.db import models
from .utils import import_single_dispatch
from ...core.types.scalars import ID, Boolean, Float, Int, String 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() singledispatch = import_single_dispatch()
try:
UUIDField = models.UUIDField
except AttributeError:
# Improved compatibility for Django 1.6
class UUIDField(object):
pass
@singledispatch @singledispatch
def convert_django_field(field): def convert_django_field(field):
@ -65,7 +59,15 @@ def convert_field_to_float(field):
@convert_django_field.register(models.ManyToOneRel) @convert_django_field.register(models.ManyToOneRel)
def convert_field_to_list_or_connection(field): def convert_field_to_list_or_connection(field):
from .fields import DjangoModelField, ConnectionOrListField 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) return ConnectionOrListField(model_field)
@ -73,4 +75,4 @@ def convert_field_to_list_or_connection(field):
@convert_django_field.register(models.ForeignKey) @convert_django_field.register(models.ForeignKey)
def convert_field_to_djangomodel(field): def convert_field_to_djangomodel(field):
from .fields import DjangoModelField from .fields import DjangoModelField
return DjangoModelField(field.related_model, description=field.help_text) return DjangoModelField(get_related_model(field), description=field.help_text)

View File

@ -2,8 +2,8 @@ from contextlib import contextmanager
from django.db import connections from django.db import connections
from ....core.types import Field
from ....core.schema import GraphQLSchema from ....core.schema import GraphQLSchema
from ....core.types import Field
from ....plugins import Plugin from ....plugins import Plugin
from .sql.tracking import unwrap_cursor, wrap_cursor from .sql.tracking import unwrap_cursor, wrap_cursor
from .sql.types import DjangoDebugSQL from .sql.types import DjangoDebugSQL

View File

@ -1,4 +1,4 @@
from .....core import Float, ObjectType, String, Boolean from .....core import Boolean, Float, ObjectType, String
class DjangoDebugSQL(ObjectType): class DjangoDebugSQL(ObjectType):

View File

@ -1,13 +1,13 @@
import warnings import warnings
from .utils import get_type_for_model, DJANGO_FILTER_INSTALLED
from .filter.fields import DjangoFilterConnectionField
from ...core.exceptions import SkipField from ...core.exceptions import SkipField
from ...core.fields import Field from ...core.fields import Field
from ...core.types.base import FieldType from ...core.types.base import FieldType
from ...core.types.definitions import List from ...core.types.definitions import List
from ...relay import ConnectionField from ...relay import ConnectionField
from ...relay.utils import is_node from ...relay.utils import is_node
from .filter.fields import DjangoFilterConnectionField
from .utils import get_type_for_model
class DjangoConnectionField(ConnectionField): class DjangoConnectionField(ConnectionField):

View File

@ -1,6 +1,6 @@
from graphene.relay import ConnectionField
from graphene.contrib.django.filter.resolvers import FilterConnectionResolver from graphene.contrib.django.filter.resolvers import FilterConnectionResolver
from graphene.contrib.django.utils import get_filtering_args_from_filterset from graphene.contrib.django.utils import get_filtering_args_from_filterset
from graphene.relay import ConnectionField
class DjangoFilterConnectionField(ConnectionField): class DjangoFilterConnectionField(ConnectionField):

View File

@ -2,11 +2,12 @@ import six
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.utils.text import capfirst 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): class GlobalIDFilter(Filter):
@ -45,6 +46,7 @@ GRAPHENE_FILTER_SET_OVERRIDES = {
class GrapheneFilterSetMetaclass(FilterSetMetaclass): class GrapheneFilterSetMetaclass(FilterSetMetaclass):
def __new__(cls, name, bases, attrs): def __new__(cls, name, bases, attrs):
new_class = super(GrapheneFilterSetMetaclass, cls).__new__(cls, name, bases, attrs) new_class = super(GrapheneFilterSetMetaclass, cls).__new__(cls, name, bases, attrs)
# Customise the filter_overrides for Graphene # 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 DjangoFilterConnectionField will wrap FilterSets with this class as
necessary necessary
""" """
pass
def setup_filterset(filterset_class): def setup_filterset(filterset_class):

View File

@ -1,6 +1,7 @@
from django.core.exceptions import ImproperlyConfigured 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 from graphene.contrib.django.resolvers import BaseQuerySetConnectionResolver

View File

@ -1,9 +1,12 @@
from django import forms from django import forms
from django.forms.fields import BaseTemporalField 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.contrib.django.utils import import_single_dispatch
from graphene.core.types.definitions import List from graphene.core.types.definitions import List
singledispatch = import_single_dispatch() singledispatch = import_single_dispatch()
try: try:

View File

@ -1,7 +1,7 @@
import binascii import binascii
from django.core.exceptions import ValidationError 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 django.utils.translation import ugettext_lazy as _
from graphql_relay import from_global_id from graphql_relay import from_global_id

View File

@ -1,7 +1,7 @@
from .utils import DJANGO_FILTER_INSTALLED
from ...core.classtypes.objecttype import ObjectTypeOptions from ...core.classtypes.objecttype import ObjectTypeOptions
from ...relay.types import Node from ...relay.types import Node
from ...relay.utils import is_node from ...relay.utils import is_node
from .utils import DJANGO_FILTER_INSTALLED
VALID_ATTRS = ('model', 'only_fields', 'exclude_fields') VALID_ATTRS = ('model', 'only_fields', 'exclude_fields')

View File

@ -1,7 +1,5 @@
import django_filters import django_filters
from graphene.contrib.django.tests.models import Article, Pet, Reporter
from graphene.contrib.django.tests.models import Reporter
from graphene.contrib.django.tests.models import Article, Pet
class ArticleFilter(django_filters.FilterSet): class ArticleFilter(django_filters.FilterSet):

View File

@ -1,14 +1,13 @@
import pytest import pytest
from graphene import ObjectType, Schema 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.contrib.django.utils import DJANGO_FILTER_INSTALLED
from graphene.relay import NodeField 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 = [] pytestmark = []
if DJANGO_FILTER_INSTALLED: if DJANGO_FILTER_INSTALLED:
import django_filters import django_filters
@ -22,21 +21,25 @@ pytestmark.append(pytest.mark.django_db)
class ArticleNode(DjangoNode): class ArticleNode(DjangoNode):
class Meta: class Meta:
model = Article model = Article
class ReporterNode(DjangoNode): class ReporterNode(DjangoNode):
class Meta: class Meta:
model = Reporter model = Reporter
class PetNode(DjangoNode): class PetNode(DjangoNode):
class Meta: class Meta:
model = Pet model = Pet
schema = Schema() schema = Schema()
def assert_arguments(field, *arguments): def assert_arguments(field, *arguments):
ignore = ('after', 'before', 'first', 'last', 'orderBy') ignore = ('after', 'before', 'first', 'last', 'orderBy')
actual = [ actual = [
@ -48,7 +51,7 @@ def assert_arguments(field, *arguments):
'Expected arguments ({}) did not match actual ({})'.format( 'Expected arguments ({}) did not match actual ({})'.format(
arguments, arguments,
actual actual
) )
def assert_orderable(field): def assert_orderable(field):
@ -118,6 +121,7 @@ def test_filter_shortcut_filterset_extra_meta():
def test_filter_filterset_information_on_meta(): def test_filter_filterset_information_on_meta():
class ReporterFilterNode(DjangoNode): class ReporterFilterNode(DjangoNode):
class Meta: class Meta:
model = Reporter model = Reporter
filter_fields = ['first_name', 'articles'] filter_fields = ['first_name', 'articles']
@ -130,12 +134,14 @@ def test_filter_filterset_information_on_meta():
def test_filter_filterset_information_on_meta_related(): def test_filter_filterset_information_on_meta_related():
class ReporterFilterNode(DjangoNode): class ReporterFilterNode(DjangoNode):
class Meta: class Meta:
model = Reporter model = Reporter
filter_fields = ['first_name', 'articles'] filter_fields = ['first_name', 'articles']
filter_order_by = True filter_order_by = True
class ArticleFilterNode(DjangoNode): class ArticleFilterNode(DjangoNode):
class Meta: class Meta:
model = Article model = Article
filter_fields = ['headline', 'reporter'] filter_fields = ['headline', 'reporter']
@ -164,6 +170,7 @@ def test_global_id_field_implicit():
def test_global_id_field_explicit(): def test_global_id_field_explicit():
class ArticleIdFilter(django_filters.FilterSet): class ArticleIdFilter(django_filters.FilterSet):
class Meta: class Meta:
model = Article model = Article
fields = ['id'] fields = ['id']
@ -193,6 +200,7 @@ def test_global_id_multiple_field_implicit():
def test_global_id_multiple_field_explicit(): def test_global_id_multiple_field_explicit():
class ReporterPetsFilter(django_filters.FilterSet): class ReporterPetsFilter(django_filters.FilterSet):
class Meta: class Meta:
model = Reporter model = Reporter
fields = ['pets'] fields = ['pets']
@ -214,6 +222,7 @@ def test_global_id_multiple_field_implicit_reverse():
def test_global_id_multiple_field_explicit_reverse(): def test_global_id_multiple_field_explicit_reverse():
class ReporterPetsFilter(django_filters.FilterSet): class ReporterPetsFilter(django_filters.FilterSet):
class Meta: class Meta:
model = Reporter model = Reporter
fields = ['articles'] fields = ['articles']

View File

@ -1,6 +1,9 @@
import pytest import pytest
from django.core.exceptions import ImproperlyConfigured 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 from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED
if DJANGO_FILTER_INSTALLED: if DJANGO_FILTER_INSTALLED:
@ -9,9 +12,6 @@ if DJANGO_FILTER_INSTALLED:
else: else:
pytestmark = pytest.mark.skipif(True, reason='django_filters not installed') 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(): def test_filter_get_filterset_class_explicit():
reporter = Reporter(id=1, first_name='Cookie Monster') reporter = Reporter(id=1, first_name='Cookie Monster')

View File

@ -9,8 +9,8 @@ from graphene.contrib.django.fields import (ConnectionOrListField,
from .models import Article, Reporter from .models import Article, Reporter
def assert_conversion(django_field, graphene_field, *args): def assert_conversion(django_field, graphene_field, *args, **kwargs):
field = django_field(*args, help_text='Custom Help Text') 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.as_field() field = graphene_type.as_field()
@ -49,7 +49,7 @@ def test_should_url_convert_string():
def test_should_auto_convert_id(): 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(): def test_should_positive_integer_convert_int():

View File

@ -1,10 +1,9 @@
from django import forms from django import forms
from graphene.core.types import List, ID
from py.test import raises from py.test import raises
import graphene import graphene
from graphene.contrib.django.form_converter import convert_form_field from graphene.contrib.django.form_converter import convert_form_field
from graphene.core.types import ID, List
from .models import Reporter from .models import Reporter

View File

@ -7,7 +7,6 @@ from graphene.contrib.django import DjangoNode, DjangoObjectType
from .models import Article, Reporter from .models import Article, Reporter
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db

View File

@ -3,15 +3,17 @@ from django.db.models.query import QuerySet
from graphene.contrib.django import DjangoNode from graphene.contrib.django import DjangoNode
from graphene.contrib.django.resolvers import SimpleQuerySetConnectionResolver 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 ReporterNode(DjangoNode):
class Meta: class Meta:
model = Reporter model = Reporter
class ArticleNode(DjangoNode): class ArticleNode(DjangoNode):
class Meta: class Meta:
model = Article model = Article
@ -34,7 +36,7 @@ def test_simple_get_manager_all():
reporter = Reporter(id=1, first_name='Cookie Monster') reporter = Reporter(id=1, first_name='Cookie Monster')
resolver = SimpleQuerySetConnectionResolver(ReporterNode) resolver = SimpleQuerySetConnectionResolver(ReporterNode)
resolver(inst=reporter, args={}, info=None) 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(): def test_simple_filter():

View File

@ -5,7 +5,6 @@ from django.db import models
from ...core.classtypes.objecttype import ObjectType, ObjectTypeMeta from ...core.classtypes.objecttype import ObjectType, ObjectTypeMeta
from ...relay.types import Connection, Node, NodeMeta from ...relay.types import Connection, Node, NodeMeta
from .utils import DJANGO_FILTER_INSTALLED
from .converter import convert_django_field from .converter import convert_django_field
from .options import DjangoOptions from .options import DjangoOptions
from .utils import get_reverse_fields, maybe_queryset from .utils import get_reverse_fields, maybe_queryset

View File

@ -3,9 +3,10 @@ from django.db import models
from django.db.models.manager import Manager from django.db.models.manager import Manager
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from graphene import Argument, String
from graphene.utils import LazyList from graphene.utils import LazyList
from graphene import Argument, String from .compat import RelatedObject
try: try:
import django_filters # noqa import django_filters # noqa
@ -29,7 +30,12 @@ def get_reverse_fields(model):
# Django =>1.9 uses 'rel', django <1.9 uses 'related' # Django =>1.9 uses 'rel', django <1.9 uses 'related'
related = getattr(attr, 'rel', None) or \ related = getattr(attr, 'rel', None) or \
getattr(attr, 'related', None) 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 yield related
@ -70,6 +76,13 @@ def get_filtering_args_from_filterset(filterset_class, type):
return args 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(): def import_single_dispatch():
try: try:
from functools import singledispatch from functools import singledispatch

View File

@ -9,7 +9,8 @@ from ..classtypes.inputobjecttype import InputObjectType
from ..classtypes.mutation import Mutation from ..classtypes.mutation import Mutation
from ..exceptions import SkipField from ..exceptions import SkipField
from .argument import Argument, ArgumentsGroup, snake_case_args 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 from .definitions import NonNull

View File

@ -66,7 +66,7 @@ setup(
], ],
extras_require={ extras_require={
'django': [ 'django': [
'Django>=1.8.0', 'Django>=1.6.0',
'singledispatch>=3.4.0.3', 'singledispatch>=3.4.0.3',
'graphql-django-view>=1.1.0', 'graphql-django-view>=1.1.0',
], ],