Skip to content

Commit 9b41472

Browse files
committed
Merge branch 'master' into v3
2 parents 07c5109 + 709611b commit 9b41472

File tree

13 files changed

+349
-32
lines changed

13 files changed

+349
-32
lines changed

.github/workflows/deploy.yml

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ jobs:
1717
python-version: 3.8
1818
- name: Build wheel and source tarball
1919
run: |
20+
pip install wheel
2021
python setup.py sdist bdist_wheel
2122
- name: Publish a Python distribution to PyPI
2223
uses: pypa/gh-action-pypi-publish@v1.1.0

docs/extra-types.rst

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Extra Types
2+
===========
3+
4+
Here are some libraries that provide common types for Django specific fields.
5+
6+
7+
GeoDjango
8+
---------
9+
10+
Use the graphene-gis_ library to add GeoDjango types to your Schema.
11+
12+
.. _graphene-gis: https://github.com/EverWinter23/graphene-gis

docs/fields.rst

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
Fields
2+
======
3+
4+
Graphene-Django provides some useful fields to help integrate Django with your GraphQL
5+
Schema.
6+
7+
DjangoListField
8+
---------------
9+
10+
``DjangoListField`` allows you to define a list of :ref:`DjangoObjectType<queries-objecttypes>`'s. By default it will resolve the default queryset of the Django model.
11+
12+
.. code:: python
13+
14+
from graphene import ObjectType, Schema
15+
from graphene_django import DjangoListField
16+
17+
class RecipeType(DjangoObjectType):
18+
class Meta:
19+
model = Recipe
20+
fields = ("title", "instructions")
21+
22+
class Query(ObjectType):
23+
recipes = DjangoListField(RecipeType)
24+
25+
schema = Schema(query=Query)
26+
27+
The above code results in the following schema definition:
28+
29+
.. code::
30+
31+
schema {
32+
query: Query
33+
}
34+
35+
type Query {
36+
recipes: [RecipeType!]
37+
}
38+
39+
type RecipeType {
40+
title: String!
41+
instructions: String!
42+
}
43+
44+
Custom resolvers
45+
****************
46+
47+
If your ``DjangoObjectType`` has defined a custom
48+
:ref:`get_queryset<django-objecttype-get-queryset>` method, when resolving a
49+
``DjangoListField`` it will be called with either the return of the field
50+
resolver (if one is defined) or the default queryeset from the Django model.
51+
52+
For example the following schema will only resolve recipes which have been
53+
published and have a title:
54+
55+
.. code:: python
56+
57+
from graphene import ObjectType, Schema
58+
from graphene_django import DjangoListField
59+
60+
class RecipeType(DjangoObjectType):
61+
class Meta:
62+
model = Recipe
63+
fields = ("title", "instructions")
64+
65+
@classmethod
66+
def get_queryset(cls, queryset, info):
67+
# Filter out recipes that have no title
68+
return queryset.exclude(title__exact="")
69+
70+
class Query(ObjectType):
71+
recipes = DjangoListField(RecipeType)
72+
73+
def resolve_recipes(parent, info):
74+
# Only get recipes that have been published
75+
return Recipe.objects.filter(published=True)
76+
77+
schema = Schema(query=Query)
78+
79+
80+
DjangoConnectionField
81+
---------------------
82+
83+
*TODO*

docs/index.rst

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ For more advanced use, check out the Relay tutorial.
2525
tutorial-relay
2626
schema
2727
queries
28+
fields
29+
extra-types
2830
mutations
2931
filtering
3032
authorization

docs/queries.rst

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.. _queries-objecttypes:
2+
13
Queries & ObjectTypes
24
=====================
35

@@ -205,6 +207,8 @@ need to create the most basic class for this to work:
205207
class Meta:
206208
model = Category
207209
210+
.. _django-objecttype-get-queryset:
211+
208212
Default QuerySet
209213
-----------------
210214

graphene_django/__init__.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
from .fields import DjangoConnectionField, DjangoListField
12
from .types import DjangoObjectType
2-
from .fields import DjangoConnectionField
33

4-
__version__ = "2.9.1"
4+
__version__ = "2.10.0"
55

6-
__all__ = ["__version__", "DjangoObjectType", "DjangoConnectionField"]
6+
__all__ = [
7+
"__version__",
8+
"DjangoObjectType",
9+
"DjangoListField",
10+
"DjangoConnectionField",
11+
]

graphene_django/converter.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -154,13 +154,9 @@ def convert_field_to_int(field, registry=None):
154154
return Int(description=field.help_text, required=not field.null)
155155

156156

157+
@convert_django_field.register(models.NullBooleanField)
157158
@convert_django_field.register(models.BooleanField)
158159
def convert_field_to_boolean(field, registry=None):
159-
return NonNull(Boolean, description=field.help_text)
160-
161-
162-
@convert_django_field.register(models.NullBooleanField)
163-
def convert_field_to_nullboolean(field, registry=None):
164160
return Boolean(description=field.help_text, required=not field.null)
165161

166162

graphene_django/fields.py

+18-8
Original file line numberDiff line numberDiff line change
@@ -38,24 +38,34 @@ def _underlying_type(self):
3838
def model(self):
3939
return self._underlying_type._meta.model
4040

41+
def get_default_queryset(self):
42+
return self.model._default_manager.get_queryset()
43+
4144
@staticmethod
42-
def list_resolver(django_object_type, resolver, root, info, **args):
45+
def list_resolver(
46+
django_object_type, resolver, default_queryset, root, info, **args
47+
):
4348
queryset = maybe_queryset(resolver(root, info, **args))
4449
if queryset is None:
45-
# Default to Django Model queryset
46-
# N.B. This happens if DjangoListField is used in the top level Query object
47-
model_manager = django_object_type._meta.model.objects
48-
queryset = maybe_queryset(
49-
django_object_type.get_queryset(model_manager, info)
50-
)
50+
queryset = default_queryset
51+
52+
if isinstance(queryset, QuerySet):
53+
# Pass queryset to the DjangoObjectType get_queryset method
54+
queryset = maybe_queryset(django_object_type.get_queryset(queryset, info))
55+
5156
return queryset
5257

5358
def get_resolver(self, parent_resolver):
5459
_type = self.type
5560
if isinstance(_type, NonNull):
5661
_type = _type.of_type
5762
django_object_type = _type.of_type.of_type
58-
return partial(self.list_resolver, django_object_type, parent_resolver)
63+
return partial(
64+
self.list_resolver,
65+
django_object_type,
66+
parent_resolver,
67+
self.get_default_queryset(),
68+
)
5969

6070

6171
class DjangoConnectionField(ConnectionField):

graphene_django/tests/test_converter.py

+15-3
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,19 @@
2525

2626

2727
def assert_conversion(django_field, graphene_field, *args, **kwargs):
28-
field = django_field(help_text="Custom Help Text", null=True, *args, **kwargs)
28+
_kwargs = kwargs.copy()
29+
if "null" not in kwargs:
30+
_kwargs["null"] = True
31+
field = django_field(help_text="Custom Help Text", *args, **_kwargs)
2932
graphene_type = convert_django_field(field)
3033
assert isinstance(graphene_type, graphene_field)
3134
field = graphene_type.Field()
3235
assert field.description == "Custom Help Text"
33-
nonnull_field = django_field(null=False, *args, **kwargs)
36+
37+
_kwargs = kwargs.copy()
38+
if "null" not in kwargs:
39+
_kwargs["null"] = False
40+
nonnull_field = django_field(*args, **_kwargs)
3441
if not nonnull_field.null:
3542
nonnull_graphene_type = convert_django_field(nonnull_field)
3643
nonnull_field = nonnull_graphene_type.Field()
@@ -126,7 +133,12 @@ def test_should_integer_convert_int():
126133

127134

128135
def test_should_boolean_convert_boolean():
129-
field = assert_conversion(models.BooleanField, graphene.NonNull)
136+
assert_conversion(models.BooleanField, graphene.Boolean, null=True)
137+
138+
139+
def test_should_boolean_convert_non_null_boolean():
140+
field = assert_conversion(models.BooleanField, graphene.Boolean, null=False)
141+
assert isinstance(field.type, graphene.NonNull)
130142
assert field.type.of_type == graphene.Boolean
131143

132144

0 commit comments

Comments
 (0)