Skip to content

Commit c049ab7

Browse files
authored
WIP: Merge master into v3 (#1086)
* merge master into v3 * fix order_by snake casing by checking if value is None, switch executor to execution_context_class since schema.execute no longer supports executor * fix linting by removing duplicate defintion and test of convert_form_field_to_string_list
1 parent 48ed516 commit c049ab7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1365
-201
lines changed

.github/stale.yml

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Number of days of inactivity before an issue becomes stale
2-
daysUntilStale: 120
2+
daysUntilStale: false
33
# Number of days of inactivity before a stale issue is closed
4-
daysUntilClose: 30
4+
daysUntilClose: false
55
# Issues with these labels will never be considered stale
66
exemptLabels:
77
- pinned
@@ -13,9 +13,10 @@ exemptLabels:
1313
# Label to use when marking an issue as stale
1414
staleLabel: wontfix
1515
# Comment to post when marking an issue as stale. Set to `false` to disable
16-
markComment: >
17-
This issue has been automatically marked as stale because it has not had
18-
recent activity. It will be closed if no further activity occurs. Thank you
19-
for your contributions.
16+
markComment: false
17+
# markComment: >
18+
# This issue has been automatically marked as stale because it has not had
19+
# recent activity. It will be closed if no further activity occurs. Thank you
20+
# for your contributions.
2021
# Comment to post when closing a stale issue. Set to `false` to disable
2122
closeComment: false

MANIFEST.in

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
include README.md LICENSE
22
recursive-include graphene_django/templates *
33
recursive-include graphene_django/static *
4+
5+
include examples/cookbook/cookbook/ingredients/fixtures/ingredients.json
6+
include examples/cookbook-plain/cookbook/ingredients/fixtures/ingredients.json

README.md

+8-3
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33

44
A [Django](https://www.djangoproject.com/) integration for [Graphene](http://graphene-python.org/).
55

6-
[![travis][travis-image]][travis-url]
6+
[![build][build-image]][build-url]
77
[![pypi][pypi-image]][pypi-url]
88
[![Anaconda-Server Badge][conda-image]][conda-url]
99
[![coveralls][coveralls-image]][coveralls-url]
1010

11-
[travis-image]: https://travis-ci.org/graphql-python/graphene-django.svg?branch=master&style=flat
12-
[travis-url]: https://travis-ci.org/graphql-python/graphene-django
11+
[build-image]: https://github.com/graphql-python/graphene-django/workflows/Tests/badge.svg
12+
[build-url]: https://github.com/graphql-python/graphene-django/actions
1313
[pypi-image]: https://img.shields.io/pypi/v/graphene-django.svg?style=flat
1414
[pypi-url]: https://pypi.org/project/graphene-django/
1515
[coveralls-image]: https://coveralls.io/repos/github/graphql-python/graphene-django/badge.svg?branch=master
@@ -110,6 +110,11 @@ To learn more check out the following [examples](examples/):
110110
* **Relay Schema**: [Starwars Relay example](examples/starwars)
111111

112112

113+
## GraphQL testing clients
114+
- [Firecamp](https://firecamp.io/graphql)
115+
- [GraphiQL](https://github.com/graphql/graphiql)
116+
117+
113118
## Contributing
114119

115120
See [CONTRIBUTING.md](CONTRIBUTING.md)

README.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ Contributing
114114
See `CONTRIBUTING.md <CONTRIBUTING.md>`__.
115115

116116
.. |Graphene Logo| image:: http://graphene-python.org/favicon.png
117-
.. |Build Status| image:: https://travis-ci.org/graphql-python/graphene-django.svg?branch=master
118-
:target: https://travis-ci.org/graphql-python/graphene-django
117+
.. |Build Status| image:: https://github.com/graphql-python/graphene-django/workflows/Tests/badge.svg
118+
:target: https://github.com/graphql-python/graphene-django/actions
119119
.. |PyPI version| image:: https://badge.fury.io/py/graphene-django.svg
120120
:target: https://badge.fury.io/py/graphene-django
121121
.. |Coverage Status| image:: https://coveralls.io/repos/graphql-python/graphene-django/badge.svg?branch=master&service=github

django_test_settings.py

-35
This file was deleted.

docs/debug.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Django Debug Middleware
33

44
You can debug your GraphQL queries in a similar way to
55
`django-debug-toolbar <https://django-debug-toolbar.readthedocs.org/>`__,
6-
but outputing in the results in GraphQL response as fields, instead of
6+
but outputting in the results in GraphQL response as fields, instead of
77
the graphical HTML interface.
88

99
For that, you will need to add the plugin in your graphene schema.
@@ -43,7 +43,7 @@ And in your ``settings.py``:
4343
Querying
4444
--------
4545

46-
You can query it for outputing all the sql transactions that happened in
46+
You can query it for outputting all the sql transactions that happened in
4747
the GraphQL request, like:
4848

4949
.. code::

docs/filtering.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ Extend the tuple of fields if you want to order by more than one field.
230230
231231
order_by = OrderingFilter(
232232
fields=(
233-
('created_at', 'created_at'),
233+
('name', 'created_at'),
234234
)
235235
)
236236

docs/installation.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ To learn how to extend the schema object for your project, read the basic tutori
7373
CSRF exempt
7474
-----------
7575

76-
If have enabled `CSRF protection <https://docs.djangoproject.com/en/3.0/ref/csrf/>`_ in your Django app
76+
If you have enabled `CSRF protection <https://docs.djangoproject.com/en/3.0/ref/csrf/>`_ in your Django app
7777
you will find that it prevents your API clients from POSTing to the ``graphql`` endpoint. You can either
7878
update your API client to pass the CSRF token with each request (the Django docs have a guide on how to do that: https://docs.djangoproject.com/en/3.0/ref/csrf/#ajax) or you can exempt your Graphql endpoint from CSRF protection by wrapping the ``GraphQLView`` with the ``csrf_exempt``
7979
decorator:

docs/mutations.rst

+120-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ Simple example
3737
# The class attributes define the response of the mutation
3838
question = graphene.Field(QuestionType)
3939
40-
def mutate(self, info, text, id):
40+
@classmethod
41+
def mutate(cls, root, info, text, id):
4142
question = Question.objects.get(pk=id)
4243
question.text = text
4344
question.save()
@@ -231,3 +232,121 @@ This argument is also sent back to the client with the mutation result
231232
(you do not have to do anything). For services that manage
232233
a pool of many GraphQL requests in bulk, the ``clientIDMutation``
233234
allows you to match up a specific mutation with the response.
235+
236+
237+
238+
Django Database Transactions
239+
----------------------------
240+
241+
Django gives you a few ways to control how database transactions are managed.
242+
243+
Tying transactions to HTTP requests
244+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
245+
246+
A common way to handle transactions in Django is to wrap each request in a transaction.
247+
Set ``ATOMIC_REQUESTS`` settings to ``True`` in the configuration of each database for
248+
which you want to enable this behavior.
249+
250+
It works like this. Before calling ``GraphQLView`` Django starts a transaction. If the
251+
response is produced without problems, Django commits the transaction. If the view, a
252+
``DjangoFormMutation`` or a ``DjangoModelFormMutation`` produces an exception, Django
253+
rolls back the transaction.
254+
255+
.. warning::
256+
257+
While the simplicity of this transaction model is appealing, it also makes it
258+
inefficient when traffic increases. Opening a transaction for every request has some
259+
overhead. The impact on performance depends on the query patterns of your application
260+
and on how well your database handles locking.
261+
262+
Check the next section for a better solution.
263+
264+
Tying transactions to mutations
265+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
266+
267+
A mutation can contain multiple fields, just like a query. There's one important
268+
distinction between queries and mutations, other than the name:
269+
270+
..
271+
272+
`While query fields are executed in parallel, mutation fields run in series, one
273+
after the other.`
274+
275+
This means that if we send two ``incrementCredits`` mutations in one request, the first
276+
is guaranteed to finish before the second begins, ensuring that we don't end up with a
277+
race condition with ourselves.
278+
279+
On the other hand, if the first ``incrementCredits`` runs successfully but the second
280+
one does not, the operation cannot be retried as it is. That's why is a good idea to
281+
run all mutation fields in a transaction, to guarantee all occur or nothing occurs.
282+
283+
To enable this behavior for all databases set the graphene ``ATOMIC_MUTATIONS`` settings
284+
to ``True`` in your settings file:
285+
286+
.. code:: python
287+
288+
GRAPHENE = {
289+
# ...
290+
"ATOMIC_MUTATIONS": True,
291+
}
292+
293+
On the contrary, if you want to enable this behavior for a specific database, set
294+
``ATOMIC_MUTATIONS`` to ``True`` in your database settings:
295+
296+
.. code:: python
297+
298+
DATABASES = {
299+
"default": {
300+
# ...
301+
"ATOMIC_MUTATIONS": True,
302+
},
303+
# ...
304+
}
305+
306+
Now, given the following example mutation:
307+
308+
.. code::
309+
310+
mutation IncreaseCreditsTwice {
311+
312+
increaseCredits1: increaseCredits(input: { amount: 10 }) {
313+
balance
314+
errors {
315+
field
316+
messages
317+
}
318+
}
319+
320+
increaseCredits2: increaseCredits(input: { amount: -1 }) {
321+
balance
322+
errors {
323+
field
324+
messages
325+
}
326+
}
327+
328+
}
329+
330+
The server is going to return something like:
331+
332+
.. code:: json
333+
334+
{
335+
"data": {
336+
"increaseCredits1": {
337+
"balance": 10.0,
338+
"errors": []
339+
},
340+
"increaseCredits2": {
341+
"balance": null,
342+
"errors": [
343+
{
344+
"field": "amount",
345+
"message": "Amount should be a positive number"
346+
}
347+
]
348+
},
349+
}
350+
}
351+
352+
But the balance will remain the same.

docs/queries.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ Where "foo" is the name of the field declared in the ``Query`` object.
291291
class Query(graphene.ObjectType):
292292
foo = graphene.List(QuestionType)
293293
294-
def resolve_foo(root, info):
294+
def resolve_foo(root, info, **kwargs):
295295
id = kwargs.get("id")
296296
return Question.objects.get(id)
297297

docs/testing.rst

+4-3
Original file line numberDiff line numberDiff line change
@@ -90,19 +90,20 @@ To use pytest define a simple fixture using the query helper below
9090
.. code:: python
9191
9292
# Create a fixture using the graphql_query helper and `client` fixture from `pytest-django`.
93+
import json
9394
import pytest
9495
from graphene_django.utils.testing import graphql_query
9596
9697
@pytest.fixture
97-
def client_query(client)
98+
def client_query(client):
9899
def func(*args, **kwargs):
99100
return graphql_query(*args, **kwargs, client=client)
100101
101102
return func
102103
103104
# Test you query using the client_query fixture
104105
def test_some_query(client_query):
105-
response = graphql_query(
106+
response = client_query(
106107
'''
107108
query {
108109
myModel {
@@ -115,4 +116,4 @@ To use pytest define a simple fixture using the query helper below
115116
)
116117
117118
content = json.loads(response.content)
118-
assert 'errors' not in content
119+
assert 'errors' not in content

examples/__init__.py

Whitespace-only changes.

examples/cookbook-plain/__init__.py

Whitespace-only changes.

examples/cookbook/__init__.py

Whitespace-only changes.

examples/django_test_settings.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import sys
2+
import os
3+
4+
ROOT_PATH = os.path.dirname(os.path.abspath(__file__))
5+
sys.path.insert(0, ROOT_PATH + "/examples/")
6+
7+
SECRET_KEY = 1
8+
9+
INSTALLED_APPS = [
10+
"graphene_django",
11+
"graphene_django.rest_framework",
12+
"graphene_django.tests",
13+
"examples.starwars",
14+
]
15+
16+
DATABASES = {
17+
"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": "django_test.sqlite"}
18+
}
19+
20+
TEMPLATES = [
21+
{
22+
"BACKEND": "django.template.backends.django.DjangoTemplates",
23+
"DIRS": [],
24+
"APP_DIRS": True,
25+
}
26+
]
27+
28+
GRAPHENE = {"SCHEMA": "graphene_django.tests.schema_view.schema"}
29+
30+
ROOT_URLCONF = "graphene_django.tests.urls"

graphene_django/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from .fields import DjangoConnectionField, DjangoListField
22
from .types import DjangoObjectType
33

4-
__version__ = "3.0.0b6"
4+
__version__ = "3.0.0b7"
55

66
__all__ = [
77
"__version__",

graphene_django/constants.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
MUTATION_ERRORS_FLAG = "graphene_mutation_has_errors"

graphene_django/converter.py

+5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
NonNull,
2121
String,
2222
Time,
23+
Decimal,
2324
)
2425
from graphene.types.json import JSONString
2526
from graphene.utils.str_converters import to_camel_case
@@ -174,6 +175,10 @@ def convert_field_to_boolean(field, registry=None):
174175

175176

176177
@convert_django_field.register(models.DecimalField)
178+
def convert_field_to_decimal(field, registry=None):
179+
return Decimal(description=field.help_text, required=not field.null)
180+
181+
177182
@convert_django_field.register(models.FloatField)
178183
@convert_django_field.register(models.DurationField)
179184
def convert_field_to_float(field, registry=None):

0 commit comments

Comments
 (0)