Skip to content

Commit

Permalink
WIP: Merge master into v3 (#1086)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
zbyte64 authored Dec 30, 2020
1 parent 48ed516 commit c049ab7
Show file tree
Hide file tree
Showing 46 changed files with 1,365 additions and 201 deletions.
13 changes: 7 additions & 6 deletions .github/stale.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 120
daysUntilStale: false
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 30
daysUntilClose: false
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
Expand All @@ -13,9 +13,10 @@ exemptLabels:
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
markComment: false
# markComment: >
# This issue has been automatically marked as stale because it has not had
# recent activity. It will be closed if no further activity occurs. Thank you
# for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
3 changes: 3 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
include README.md LICENSE
recursive-include graphene_django/templates *
recursive-include graphene_django/static *

include examples/cookbook/cookbook/ingredients/fixtures/ingredients.json
include examples/cookbook-plain/cookbook/ingredients/fixtures/ingredients.json
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

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

[![travis][travis-image]][travis-url]
[![build][build-image]][build-url]
[![pypi][pypi-image]][pypi-url]
[![Anaconda-Server Badge][conda-image]][conda-url]
[![coveralls][coveralls-image]][coveralls-url]

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


## GraphQL testing clients
- [Firecamp](https://firecamp.io/graphql)
- [GraphiQL](https://github.com/graphql/graphiql)


## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md)
Expand Down
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ Contributing
See `CONTRIBUTING.md <CONTRIBUTING.md>`__.

.. |Graphene Logo| image:: http://graphene-python.org/favicon.png
.. |Build Status| image:: https://travis-ci.org/graphql-python/graphene-django.svg?branch=master
:target: https://travis-ci.org/graphql-python/graphene-django
.. |Build Status| image:: https://github.com/graphql-python/graphene-django/workflows/Tests/badge.svg
:target: https://github.com/graphql-python/graphene-django/actions
.. |PyPI version| image:: https://badge.fury.io/py/graphene-django.svg
:target: https://badge.fury.io/py/graphene-django
.. |Coverage Status| image:: https://coveralls.io/repos/graphql-python/graphene-django/badge.svg?branch=master&service=github
Expand Down
35 changes: 0 additions & 35 deletions django_test_settings.py

This file was deleted.

4 changes: 2 additions & 2 deletions docs/debug.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Django Debug Middleware

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

For that, you will need to add the plugin in your graphene schema.
Expand Down Expand Up @@ -43,7 +43,7 @@ And in your ``settings.py``:
Querying
--------

You can query it for outputing all the sql transactions that happened in
You can query it for outputting all the sql transactions that happened in
the GraphQL request, like:

.. code::
Expand Down
2 changes: 1 addition & 1 deletion docs/filtering.rst
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ Extend the tuple of fields if you want to order by more than one field.
order_by = OrderingFilter(
fields=(
('created_at', 'created_at'),
('name', 'created_at'),
)
)
Expand Down
2 changes: 1 addition & 1 deletion docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ To learn how to extend the schema object for your project, read the basic tutori
CSRF exempt
-----------

If have enabled `CSRF protection <https://docs.djangoproject.com/en/3.0/ref/csrf/>`_ in your Django app
If you have enabled `CSRF protection <https://docs.djangoproject.com/en/3.0/ref/csrf/>`_ in your Django app
you will find that it prevents your API clients from POSTing to the ``graphql`` endpoint. You can either
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``
decorator:
Expand Down
121 changes: 120 additions & 1 deletion docs/mutations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ Simple example
# The class attributes define the response of the mutation
question = graphene.Field(QuestionType)
def mutate(self, info, text, id):
@classmethod
def mutate(cls, root, info, text, id):
question = Question.objects.get(pk=id)
question.text = text
question.save()
Expand Down Expand Up @@ -231,3 +232,121 @@ This argument is also sent back to the client with the mutation result
(you do not have to do anything). For services that manage
a pool of many GraphQL requests in bulk, the ``clientIDMutation``
allows you to match up a specific mutation with the response.



Django Database Transactions
----------------------------

Django gives you a few ways to control how database transactions are managed.

Tying transactions to HTTP requests
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A common way to handle transactions in Django is to wrap each request in a transaction.
Set ``ATOMIC_REQUESTS`` settings to ``True`` in the configuration of each database for
which you want to enable this behavior.

It works like this. Before calling ``GraphQLView`` Django starts a transaction. If the
response is produced without problems, Django commits the transaction. If the view, a
``DjangoFormMutation`` or a ``DjangoModelFormMutation`` produces an exception, Django
rolls back the transaction.

.. warning::

While the simplicity of this transaction model is appealing, it also makes it
inefficient when traffic increases. Opening a transaction for every request has some
overhead. The impact on performance depends on the query patterns of your application
and on how well your database handles locking.

Check the next section for a better solution.

Tying transactions to mutations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A mutation can contain multiple fields, just like a query. There's one important
distinction between queries and mutations, other than the name:

..
`While query fields are executed in parallel, mutation fields run in series, one
after the other.`

This means that if we send two ``incrementCredits`` mutations in one request, the first
is guaranteed to finish before the second begins, ensuring that we don't end up with a
race condition with ourselves.

On the other hand, if the first ``incrementCredits`` runs successfully but the second
one does not, the operation cannot be retried as it is. That's why is a good idea to
run all mutation fields in a transaction, to guarantee all occur or nothing occurs.

To enable this behavior for all databases set the graphene ``ATOMIC_MUTATIONS`` settings
to ``True`` in your settings file:

.. code:: python
GRAPHENE = {
# ...
"ATOMIC_MUTATIONS": True,
}
On the contrary, if you want to enable this behavior for a specific database, set
``ATOMIC_MUTATIONS`` to ``True`` in your database settings:

.. code:: python
DATABASES = {
"default": {
# ...
"ATOMIC_MUTATIONS": True,
},
# ...
}
Now, given the following example mutation:

.. code::
mutation IncreaseCreditsTwice {
increaseCredits1: increaseCredits(input: { amount: 10 }) {
balance
errors {
field
messages
}
}
increaseCredits2: increaseCredits(input: { amount: -1 }) {
balance
errors {
field
messages
}
}
}
The server is going to return something like:

.. code:: json
{
"data": {
"increaseCredits1": {
"balance": 10.0,
"errors": []
},
"increaseCredits2": {
"balance": null,
"errors": [
{
"field": "amount",
"message": "Amount should be a positive number"
}
]
},
}
}
But the balance will remain the same.
2 changes: 1 addition & 1 deletion docs/queries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ Where "foo" is the name of the field declared in the ``Query`` object.
class Query(graphene.ObjectType):
foo = graphene.List(QuestionType)
def resolve_foo(root, info):
def resolve_foo(root, info, **kwargs):
id = kwargs.get("id")
return Question.objects.get(id)
Expand Down
7 changes: 4 additions & 3 deletions docs/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,20 @@ To use pytest define a simple fixture using the query helper below
.. code:: python
# Create a fixture using the graphql_query helper and `client` fixture from `pytest-django`.
import json
import pytest
from graphene_django.utils.testing import graphql_query
@pytest.fixture
def client_query(client)
def client_query(client):
def func(*args, **kwargs):
return graphql_query(*args, **kwargs, client=client)
return func
# Test you query using the client_query fixture
def test_some_query(client_query):
response = graphql_query(
response = client_query(
'''
query {
myModel {
Expand All @@ -115,4 +116,4 @@ To use pytest define a simple fixture using the query helper below
)
content = json.loads(response.content)
assert 'errors' not in content
assert 'errors' not in content
Empty file added examples/__init__.py
Empty file.
Empty file.
Empty file added examples/cookbook/__init__.py
Empty file.
30 changes: 30 additions & 0 deletions examples/django_test_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import sys
import os

ROOT_PATH = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, ROOT_PATH + "/examples/")

SECRET_KEY = 1

INSTALLED_APPS = [
"graphene_django",
"graphene_django.rest_framework",
"graphene_django.tests",
"examples.starwars",
]

DATABASES = {
"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": "django_test.sqlite"}
}

TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
}
]

GRAPHENE = {"SCHEMA": "graphene_django.tests.schema_view.schema"}

ROOT_URLCONF = "graphene_django.tests.urls"
2 changes: 1 addition & 1 deletion graphene_django/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from .fields import DjangoConnectionField, DjangoListField
from .types import DjangoObjectType

__version__ = "3.0.0b6"
__version__ = "3.0.0b7"

__all__ = [
"__version__",
Expand Down
1 change: 1 addition & 0 deletions graphene_django/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MUTATION_ERRORS_FLAG = "graphene_mutation_has_errors"
5 changes: 5 additions & 0 deletions graphene_django/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
NonNull,
String,
Time,
Decimal,
)
from graphene.types.json import JSONString
from graphene.utils.str_converters import to_camel_case
Expand Down Expand Up @@ -174,6 +175,10 @@ def convert_field_to_boolean(field, registry=None):


@convert_django_field.register(models.DecimalField)
def convert_field_to_decimal(field, registry=None):
return Decimal(description=field.help_text, required=not field.null)


@convert_django_field.register(models.FloatField)
@convert_django_field.register(models.DurationField)
def convert_field_to_float(field, registry=None):
Expand Down
Loading

0 comments on commit c049ab7

Please sign in to comment.