Toolbox for changes to streamline graphene-django
ATTENTION: This package is deprecated. All functionality will live on in the ai-django-core package. Please install it with the graphql-extension enabled. More details in the changelog.
For installing graphene, just run this command in your shell
pip install "graphene-django-ai>=1.0.0"
Refer to the documentation of django-graphene
base package.
https://github.com/graphql-python/graphene-django/blob/master/README.md
Here is a simple Django model in my_app/models.py
:
from django.db import models
class User(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
Now we create a ModelForm
in my_app/forms.py
:
from django import forms
from .models import User
class SpaceForm(forms.ModelForm):
class Meta:
model = User
exclude = []
We need to create an ObjectType
which we derive from our model.
Lives in my_apps/schemes/schematypes.py
:
from graphene_django import DjangoObjectType
from ..models import User
class UserType(DjangoObjectType):
class Meta:
model = User
Here's the mutation in my_app/schema/mutations.py
.
It takes a ModelForm
(or a non-model form) to derive the validation rules from:
import graphene
from graphene_django_ai.forms.mutations import LoginRequiredDjangoModelFormMutation
from .schematypes import UserType
from ..forms import UserForm
class UserCreateUpdateMutation(LoginRequiredDjangoModelFormMutation):
space = graphene.Field(UserType)
class Meta:
form_class = UserForm
# Register new mutation
class UserMutation(graphene.ObjectType):
spaces = UserCreateUpdateMutation.Field(description='Create and update users')
If you register now your UserMutation
in your schema you have a working model-based and DRY API
endpoint. Congratulations!
If you want to delete an object you can easily use the DeleteMutation
like this:
from graphene_django_ai.schemes.mutations import DeleteMutation
from my_app.models import MyModel
class MyModelDeleteMutation(DeleteMutation):
class Meta:
model = MyModel
If you are using django-graphql-jwt
authentication you can ensure only logged in access to your delete endpoint like this:
from graphene_django_ai.schemes.mutations import LoginRequiredDeleteMutation
from my_app.models import MyModel
class MyModelDeleteMutation(LoginRequiredDeleteMutation):
class Meta:
model = MyModel
If you need to customize the validation or the base queryset you can override methods like this:
from graphene_django_ai.schemes.mutations import LoginRequiredDeleteMutation
from graphql import GraphQLError
from my_app.models import MyModel
class MyModelDeleteMutation(LoginRequiredDeleteMutation):
class Meta:
model = MyModel
def validate(self, request):
if not request.user.is_superuser:
raise GraphQLError("This is only allowed for superusers!")
def get_queryset(self, request):
return self.model.objects.filter(created_by=request.user)
If you derive your mutation from LoginRequiredDjangoModelFormMutation
you don't have to manually take
care about securing the login with the decorators.
from graphene_django_ai.forms.mutations import LoginRequiredDjangoModelFormMutation
class MyMutation(LoginRequiredDjangoModelFormMutation):
...
If you want to unittest your API calls derive your test case from the class GraphQLTestCase
.
Usage:
import json
from graphene_django.tests.base_test import GraphQLTestCase
from my_project.config.schema import schema
class MyFancyTestCase(GraphQLTestCase):
# Here you need to inject your test case's schema
GRAPHQL_SCHEMA = schema
def test_some_query(self):
response = self.query(
'''
query {
myModel {
id
name
}
}
''',
op_name='myModel'
)
content = json.loads(response.content)
# This validates the status code and if you get errors
self.assertResponseNoErrors(response)
# Add some more asserts if you like
...
def test_some_mutation(self):
response = self.query(
'''
mutation myMutation($input: MyMutationInput!) {
myMutation(input: $input) {
my-model {
id
name
}
}
}
''',
op_name='myMutation',
input_data={'my_field': 'foo', 'other_field': 'bar'}
)
# This validates the status code and if you get errors
self.assertResponseNoErrors(response)
# Add some more asserts if you like
...
def test_failing_call(self):
response = self.query(
'''
mutation myMutation($input: MyMutationInput!) {
myMutation(input: $badInput) {
my-model {
id
name
}
}
}
''',
op_name='myMutation',
input_data={'my_field': 'foo', 'other_field': 'bar'}
)
# This assert tests if the call raised some errors
# For example if you want to test if invalid input is handled correctly by your endpoint
self.assertResponseHasErrors(response)
# Add some more asserts if you like
...
Still W.I.P.!
python -m unittest discover -v
-
Update
Changelog
inReadme.md
-
Create pull request / merge to master
-
Run:
- Make sure you have all the required packages installed
pip install twine wheel
- Create a file in your home directory:
~/.pypirc
[distutils] index-servers= pypi testpypi [pypi] repository: https://upload.pypi.org/legacy/ username: ambient-innovation [testpypi] repository: https://test.pypi.org/legacy/ username: ambient-innovation
- Create distribution
python setup.py sdist bdist_wheel
- Upload to Test-PyPi
twine upload --repository testpypi dist/*
- Check at Test-PyPi if it looks nice
- Upload to real PyPi
twine upload dist/*
- Make sure you have all the required packages installed
-
1.0.8 (2020-11-18)
- Added deprection warning to main init file
-
1.0.7 (2020-11-10)
- Deprecated package and moved most code to ai-django-core
-
1.0.6 (2020-08-06)
- Removed monkey-patched bugfixes from one year ago - hopefully these issues have been resolved by now
-
1.0.5 (2020-08-06)
- Extended compat with
graphene-django
- Extended compat with
-
1.0.4 (2019-04-05)
- Fixed a bug that a BooleanField from a django form would always be required
-
1.0.3 (2019-04-05)
- Added delete mutation for django-model objects
DeleteMutation
- Added delete mutation which ensures JWT authentication
- Added delete mutation for django-model objects
-
1.0.2 (2019-04-05)
- Updated deployment documentation
- Added markdown support to Readme file
-
1.0.1 (2019-04-05)
- Added documentation about
GraphQLTestCase
- Put version to variable in
__init__.py
- Added documentation about
-
1.0.0 (2019-04-04)
- Initial package released