Package: django-relay-endpoint
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".Table of ContentsDjango Relay EndpointTable of ContentsRequirementsInstallationHow to useAdding custom query and mutation typesConfiguring custom NodeType for node root fieldConfiguring NodeType subclassesValidatorsPermissionsUseful subclasses and toolsLicenseDocumentationTip the authorClientTo-DoRequirementsPython 3.8 or higher
Django 4.2.7 or higherIt 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 setupPython 3.11.2Django >= 4.2.7graphene-django >= 3.3.0graphene-file-upload >= 1.3.0django_filter >= 23.3.0This package will also installgraphene-django,graphene-file-uploadanddjango_filter.InstallationInstall using pip:pip install django-relay-endpointHow to useAdd'graphene_django'to your project'ssettings.py:INSTALLED_APPS = [
    # ... other apps and addons
    'graphene_django',
]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 usesFileUploadGraphQLViewfromgraphene_file_upload.djangoto support file uploads.Adding custom query and mutation typesTheSchemaConfiguratorinstancehasqueryandmutationproperties of type List, when instantiated.Custom object types can be appended to thequeryandmutationproperties: 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 fieldPer relay specification the endpoint must implementnoderoot field.
The configured server implements thenoderoot field with default configuration per graphene.If the developer wants to provide customNodeTypeit can do so by subclassinggraphene.ObjectTypeand 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 subclassesA 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. seehttps://docs.graphene-python.org/projects/django/en/latest/filtering/#filterable-fields.filterset_class: FilterSet - a filterset_class. seehttps://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-caseAppNameModelNameType.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 serializerextra_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 seehttps://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 therequiredparameter value from the declaredallow_blankandallow_nullparameters of the respective model.field.field_validators: Dict[str, List[Callable]] - a dictionary of field_names mapped to the list of validators: seeValidators.non_field_validators: List[Callable] - list of validators: seeValidators.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. seePermissions.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 inHow to use.ValidatorsA validator passed tofield_validatorsornon_field_validatorsis a function that takes the following arguments:data: the field value for field_validators and whole data object for non_field_validatorsnot_updated_model_instance: the instance with the state before merging data with the instanceinfo: the graphene resolve info object instance.PermissionsWe have extended DjangoObjectType and ClientIDMutation to support string permissions and class based permissions for queryset and object level permission checks.Class based permissions extend customBasePermissionclass, which implementshas_permission(self, info) -> boolandhas_object_permission(self, info, obj) -> boolmethods. If the class returnsFalsea 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 frameworkIsAuthenticated: 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 toolsThe addon comes with builtin DjangoClientIDMutation abstract subclass, which implements following methodsget_queryset: same as on graphen_django.DjangoObjectTypeget_node: same as on graphen_django.DjangoObjectTypecreate_node: creates an empty instance of the given modevalidate: 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 theadd_<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 amutate_and_get_payloadclassmethod, the developer must implement it on a subclass.LicenseSee the MIT licens in the LICENSE file in the project.DocumentationThe addon is pretty simple. TheHow to useandConfiguring NodeType subclassesexplains 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-uploadTip the authorIf 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:Opera browser wallet:3N5ot3DA2vSLwEqhjTGhfVnGaAuQoWBrCfThank you!ClientAn ember client with ember-data style encapsulation layer is on the way.To-DoDefault Login configurator