A stupid simple GraphQL setup for Django
This project is still a WIP and will receive breaking changes
- Support mutations
- Support subscriptions
- An easy default for authentication
- Account for reverse-relations automatically
- Handle django-graphene issue with relations ponting to non-pk fields and ID encoding
- Configurability
- Custom set of node interfaces (currently relay.Node)
- Custom relation connection handler (currently DjangoFilterConnectionField)
- Custom node/query/mutation/subscription builder functions
- Custom schema member naming
- Custom search
- Enable/disable search globally
- Enable/disable ordering globally
- Global default ordering options
- Injection of GraphQL ID property to models, configurable name and/or disable
- Examples
- More lax version pinning (Min python 3.5 or higher)
- Test suite against multiple version configurations
- Proper readme
- Better type definitions
- GraphQL schema docstring generation
- Validation checks when building the schema to prevent blatantly incorrect config
- For example, a field in search fields that doesn't exist or isn't supported
- Perhaps a way to auto-render the schema for github diffs?
- A way to easily include extra queries for models (e.g. with different filters)
- Require either field exclusions or inclusions to be explicitly defined
- Build a namespace package instead of a normal one (use "simple_graphql" as namespace root)
- Don't be as tightly coupled with graphene
- e.g. support to https://github.com/strawberry-graphql/strawberry would be nice
- Automatic CRUD operations
- Support more complex ordering options (as well as explicit naming of ordering)
- Run tests for code included in documentation
- Support for permissions
- Query cost analysis / rate limiting
- Consider supporting an alternative approach where registration decorator could be applied to a GraphQL config object instead of the model class
- Allow the register decorator be used with or without function call. Possibly also allow it's use as a non-decorator registering function.
- Add support for using the schema builder if there's need to combine with an existing graphene schema declaration.
TODO: Improve the documentation
- Enable GraphQL queries for Django models with a decorator
- By default, includes a
getModelName
andlistModelName
queries - Configure by adding a
GraphQL
meta class to the model class - Alternatively supply a configuration class to the decorator
- By default, includes a
- Supported configuration options
filters
: Adjango-filter
compatible set of filters supported on the model's QuerySet. List or a Dictionary.exclude_fields
: A list of field names to exclude from the schemasearch_fields
: A list of fields to perform search onordering_fields
: A list of fields that can be used to order resultsdefault_ordering
: What ordering to use if none was specified
- Adds a
graphql_node_name
field to model classes - Adds a
graphql_id
property to models, which can be used to retrieve the Global ID of a model instance.
Steps 1-3 are setup for graphene-django
.
See https://docs.graphene-python.org/projects/django/en/latest/installation/ for
more details.
If you are already using graphene-django
, you can skip to step 4.
- Add
graphene-django
to yourINSTALLED_APPS
:INSTALLED_APPS = [ # ... "django.contrib.staticfiles", # Required for GraphiQL "graphene_django", ]
- Add a GraphQL endpoint to the URL config:
from django.urls import path from django.views.decorators.csrf import csrf_exempt from graphene_django.views import GraphQLView urlpatterns = [ # ... path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))), ]
- Create a schema file (e.g.
schema.py
) and configure it to Graphene:# settings.py GRAPHENE = { "SCHEMA": "myapp.shcema.schema", }
- Declare the schema in your schema file
# schema.py from simple_graphql.django import Schema schema = Schema()
By default, all model classes registered to the schema will get a query for fetching a single object by ID as well as a list query.
For the sake of an example, let's say we have the following model declaration:
from django.db import models
from myapp.schema import schema
@schema.graphql_model()
class Person(models.Model):
first_name = models.TextField()
last_name = models.TextField()
The graphql_model
decorator will add the model to our GraphQL schema builder,
which will build it into the following schema (relay schema omitted):
type Person implements Node {
id: ID!
lastName: String!
firstName: String!
}
type Query {
getPerson(id: ID!): Person
listPerson(after: String, before: String, first: Int, last: Int, offset: Int): PersonConnection
}
For a more complete example of the generated schema, see example/schema.graphql
TODO
There's two ways models can be added to the schema
from django.db import models
from myapp.schema import schema
@schema.graphql_model()
class Person(models.Model):
first_name = models.TextField()
last_name = models.TextField()
from django.contrib.auth import get_user_model
from myapp.schema import schema
User = get_user_model()
schema.register_model(User)
Model specific schemas can be configured either with a metaclass or passed in as a parameter. A base configuration also is present regardless of custom declarations.
If multiple configurations are present, they will be merged in the following precedence:
- Configuration supplied via parameters
- Metaclass based configuration
- Default configuration
Where lower number means higher priority.
from django.db import models
from myapp.schema import schema
@schema.graphql_model()
class Person(models.Model):
first_name = models.TextField()
last_name = models.TextField()
credit_card_number = models.TextField()
parent = models.ForeignKey("self", on_delete=models.SET_NULL)
class GraphQL:
exclude_fields = ["credit_card_number"]
ordering_fields = ["first_name", "last_name"]
default_ordering = ["first_name"]
search_fields = ["first_name", "last_name"]
filters = ["parent"]
@staticmethod
def get_queryset(queryset: QuerySet["Person"], info: Any):
if info.context.user.is_superuser:
return queryset
return queryset.none()
from django.db import models
from myapp.schema import schema
class PersonGraphQLConfig:
exclude_fields = ["credit_card_number"]
ordering_fields = ["first_name", "last_name"]
default_ordering = ["first_name"]
search_fields = ["first_name", "last_name"]
filters = ["parent"]
@schema.graphql_model(PersonGraphQLConfig)
class Person(models.Model):
first_name = models.TextField()
last_name = models.TextField()
credit_card_number = models.TextField()
parent = models.ForeignKey("self", on_delete=models.SET_NULL)
from django.db import models
from simple_graphql.django import ModelSchemaConfig
from myapp.schema import schema
@schema.graphql_model(ModelSchemaConfig(
exclude_fields=["credit_card_number"],
ordering_fields=["first_name", "last_name"],
default_ordering=["first_name"],
search_fields=["first_name", "last_name"],
filters=["parent"],
))
class Person(models.Model):
first_name = models.TextField()
last_name = models.TextField()
credit_card_number = models.TextField()
parent = models.ForeignKey("self", on_delete=models.SET_NULL)
from django.contrib.auth import get_user_model
from simple_graphql.django import ModelSchemaConfig
from myapp.schema import schema
User = get_user_model()
# Could also use a class here just like with the decorator
schema.register_model(User, ModelSchemaConfig(
exclude_fields=["password"],
))