Skip to content

JoaRiski/django-simple-graphql

Repository files navigation

django-simple-graphql

A stupid simple GraphQL setup for Django

This project is still a WIP and will receive breaking changes

TODO

  • 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
  • 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.

Features (already supported)

TODO: Improve the documentation

  • Enable GraphQL queries for Django models with a decorator
    • By default, includes a getModelName and listModelName queries
    • Configure by adding a GraphQL meta class to the model class
    • Alternatively supply a configuration class to the decorator
  • Supported configuration options
    • filters: A django-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 schema
    • search_fields: A list of fields to perform search on
    • ordering_fields: A list of fields that can be used to order results
    • default_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.

Usage

Setup

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.

  1. Add graphene-django to your INSTALLED_APPS:
    INSTALLED_APPS = [
        # ...
        "django.contrib.staticfiles", # Required for GraphiQL
        "graphene_django",
    ]
  2. 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))),
    ]
  3. Create a schema file (e.g. schema.py) and configure it to Graphene:
    # settings.py
    GRAPHENE = {
        "SCHEMA": "myapp.shcema.schema",
    }
  4. Declare the schema in your schema file
    # schema.py
    from simple_graphql.django import Schema
    
    schema = Schema()

Default queries

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

Search

TODO

Examples

Registering models

There's two ways models can be added to the schema

With a class decorator

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()

With a function call

from django.contrib.auth import get_user_model

from myapp.schema import schema

User = get_user_model()

schema.register_model(User)

Configuring models

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:

  1. Configuration supplied via parameters
  2. Metaclass based configuration
  3. Default configuration

Where lower number means higher priority.

Metaclass configuration

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()

Parameter configuration (with a class)

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)

Parameter configuration (with a config object)

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)

Parameter configuration (function variant)

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"],
))

About

No description or website provided.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages