Graphene-Django-Extras add some extra funcionalities to graphene-django to facilitate the graphql use without Relay and allow paginations and filtering integration
Project description
- This package add some extra functionalities to graphene-django to facilitate the graphql use without Relay:
Allow pagination and filtering on Queries.
Allow to define DjangoRestFramework serializers based Mutations.
Add support to Subscription’s requests and its integration with websockets using Channels package.
Installation:
For installing graphene-django-extras, just run this command in your shell:
pip install "graphene-django-extras"
Documentation:
Extra functionalities:
- Fields:
DjangoObjectField
DjangoFilterListField
DjangoFilterPaginateListField
DjangoListObjectField (Recommended for Queries definition)
- Mutations:
DjangoSerializerMutation (Recommended for Mutations definition)
- Types:
DjangoListObjectType (Recommended for Types definition)
DjangoInputObjectType
- Paginations:
LimitOffsetGraphqlPagination
PageGraphqlPagination
CursorGraphqlPagination (coming soon)
- Subscriptions:
Subscription (Abstract class to define subscriptions to a DjangoSerializerMutation)
GraphqlAPIDemultiplexer (Custom WebSocket consumer subclass that handles demultiplexing streams)
Queries and Mutations examples:
This is a basic example of graphene-django-extras package use:
1- Types Definition:
from django.contrib.auth.models import User
from graphene_django import DjangoObjectType
from graphene_django_extras import DjangoListObjectType
from graphene_django_extras.pagination import LimitOffsetGraphqlPagination
class UserType(DjangoObjectType):
class Meta:
model = User
description = " Type definition for a single user object "
filter_fields = {
'id': ['exact', ],
'first_name': ['icontains', 'iexact'],
'last_name': ['icontains', 'iexact'],
'username': ['icontains', 'iexact'],
'email': ['icontains', 'iexact']
}
class UserListType(DjangoListObjectType):
class Meta:
description = " Type definition for users objects list "
model = User
pagination = LimitOffsetGraphqlPagination()
2- InputTypes can be defined for use on mutations:
from graphene_django_extras import DjangoInputObjectType
class UserInput(DjangoInputObjectType):
class Meta:
description = " User Input Type for used as input on Arguments classes on traditional Mutations "
model = User
3- Defining Mutations:
You can define traditional mutations that use Input Types or Mutations based on DRF SerializerClass:
import graphene
from .serializers import UserSerializer
from graphene_django_extras import DjangoSerializerMutation
from .types import UserType
from .input_types import UserInputType
class UserSerializerMutation(DjangoSerializerMutation):
"""
DjangoSerializerMutation auto implement Create, Delete and Update functions
"""
class Meta:
description = " Serializer based Mutation for Users "
serializer_class = UserSerializer
class UserMutation(graphene.Mutation):
"""
On traditional mutation classes definition you must implement the mutate function
"""
user = graphene.Field(UserType, required=False)
class Arguments:
new_user = graphene.Argument(UserInput)
class Meta:
description = " Traditional graphene mutation for Users "
@classmethod
def mutate(cls, root, info, *args, **kwargs):
...
4- Defining schemes:
import graphene
from graphene_django_extras import DjangoObjectField, DjangoListObjectField, DjangoFilterPaginateListField, DjangoFilterListField, LimitOffsetGraphqlPagination
from .types import UserType, UserListType
from .mutations import UserMutation, UserSerializerMutation
class Queries(graphene.ObjectType):
# Possible User list queries definitions
all_users = DjangoListObjectField(UserListType, description=_('All Users query'))
all_users1 = DjangoFilterPaginateListField(UserType, pagination=LimitOffsetGraphqlPagination())
all_users2 = DjangoFilterListField(UserType)
all_users3 = DjangoListObjectField(UserListType, filterset_class=UserFilter, description=_('All Users query'))
# Defining a query for a single user
# The DjangoObjectField have a ID input field, that allow filter by id and is't necessary resolve method definition
user = DjangoObjectField(UserType, description=_('Single User query'))
# Another way to define a single user query
user1 = DjangoObjectField(UserListType.getOne(), description=_('User List with pagination and filtering'))
class Mutations(graphene.ObjectType):
user_create = UserSerializerMutation.CreateField(deprecation_reason='Some deprecation message')
user_delete = UserSerializerMutation.DeleteField()
user_update = UserSerializerMutation.UpdateField()
traditional_user_mutation = UserMutation.Field()
5- Examples of queries:
{
allUsers(username_Icontains:"john"){
results(limit:5, offset:5){
id
username
firstName
lastName
}
totalCount
}
allUsers1(lastName_Iexact:"Doe", limit:5, offset:0){
id
username
firstName
lastName
}
allUsers2(firstName_Icontains: "J"){
id
username
firstName
lastName
}
user(id:2){
id
username
firstName
}
user1(id:2){
id
username
firstName
}
}
6- Examples of Mutations:
mutation{
userCreate(newUser:{password:"test*123", email: "test@test.com", username:"test"}){
user{
id
username
firstName
lastName
}
ok
errors{
field
messages
}
}
userDelete(id:1){
ok
errors{
field
messages
}
}
userUpdate(newUser:{id:1, username:"John"}){
user{
id
username
}
ok
errors{
field
messages
}
}
}
Subscriptions:
This first approach to support Graphql subscriptions with Channels in graphene-django-extras, use channels-api package.
1- Defining custom Subscriptions classes:
You must to have defined a DjangoSerializerMutation class for each model that you want to define a Subscription class:
# app/graphql/subscriptions.py
import graphene
from graphene_django_extras.subscription import Subscription
from .mutations import UserMutation, GroupMutation
class UserSubscription(Subscription):
class Meta:
mutation_class = UserMutation
stream = 'users'
description = 'Subscription for users'
class GroupSubscription(Subscription):
class Meta:
mutation_class = GroupMutation
stream = 'groups'
description = 'Subscriptions for groups'
Add ours subscriptions definitions into our app schema:
# app/graphql/schema.py
import graphene
from .subscriptions import UserSubscription, GroupSubscription
class Subscriptions(graphene.ObjectType):
user_subscription = UserSubscription.Field()
GroupSubscription = PersonSubscription.Field()
Add your app schema into your project root schema:
# schema.py
import graphene
import custom.app.route.graphql.schema
class RootQuery(custom.app.route.graphql.schema.Query, graphene.ObjectType):
class Meta:
description = 'Root Queries for my Project'
class RootSubscription(custom.app.route.graphql.schema.Mutation, graphene.ObjectType):
class Meta:
description = 'Root Mutations for my Project'
class RootSubscription(custom.app.route.graphql.schema.Subscriptions, graphene.ObjectType):
class Meta:
description = 'Root Subscriptions for my Project'
schema = graphene.Schema(
query=RootQuery,
mutation=RootMutation,
subscription=RootSubscription
)
2- Defining Channels settings and custom routing config:
Note: For more information about this step see Channels documentation.
You must to have defined a DjangoSerializerMutation class for each model that you want to define a Subscription class:
# app/routing.py
from graphene_django_extras.subscriptions import GraphqlAPIDemultiplexer
from channels.routing import route_class
from .graphql.subscriptions import UserSubscription, GroupSubscription
class CustomAppDemultiplexer(GraphqlAPIDemultiplexer):
consumers = {
'users': UserSubscription.get_binding().consumer,
'groups': GroupSubscription.get_binding().consumer
}
app_routing = [
route_class(CustomAppDemultiplexer)
]
Defining our project routing, like custom root project urls:
# project/routing.py
from channels import include
project_routing = [
include("custom.app.folder.routing.app_routing", path=r"^/custom_websocket_path"),
]
You should add channels and channels_api modules into your INSTALLED_APPS setting and you must defining your routing project definition into the CHANNEL_LAYERS setting:
# settings.py
...
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
...
'channels',
'channels_api',
'custom_app'
)
CHANNEL_LAYERS = {
"default": {
"BACKEND": "asgiref.inmemory.ChannelLayer",
"ROUTING": "myproject.routing.project_routing", # Our project routing
},
}
...
3- Examples of Subscriptions:
In your client you must define websocket connection to: ‘ws://host:port/custom_websocket_path’. When the connection is established, the server return a websocket message like this: {“channel_id”: “GthKdsYVrK!WxRCdJQMPi”, “connect”: “success”}, where you must store the channel_id value to later use in your graphql subscriptions request for subscribe or unsubscribe operations. The Subscription accept five possible parameters:
operation: Operation to perform: subscribe or unsubscribe. (required)
action: Action you wish to subscribe: create, update, delete or all_actions. (required)
channelId: Websocket connection identification. (required)
id: ID field value of model object that you wish to subscribe to. (optional)
data: List of desired model fields that you want in subscription’s notification. (optional)
subscription{
userSubscription(
action: UPDATE,
operation: SUBSCRIBE,
channelId: "GthKdsYVrK!WxRCdJQMPi",
id: 5,
data: [ID, USERNAME, FIRST_NAME, LAST_NAME, EMAIL, IS_SUPERUSER]
){
ok
error
stream
}
}
In this case, the subscription request sanded return a websocket message to client like this: {“action”: “update”, “operation”: “subscribe”, “ok”: true, “stream”: “users”, “error”: null} and each time than the user with id=5 get modified, you will receive a message through websocket’s connection with the following format:
{
"stream": "users",
"payload": {
"action": "update",
"model": "auth.user",
"data": {
"id": 5,
"username": "meaghan90",
"first_name": "Meaghan",
"last_name": "Ackerman",
"email": "meaghan@gmail.com",
"is_superuser": false
}
}
}
For unsubscribe you must send a graphql subscription request like this:
subscription{
userSubscription(
action: UPDATE,
operation: UNSUBSCRIBE,
channelId: "GthKdsYVrK!WxRCdJQMPi",
id: 5
){
ok
error
stream
}
}
NOTE: Each time than the Graphql server restart, you must to reestablish the websocket’s connection and resend the subscription graphql request with a new websocket connection id.
Change Log:
v0.1.0-alpha6:
Fixed with exclude fields and converter function.
v0.1.0-alpha5:
Updated to graphene-django>=2.0.
Fixed minor bugs on queryset_builder performance.
v0.1.0-alpha4:
v0.1.0-alpha3:
1. Fixed bug on subscriptions when not specified any field in “data” parameter to bean return on notification message.
v0.1.0-alpha2:
Fixed bug when subscribing to a given action (create, update pr delete).
Added intuitive and simple web tool to test notifications of graphene-django-extras subscription.
v0.1.0-alpha1:
Added support to multiselect choices values for models.CharField with choices attribute, on queries and mutations. Example: Integration with django-multiselectfield package.
Added support to GenericForeignKey and GenericRelation fields, on queries and mutations.
Added first approach to support Subscriptions with Channels, with subscribe and unsubscribe operations. Using channels-api package.
Fixed minors bugs.
v0.0.4:
Fix error on DateType encode.
v0.0.3:
Implement custom implementation of DateType for use converter and avoid error on Serializer Mutation.
v0.0.2:
Changed dependency of DRF to 3.6.4 on setup.py file, to avoid an import error produced by some changes in new version of DRF=3.7.0 and because DRF 3.7.0 dropped support to Django versions < 1.10.
v0.0.1:
Fixed bug on DjangoInputObjectType class that refer to unused interface attribute.
Added support to create nested objects like in DRF <http://www.django-rest-framework.org/api-guide/serializers/#writable-nested-representations>, it’s valid to SerializerMutation and DjangoInputObjectType, only is necessary to specify nested_fields=True on its Meta class definition.
Added support to show, only in mutations types to create objects and with debug=True on settings, inputs autocomplete ordered by required fields first.
Fixed others minors bugs.
v0.0.1-rc.2:
Make queries pagination configuration is more friendly.
v0.0.1-rc.1:
Fixed a bug with input fields in the converter function.
v0.0.1-beta.10:
Fixed bug in the queryset_factory function because it did not always return a queryset.
v0.0.1-beta.9:
Remove hard dependence with psycopg2 module.
Fixed bug that prevented use queries with fragments.
Fixed bug relating to custom django_filters module and ordering fields.
v0.0.1-beta.6:
Optimizing imports, fix some minors bugs and working on performance.
v0.0.1-beta.5:
Repair conflict on converter.py, by the use of get_related_model function with: OneToOneRel, ManyToManyRel and ManyToOneRel.
v0.0.1-beta.4:
First commit
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for graphene-django-extras-0.1.0a6.tar.gz
Algorithm | Hash digest | |
---|---|---|
SHA256 | f8ae937c3993bfd589d9974913451829d1cee4ff400f6dd959a3be31ed8fef59 |
|
MD5 | 6c6dca3e2c3f888f531082a03d33a7b2 |
|
BLAKE2b-256 | 6584ce6b3053244d4cb098d2c68788831ff7e514b1d330c3598e89a20c1b2029 |
Hashes for graphene_django_extras-0.1.0a6-py2.py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 5473a5a26795a7060fe585e11f1576a6c505b1bd672f10e0e949bf77f24f4fcc |
|
MD5 | 424f82c7e19cf910f9c22a15e67f47c4 |
|
BLAKE2b-256 | 708b0abb74a2b3b8d12f8c943e07a4fc546b13d99615ce4121eaa0233877aea6 |