Skip to content

Commit 76a3903

Browse files
authored
Merge branch 'master' into master
2 parents db305b3 + 2929d08 commit 76a3903

23 files changed

+842
-105
lines changed

.travis.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,6 @@ env:
4949
matrix:
5050
fast_finish: true
5151
include:
52-
- python: '2.7'
53-
env: TEST_TYPE=build DJANGO_VERSION=1.6
54-
- python: '2.7'
55-
env: TEST_TYPE=build DJANGO_VERSION=1.7
5652
- python: '2.7'
5753
env: TEST_TYPE=build DJANGO_VERSION=1.8
5854
- python: '2.7'
@@ -68,3 +64,4 @@ deploy:
6864
tags: true
6965
password:
7066
secure: kymIFCEPUbkgRqe2NAXkWfxMmGRfWvWBOP6LIXdVdkOOkm91fU7bndPGrAjos+/7gN0Org609ZmHSlVXNMJUWcsL2or/x5LcADJ4cZDe+79qynuoRb9xs1Ri4O4SBAuVMZxuVJvs8oUzT2R11ql5vASSMtXgbX+ZDGpmPRVZStkCuXgOc4LBhbPKyl3OFy7UQFPgAEmy3Yjh4ZSKzlXheK+S6mmr60+DCIjpaA0BWPxYK9FUE0qm7JJbHLUbwsUP/QMp5MmGjwFisXCNsIe686B7QKRaiOw62eJc2R7He8AuEC8T9OM4kRwDlecSn8mMpkoSB7QWtlJ+6XdLrJFPNvtrOfgfzS9/96Qrw9WlOslk68hMlhJeRb0s2YUD8tiV3UUkvbL1mfFoS4SI9U+rojS55KhUEJWHg1w7DjoOPoZmaIL2ChRupmvrFYNAGae1cxwG3Urh+t3wYlN3gpKsRDe5GOT7Wm2tr0ad3McCpDGUwSChX59BAJXe/MoLxkKScTrMyR8yMxHOF0b4zpVn5l7xB/o2Ik4zavx5q/0rGBMK2D+5d+gpQogKShoquTPsZUwO7sB5hYeH2hqGqpeGzZtb76E2zZYd18pJ0FsBudm5+KWjYdZ+vbtGrLxdTXJ1EEtzVXm0lscykTpqUucbXSa51dhStJvW2xEEz6p3rHo=
67+
distributions: "sdist bdist_wheel"

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ Contents:
1111
filtering
1212
authorization
1313
debug
14+
rest-framework
1415
introspection

docs/rest-framework.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Integration with Django Rest Framework
2+
======================================
3+
4+
You can re-use your Django Rest Framework serializer with
5+
graphene django.
6+
7+
8+
Mutation
9+
--------
10+
11+
You can create a Mutation based on a serializer by using the
12+
`SerializerMutation` base class:
13+
14+
.. code:: python
15+
16+
from graphene_django.rest_framework.mutation import SerializerMutation
17+
18+
class MyAwesomeMutation(SerializerMutation):
19+
class Meta:
20+
serializer_class = MySerializer
21+

docs/tutorial-plain.rst

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,23 @@ Let's get started with these models:
7373
def __str__(self):
7474
return self.name
7575
76+
Add ingredients as INSTALLED_APPS:
77+
78+
.. code:: python
79+
80+
INSTALLED_APPS = [
81+
...
82+
# Install the ingredients app
83+
'ingredients',
84+
]
85+
7686
Don't forget to create & run migrations:
7787

7888
.. code:: bash
7989
8090
python manage.py makemigrations
8191
python manage.py migrate
92+
8293
8394
Load some test data
8495
^^^^^^^^^^^^^^^^^^^
@@ -95,7 +106,7 @@ following:
95106
$ python ./manage.py loaddata ingredients
96107
97108
Installed 6 object(s) from 1 fixture(s)
98-
109+
99110
Alternatively you can use the Django admin interface to create some data
100111
yourself. You'll need to run the development server (see below), and
101112
create a login for yourself too (``./manage.py createsuperuser``).
@@ -190,17 +201,14 @@ a web-based integrated development environment to assist in the writing
190201
and executing of GraphQL queries. It will provide us with a simple and
191202
easy way of testing our cookbook project.
192203

193-
Add ``ingredients`` and ``graphene_django`` to ``INSTALLED_APPS`` in ``cookbook/settings.py``:
204+
Add ``graphene_django`` to ``INSTALLED_APPS`` in ``cookbook/settings.py``:
194205

195206
.. code:: python
196207
197208
INSTALLED_APPS = [
198209
...
199210
# This will also make the `graphql_schema` management command available
200211
'graphene_django',
201-
202-
# Install the ingredients app
203-
'ingredients',
204212
]
205213
206214
And then add the ``SCHEMA`` to the ``GRAPHENE`` config in ``cookbook/settings.py``:

graphene_django/compat.py

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,10 @@
1-
from django.db import models
2-
3-
41
class MissingType(object):
52
pass
63

74

85
try:
9-
DurationField = models.DurationField
10-
UUIDField = models.UUIDField
11-
except AttributeError:
12-
# Improved compatibility for Django 1.6
13-
DurationField = MissingType
14-
UUIDField = MissingType
15-
16-
try:
17-
from django.db.models.related import RelatedObject
18-
except:
19-
# Improved compatibility for Django 1.6
20-
RelatedObject = MissingType
21-
22-
23-
try:
24-
# Postgres fields are only available in Django 1.8+
6+
# Postgres fields are only available in Django with psycopg2 installed
7+
# and we cannot have psycopg2 on PyPy
258
from django.contrib.postgres.fields import ArrayField, HStoreField, RangeField
269
except ImportError:
2710
ArrayField, HStoreField, JSONField, RangeField = (MissingType, ) * 4

graphene_django/converter.py

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
from graphene.utils.str_converters import to_camel_case, to_const
1010
from graphql import assert_valid_name
1111

12-
from .compat import (ArrayField, HStoreField, JSONField, RangeField,
13-
RelatedObject, UUIDField, DurationField)
12+
from .compat import ArrayField, HStoreField, JSONField, RangeField
1413
from .fields import get_connection_field, DjangoListField
1514
from .utils import get_related_model, import_single_dispatch
1615

@@ -88,7 +87,7 @@ def convert_field_to_string(field, registry=None):
8887

8988

9089
@convert_django_field.register(models.AutoField)
91-
@convert_django_field.register(UUIDField)
90+
@convert_django_field.register(models.UUIDField)
9291
def convert_field_to_id(field, registry=None):
9392
return ID(description=field.help_text, required=not field.null)
9493

@@ -114,7 +113,7 @@ def convert_field_to_nullboolean(field, registry=None):
114113

115114
@convert_django_field.register(models.DecimalField)
116115
@convert_django_field.register(models.FloatField)
117-
@convert_django_field.register(DurationField)
116+
@convert_django_field.register(models.DurationField)
118117
def convert_field_to_float(field, registry=None):
119118
return Float(description=field.help_text, required=not field.null)
120119

@@ -165,26 +164,6 @@ def dynamic_type():
165164
return Dynamic(dynamic_type)
166165

167166

168-
# For Django 1.6
169-
@convert_django_field.register(RelatedObject)
170-
def convert_relatedfield_to_djangomodel(field, registry=None):
171-
model = field.model
172-
173-
def dynamic_type():
174-
_type = registry.get_type_for_model(model)
175-
if not _type:
176-
return
177-
178-
if isinstance(field.field, models.OneToOneField):
179-
return Field(_type)
180-
181-
if is_node(_type):
182-
return get_connection_field(_type)
183-
return DjangoListField(_type)
184-
185-
return Dynamic(dynamic_type)
186-
187-
188167
@convert_django_field.register(models.OneToOneField)
189168
@convert_django_field.register(models.ForeignKey)
190169
def convert_field_to_djangomodel(field, registry=None):

graphene_django/debug/tests/test_query.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import graphene
44
from graphene.relay import Node
55
from graphene_django import DjangoConnectionField, DjangoObjectType
6-
from graphene_django.utils import DJANGO_FILTER_INSTALLED
76

87
from ...tests.models import Reporter
98
from ..middleware import DjangoDebugMiddleware
@@ -167,8 +166,6 @@ def resolve_all_reporters(self, *args, **kwargs):
167166
assert result.data['__debug']['sql'][1]['rawSql'] == query
168167

169168

170-
@pytest.mark.skipif(not DJANGO_FILTER_INSTALLED,
171-
reason="requires django-filter")
172169
def test_should_query_connectionfilter():
173170
from ...filter import DjangoFilterConnectionField
174171

graphene_django/fields.py

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from django.db.models.query import QuerySet
44

5+
from promise import Promise
6+
57
from graphene.types import Field, List
68
from graphene.relay import ConnectionField, PageInfo
79
from graphql_relay.connection.arrayconnection import connection_from_list_slice
@@ -57,7 +59,33 @@ def get_manager(self):
5759

5860
@classmethod
5961
def merge_querysets(cls, default_queryset, queryset):
60-
return default_queryset & queryset
62+
return queryset & default_queryset
63+
64+
@classmethod
65+
def resolve_connection(cls, connection, default_manager, args, iterable):
66+
if iterable is None:
67+
iterable = default_manager
68+
iterable = maybe_queryset(iterable)
69+
if isinstance(iterable, QuerySet):
70+
if iterable is not default_manager:
71+
default_queryset = maybe_queryset(default_manager)
72+
iterable = cls.merge_querysets(default_queryset, iterable)
73+
_len = iterable.count()
74+
else:
75+
_len = len(iterable)
76+
connection = connection_from_list_slice(
77+
iterable,
78+
args,
79+
slice_start=0,
80+
list_length=_len,
81+
list_slice_length=_len,
82+
connection_type=connection,
83+
edge_type=connection.Edge,
84+
pageinfo_type=PageInfo,
85+
)
86+
connection.iterable = iterable
87+
connection.length = _len
88+
return connection
6189

6290
@classmethod
6391
def connection_resolver(cls, resolver, connection, default_manager, max_limit,
@@ -84,29 +112,12 @@ def connection_resolver(cls, resolver, connection, default_manager, max_limit,
84112
args['last'] = min(last, max_limit)
85113

86114
iterable = resolver(root, args, context, info)
87-
if iterable is None:
88-
iterable = default_manager
89-
iterable = maybe_queryset(iterable)
90-
if isinstance(iterable, QuerySet):
91-
if iterable is not default_manager:
92-
default_queryset = maybe_queryset(default_manager)
93-
iterable = cls.merge_querysets(default_queryset, iterable)
94-
_len = iterable.count()
95-
else:
96-
_len = len(iterable)
97-
connection = connection_from_list_slice(
98-
iterable,
99-
args,
100-
slice_start=0,
101-
list_length=_len,
102-
list_slice_length=_len,
103-
connection_type=connection,
104-
edge_type=connection.Edge,
105-
pageinfo_type=PageInfo,
106-
)
107-
connection.iterable = iterable
108-
connection.length = _len
109-
return connection
115+
on_resolve = partial(cls.resolve_connection, connection, default_manager, args)
116+
117+
if Promise.is_thenable(iterable):
118+
return Promise.resolve(iterable).then(on_resolve)
119+
120+
return on_resolve(iterable)
110121

111122
def get_resolver(self, parent_resolver):
112123
return partial(

graphene_django/rest_framework/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)