Skip to main content

Django addon to automatically configure graphql endpoints based on Django models.

Project description

Django Relay Endpoint

"Django Relay Endpoint" is a Django addon that automatically configures a customizable graphql endpoint from models. The addon is made for "graphene_django".

This addon contains modules for dynamic autogeneration of an endpoint. However, given the limitations of customization, instead, dre-from-model and dre-from-json commands were supplied in version 2. These commands use the django_relay_endpoint's DjangoClientIDMutation, validators and persissions for ease of customization. They can be imported directly from django_relay_endpoint package.

Table of Contents

Requirements

Python 3.8 or higher Django 4.2.7 or higher

It is possible that the addon will work with lower code setup. Keep in mind that the code uses __init_subclass__ method and format string. It was tested made with the following code setup

  • Python 3.11.2 (probably will work on older versions, the addon was tested on 3.11 and 3.12)
  • Django >= 4.2.7
  • graphene-django >= 3.3.0
  • graphene-file-upload >= 1.3.0
  • django_filter >= 23.3.0

This package will also install graphene-django, graphene-file-upload and django_filter.

Installation

Install using pip:

pip install django-relay-endpoint

Install using uv

uv pip install django-relay-endpoint

How to use

  1. Add 'graphene_django' to your project's settings.py:
INSTALLED_APPS = [
    # ... other apps and addons
    'graphene_django',
]

Done, now you can use the commands to generate the necessary modules.

Commands

The addon comes with two commands

  • dre-from-model
  • dre-from-json

The dre-from-model one generates modules from a single model, while dre-from-json generates a type schema from a json file. dre stands for django-relay-endpoint.

The commands will generate nodes, input_types, queries and mutations folders inside the provided app, generate necessary modules inside them with conventional naming, as well as create a urls and schema modules.

Imporatant! After the generation of the schema, you must import and extend the respective query, mutation and subscription classes inside the schema module as well as inlcude the urls module in your project's urls module.

Usage dre-from-model

Example usage

python manage.py dre-from-model app_label.ModelName --in--app api

Available arguments/options:

  • "model": dot-separated app_label and model class name, e.g. 'app.Model'
  • "--in-app", "-in": the django app where to generate the schema models in
  • "--overwrite", "-owt": whether to overwrite the modeles o not, defaults to False
  • "--query", "-q": whether to generate query modules or not, defaults to True
  • "--query-fields", "-qf": a list of of fields to configure, if none provided, all model fields will be configured
  • "--create-mutation", "-cm": whether to generate mutation modules for creation, defaults to True
  • "--create-mutation-fields", "-cmf": the list of fields to configure, if none provided, all model fields will be configured
  • "--update-mutation", "-um": whether to generate mutation modules for updating, defaults to True
  • "--update-mutation-fields", "-umf": the list of fields to configure, if none provided, all model fields will be configured
  • "--delete-mutation", "-dm": whether to generate mutation modules for deletion, defaults to True

Usage dre-from-json

Example usage

python manage.py dre-from-schema --read app_label/type_config.json

Available arguments/options:

  • "--read", "-r: path to the json file.

The json file must have the following structure.

  • schema_app (str): required, the app_label, where the schema modules will be generated

  • type_config (list[dict]): required, each dictionary is for each model, and accepts the following kwargs:

  • overwrite (bool): optional, whether to overwrite existing files or not

    • model (string): required, must be declared in form of app_label.ModelName.
    • overwrite (bool): (optional) same as top level overwrite, except if it is present, the top level overwrite will not apply.
    • query (bool): (optional) whether to generate query modules or not, defaults to True
    • query_fields (list[str]): a list of of fields to configure, if none provided, all model fields will be configured
    • create_mutation (bool): whether to generate mutation modules for creation, defaults to True
    • create_mutation_fields (list[str]): a list of of fields to configure, if none provided, all model fields will be configured
    • update_mutation whether to generate mutation modules for updating, defaults to True
    • update_mutation_fields (list[str]): a list of of fields to configure, if none provided, all model fields will be configured
    • delete_mutation (bool): whether to generate mutation modules for deletion, defaults to True

Overwrite will not overwrite existing urls and schema modules.

Django relay endpoint comes with autoconfigurable dynamic endpoint, which has limitations. Instead better use the commands for better manual customization. The autoconfiguration modules are deprecated and the author does not intend to support dynamic endpoint furthermore.

Dynamic endpoint

Simple usage

Declare your NodeTypes and pass it to the SchemaConfigurator to get the schema. e.g.

# endpoint.py
from django_relay_endpoint import NodeType, SchemaConfigurator

class AuthorType(NodeType):
    @staticmethod
    def get_queryset(object_type, queryset, info):
        return queryset.filter(age__gte=35) # filter authors with age higher than 35

    class Meta:
        model = 'my_app.Author'
        fields = ['id', 'name', 'age', 'books']
        filter_fields = {
            'id': ('exact',),
            'name': ('iexact', 'icontains'),
        }
        extra_kwargs: {
            'name': {
                "required": True,
            },
            'age': {
                "required": True,
            }
        }

class BookType(NodeType):
    class Meta:
        model = 'my_app.Book'
        fields = ['id', 'name', 'authors']


schema = SchemaConfigurator([
    AuthorType,
    BookType,
]).schema()

In your urls.py add the endpoint

# urls.py
from graphene_file_upload.django import FileUploadGraphQLView
from my_app.endpoint import schema
from django.views.decorators.csrf import csrf_exempt

urlpatterns = [
    # ... other urls
    path("graphql_dashboard_v1", csrf_exempt(FileUploadGraphQLView.as_view(graphiql=True, schema=schema))),
]

It uses FileUploadGraphQLView from graphene_file_upload.django to support file uploads.

Adding custom query and mutation types

The SchemaConfigurator instance has query and mutation properties of type List, when instantiated.

Custom object types can be appended to the query and mutation properties: e.g.

# let's imagine we have created a mutation that handles login
from my_app.models import Book

class AuthType(Mutation): ...

# and we have BookType configured with NodeType 
class BookType(NodeType):
    class Meta:
        model = Book
        fields = '__all__'

schema = SchemaConfigurator([
    BookType,
]) # will instantiate the SchemaConfigurator with rootfields form BookType.

schema.mutations.append(AuthType) # adds AuthType to mutations.

schema = schema.schema() # overwrite schema with actual schema. This will create the actual schema by extending all types and return the schema.

Configuring custom NodeType for node root field

Per relay specification the endpoint must implement node root field. The configured server implements the node root field with default configuration per graphene.

If the developer wants to provide custom NodeType it can do so by subclassing graphene.ObjectType and providing own resolver, e.g.

class CustomNodeType(graphene.ObjectType):
    node = graphene.relay.Node.Field()

    @classmethod
    resolve_node(cls, root, info, id):
        # implement custom type identification using from_global_id
        ...

schema = SchemaConfigurator([
    AuthorType,
    BookType,
])

# overwrite the default NodeType with CustomNodeType
schema.node_type = CustomNodeType

# Overwrite schema with actual generated schema.
schema = schema.schema()

Configuring NodeType subclasses

A subclass of NodeType can be configured via its Meta class.

Available options are as follows:

Following options can be configured on class Meta:

  • model: (Union[str, Type[models.Model]]) - a string composed of 'app_name.model_name' or actual django model.
  • fields: List[str] | Literal["all"] - an explicit list of field names or 'all'.
  • query_root_name: str | None - a root field name. Defaults to lowered snake-case model._meta.verbose_name.
  • query_root_name_plural: str | None - a root field name. Defaults to lowered snake-case model._meta.verbose_name_plural.
  • filter_fields: Union[Dict[str, List[str]], List[str]] - fielter_fields configurations. see https://docs.graphene-python.org/projects/django/en/latest/filtering/#filterable-fields.
  • filterset_class: FilterSet - a filterset_class. see https://docs.graphene-python.org/projects/django/en/latest/filtering/#custom-filtersets.
  • object_type_name: str | None - The classname of the DjangoObjectType that will be configured. Defaults to camel-case AppNameModelNameType.
  • mutation_operations: Literal["create", "update", "delete"] - similar to query_operations, this limits the root field configuration, defaults to ["create", "update", "delete"].
  • extra_kwargs: Dict[str, Dict[str, Any]] - the mutation type fields are configured via assigned django form field; this option is similar to rest framework serializer extra_kwargs, which is a dictionary of field_names mapped to a dictionary of django form field kwargs. The configurator automatically maps the field to the respective form field: for field mapping see https://docs.djangoproject.com/en/4.2/topics/forms/modelforms/#field-types. For relations, it maps the fields to graphene.List(graphene.ID, **field_kwargs) and graphene.ID(**field_kwargs), it will also infer the required parameter value from the declared allow_blank and allow_null parameters of the respective model.field.
  • field_validators: Dict[str, List[Callable]] - a dictionary of field_names mapped to the list of validators: see Validators.
  • non_field_validators: List[Callable] - list of validators: see Validators.
  • success_keyword: str - a success keyword for mutation responses. by defualt it is 'success'.
  • input_field_name: str - a Input field name for mutations. Defaults to 'data'.
  • return_field_name: str - the field name on the response on create and update mutations, if none provided, model._meta.model_name will be used.
  • permissions: List[str] - A list of permission names, defaults to empty list, i.e. no permissions will be checked.
  • permission_classes: List[Type[BasePermission]] - A list of permission classes. see Permissions.

Following fields can be configured on the subclass of the NodeType:

  • get_queryset: Callable - a static get_queryset method. Important! this method should be declared as staticmethod, it will be returned with the configured subclass of DjangoObjectType, queryset and info. It behaves as overwrite of get_queryset method, but is a staticmethod. See the example in How to use.

Validators

A validator passed to field_validators or non_field_validators is a function that takes the following arguments:

  • data: the field value for field_validators and whole data object for non_field_validators
  • not_updated_model_instance: the instance with the state before merging data with the instance
  • info: the graphene resolve info object instance.

Permissions

The addon has extended DjangoObjectType and ClientIDMutation to support string permissions and class based permissions for queryset and object level permission checks.

Class based permissions extend custom BasePermission class, which implements has_permission(self, info) -> bool and has_object_permission(self, info, obj) -> bool methods. If the class returns False a permission-denied error will be raised. Following default permission classes can be found in graphene_relay_endpoint:

  • AllowAny: This class is intended only for explicit declaration. It does nothing similar to the same permission in REST framework
  • IsAuthenticated: Checks for authentication.
  • IsAdminUser: Checks for admin privilege.
  • IsAuthenticatedOrReadOnly: Limits mutation operations to authenticated users.
  • BasePermission: A base class to subclass for custom permission classes.

Useful subclasses and tools

The addon comes with builtin DjangoClientIDMutation abstract subclass, which implements following methods

  • get_queryset: same as on graphen_django.DjangoObjectType
  • get_node: same as on graphen_django.DjangoObjectType
  • create_node: creates an empty instance of the given mode
  • validate: validates data via 'field_validators' and 'non_field_validators' supplied with the subclass of NodeType.
  • update_instance: set's the values on the instance from data. For to-many relations it uses the add_<field_name> and remove_<field_name> convention. First it adds than it removes. The client can pass both, and the relations will be added and removed consecutively before being saved.

N.B. DjangoClientIDMutation does not implement a mutate_and_get_payload classmethod, the developer must implement it on a subclass.

License

See the MIT licens in the LICENSE file in the project.

Documentation

The addon is pretty simple. The How to use and Configuring NodeType subclasses explains it all. Each piece of code is also documented with dockstrings and has respective type hints. For additional information read the respective documentation:

Tip the author

If this project has facilitated your job, saved time spent on boilerplate code and pain of standardizing and debugging a relay style endpoint, consider tipping (donating) the author with some crypto:

Wallet: 3N5ot3DA2vSLwEqhjTGhfVnGaAuQoWBrCf

Thank you!

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

django_relay_endpoint-2.0.0-py3-none-any.whl (37.9 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page