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
- Django Relay Endpoint
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
- Add
'graphene_django'
to your project'ssettings.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, theapp_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 notmodel
(string): required, must be declared in form ofapp_label.ModelName
.overwrite
(bool): (optional) same as top leveloverwrite
, except if it is present, the top level overwrite will not apply.query
(bool): (optional) whether to generate query modules or not, defaults toTrue
query_fields
(list[str]): a list of of fields to configure, if none provided, all model fields will be configuredcreate_mutation
(bool): whether to generate mutation modules for creation, defaults toTrue
create_mutation_fields
(list[str]): a list of of fields to configure, if none provided, all model fields will be configuredupdate_mutation
whether to generate mutation modules for updating, defaults toTrue
update_mutation_fields
(list[str]): a list of of fields to configure, if none provided, all model fields will be configureddelete_mutation
(bool): whether to generate mutation modules for deletion, defaults toTrue
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 tographene.List(graphene.ID, **field_kwargs)
and graphene.ID(**field_kwargs)
, it will also infer therequired
parameter value from the declaredallow_blank
andallow_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>
andremove_<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:
- graphene_django: https://docs.graphene-python.org/projects/django/
- graphene-file-upload: https://github.com/lmcgartland/graphene-file-upload
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
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 Distributions
Built Distribution
Hashes for django_relay_endpoint-2.0.0-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 652b7a49dfdcfec6928dc9b537c5e8ca3d624f81d7d9a20febb3613e4d5e347c |
|
MD5 | abb831531ddcfe9dc7878f79c0ca2644 |
|
BLAKE2b-256 | bc28e03c7f1bf8fda84f4f7d90e0a389fa9419823019a4f42caa2d377866d23d |