Skip to content

Mutations API Reference

This section provides detailed API documentation for mutation classes in django-graphex.

DjangoModelMutation

The primary mutation class that provides automatic CRUD operations driven directly by a Django model.

class DjangoModelMutation(ObjectType)

Meta Configuration

Configure mutations through a nested Meta class:

class CustomerMutation(DjangoModelMutation):
    class Meta:
        model = Customer
        description = "Customer CRUD operations"

Meta Options

Option Type Default Description
model Model Required Django model class
pydantic_model BaseModel Auto-generated Pydantic model for custom validation; auto-generated from model when omitted
only_fields tuple/list () Include only specified fields
exclude_fields tuple/list () Exclude specified fields
include_fields tuple/list () Additional fields to include
input_field_name str 'new_{model}' Name of input argument
output_field_name str '{model}' Name of output field
description str Auto-generated Mutation description
nested_fields dict () Nested field configuration — a {field_name: Model} mapping. The empty default means no nested writes. Every key must name a relation accessor on model; one that does not raises ImproperlyConfigured, listing the accessors that would have worked.
model_operations tuple ("create", "update", "delete") Which CRUD operations to generate; any subset of ("create", "update", "delete"). Calling the *Field() builder for an excluded operation raises AttributeError.
registry Registry Global registry Type registry the mutation's output node and input type resolve against. A custom registry scopes the whole mutation subgraph to one schema's own pair, so a forked DjangoGraphQLSchema re-forks the payload's output node into its own namespace instead of reaching the process-global node.

Anything outside this table raises

Every Meta key django-graphex itself reads is in the table above; any other key raises ImproperlyConfigured at class definition. That covers the typo (exclude_field for exclude_fields, which used to leave the column it named writable) and the option that belongs to the other host: queryset is read by DjangoModelType, never here. Declaring permission_classes raises for the same reason — see Mutations.

The one exception is the handful of keys the graphene ObjectType base consumes before django-graphex sees them — name, _meta, interfaces, possible_types, default_resolver and container. They are accepted, and only name and description do anything useful on a mutation.

Fields

Every DjangoModelMutation includes these standard fields:

Field Type Description
ok Boolean Success indicator
errors List[ErrorType] Validation errors
{model_name} ObjectType The created/updated/deleted object

Class Methods

__init_subclass_with_meta__(**kwargs) (classmethod)

Initialize the mutation subclass with meta configuration.

Parameters: - model (Model): Required Django model class - pydantic_model (BaseModel): Optional Pydantic model for custom validation - only_fields (tuple): Fields to include - exclude_fields (tuple): Fields to exclude - include_fields (tuple): Additional fields - input_field_name (str): Input argument name - output_field_name (str): Output field name - description (str): Mutation description - nested_fields (dict): Nested field configuration - model_operations (tuple): CRUD operations to generate - registry (Registry): Type registry the output node and input type resolve against

get_errors(errors) (classmethod)

Create error response with provided errors.

Parameters: - errors (list): List of error objects

Returns: Mutation instance with errors

perform_mutate(obj, info) (classmethod)

Create successful mutation response.

Parameters: - obj (Model): The model instance - info (ResolveInfo): GraphQL resolve info

Returns: Mutation instance with success response

save_with_nested(root, info, data, instance=None, serializer_kwargs=None) (classmethod)

Validate and persist the parent plus any Meta.nested_fields children atomically (provided by NestedFieldsMixin). Forward FK/O2O children are written before the parent and their pk injected; reverse FK/O2O and M2M children are written after and linked to it. A reverse child (FK or O2O) supplied by pk is rejected when it currently belongs to a different parent. Any validation failure rolls the whole transaction back. See How nested writes work.

Parameters: - root (Any): Root object - info (ResolveInfo): GraphQL resolve info - data (dict): Input data (nested entries are popped from it) - instance (Model | None): Existing instance for an update, else None - serializer_kwargs (dict | None): Reserved (unused by the native backend)

Returns: (ok: bool, obj_or_errors) — the saved object, or a list of ErrorType

CRUD Operations

create(root, info, **kwargs) (classmethod)

Create a new object using the provided data.

Parameters: - root (Any): Root object - info (ResolveInfo): GraphQL resolve info - **kwargs: Mutation arguments including input data

Returns: Mutation response with created object or errors

update(root, info, **kwargs) (classmethod)

Update an existing object with provided data.

Parameters: - root (Any): Root object - info (ResolveInfo): GraphQL resolve info - **kwargs: Mutation arguments including input data

Returns: Mutation response with updated object or errors

delete(root, info, **kwargs) (classmethod)

Delete an object by its ID.

Parameters: - root (Any): Root object - info (ResolveInfo): GraphQL resolve info - **kwargs: Mutation arguments including object ID

Returns: Mutation response with deleted object or errors

Customizing persistence

There is no separate save hook. To run logic around create/update, override create / update and call super(); to change how the parent and its nested children are validated and written, override save_with_nested.

Field Generation Methods

CreateField(*args, **kwargs) (classmethod)

Create a GraphQL field for create mutations.

Returns: Field instance configured for create operations

UpdateField(*args, **kwargs) (classmethod)

Create a GraphQL field for update mutations.

Returns: Field instance configured for update operations

DeleteField(*args, **kwargs) (classmethod)

Create a GraphQL field for delete mutations.

Returns: Field instance configured for delete operations

MutationFields(*args, **kwargs) (classmethod)

Get all mutation fields (create, delete, update).

Returns: Tuple of (create_field, delete_field, update_field)

Example Usage

from django_graphex.mutation import DjangoModelMutation
from .models import Customer

class CustomerMutation(DjangoModelMutation):
    class Meta:
        model = Customer
        description = "Customer CRUD operations"
class CustomerMutation(DjangoModelMutation):
    class Meta:
        model = Customer
        exclude_fields = ('internal_notes', 'credit_limit')
        input_field_name = 'customer_data'
        output_field_name = 'customer'
from .models import Address, Profile

class CustomerMutation(DjangoModelMutation):
    class Meta:
        model = Customer
        # each nested field maps to its related Django model
        nested_fields = {
            'profile': Profile,
            'addresses': Address,
        }
from django_graphex.core import BooleanField

class CustomerMutation(DjangoModelMutation):
    class Meta:
        model = Customer

    class Arguments:
        send_email = BooleanField(
            default=False,
            description="Send welcome email",
        )

    @classmethod
    def create(cls, root, info, **kwargs):
        send_email = kwargs.pop('send_email', False)
        response = super().create(root, info, **kwargs)
        if response.ok and send_email:
            send_welcome_email(getattr(response, cls._meta.output_field_name).email)
        return response
from pydantic import BaseModel, field_validator

class CustomerValidation(BaseModel):
    @field_validator("email", check_fields=False)
    @classmethod
    def corporate_only(cls, value):
        if value and not value.endswith("@example.com"):
            raise ValueError("Only corporate email addresses are accepted.")
        return value

class CustomerMutation(DjangoModelMutation):
    class Meta:
        model = Customer
        # supply a Pydantic model with extra validators; the derived
        # model fields extend it
        pydantic_model = CustomerValidation

Schema Integration

from django_graphex.core import ObjectType
from django_graphex.schema import DjangoGraphQLSchema

class Mutation(ObjectType):
    create_customer = CustomerMutation.CreateField()
    update_customer = CustomerMutation.UpdateField()
    delete_customer = CustomerMutation.DeleteField()

schema = DjangoGraphQLSchema(query=Query, mutation=Mutation)
from django_graphex.core import ObjectType
from django_graphex.schema import DjangoGraphQLSchema

class Mutation(ObjectType):
    create_customer, delete_customer, update_customer = CustomerMutation.MutationFields()

schema = DjangoGraphQLSchema(query=Query, mutation=Mutation)

GraphQL Operations

Create Mutation

mutation CreateCustomer($customerData: CustomerInput!) {
  createCustomer(newCustomer: $customerData) {
    ok
    customer {
      id
      username
      email
    }
    errors {
      field
      messages
    }
  }
}

Update Mutation

mutation UpdateCustomer($customerData: CustomerInput!) {
  updateCustomer(newCustomer: $customerData) {
    ok
    customer {
      id
      username
      email
    }
    errors {
      field
      messages
    }
  }
}

Delete Mutation

mutation DeleteUser($id: ID!) {
  deleteCustomer(id: $id) {
    ok
    customer {
      id
      username
    }
    errors {
      field
      messages
    }
  }
}

Response Structure

Success Response

{
  "data": {
    "createCustomer": {
      "ok": true,
      "customer": {
        "id": "1",
        "username": "john_doe",
        "email": "john@example.com"
      },
      "errors": null
    }
  }
}

Error Response

{
  "data": {
    "createCustomer": {
      "ok": false,
      "customer": null,
      "errors": [
        {
          "field": "username",
          "messages": ["This field is required."]
        },
        {
          "field": "email",
          "messages": ["Enter a valid email address."]
        }
      ]
    }
  }
}

Internal options container

DjangoModelMutation._meta is a NativeObjectTypeOptions instance (django_graphex.core.base.NativeObjectTypeOptions). It is an internal detail — the public API is the Meta class options documented above.

Advanced Usage

File Upload Support

When the request content type is multipart/form-data, an uploaded part is merged into the input payload when its name matches a file field the mutation's own input publishes — under either spelling, the camelCase wire name or the model's own attribute name. A part matching nothing is ignored rather than saved, and a column the input projects away is not writable through a part any more than it is through the JSON body: the projection is the same boundary on both. Matching parts are saved on create and on update:

class ProfileMutation(DjangoModelMutation):
    class Meta:
        model = Profile  # model has an ImageField named "avatar"

# A multipart part named "avatar" lands on Profile.avatar.
mutation UpdateProfile($profileData: ProfileInput!) {
  updateProfile(newProfile: $profileData) {
    ok
    profile {
      id
      avatar  # reads back as the storage path (String)
      bio
    }
    errors {
      field
      messages
    }
  }
}

The GraphQL input field stays String: the file travels in the multipart body, never in the GraphQL variables. That field also accepts a plain storage path string, and rejects any other shape with a structured error.

The request carries a query part with the document, an optional variables part with its variables as JSON, and one part per file named after the model field — the view reads a multipart body straight out of request.POST, so there is no operations / map envelope:

requests.post(
    "https://app.example.com/graphql/",
    files={"avatar": open("avatar.png", "rb")},
    data={
        "query": "mutation UpdateProfile($profileData: ProfileInput!) { … }",
        "variables": json.dumps({"profileData": {"bio": "hi"}}),
    },
    headers={"X-Requested-With": "XMLHttpRequest"},   # <- required
)

A multipart POST must carry X-Requested-With

multipart/form-data is a CORS-simple content type, so a browser posts it cross-site with no preflight and the csrf_exempt endpoint would otherwise execute a forged <form> submit under the victim's session cookie. A multipart request without the header is refused with HTTP 403 before its body is read. See Security → Cross-site POST protection for the REQUIRE_CSRF_HEADER opt-out.

Top-level fields only

The merge is keyed by the bare form-field name, so a file field on a child declared in Meta.nested_fields cannot be addressed — and naming a part after the relation itself overwrites the nested payload and raises an uncaught ValueError (an HTTP 500). For nested uploads, use the base64 input described in Mutations.

Authentication & Authorization

from graphql import GraphQLError

class CustomerMutation(DjangoModelMutation):
    class Meta:
        model = Customer

    @classmethod
    def create(cls, root, info, **kwargs):
        user = info.context.user
        if not user.is_authenticated:
            raise GraphQLError("Authentication required")

        if not user.has_perm('crm.add_customer'):
            raise GraphQLError("Permission denied")

        return super().create(root, info, **kwargs)

Custom Error Handling

from django.core.exceptions import ValidationError
from django_graphex.errors import ErrorType

class CustomerMutation(DjangoModelMutation):
    class Meta:
        model = Customer

    @classmethod
    def create(cls, root, info, **kwargs):
        try:
            return super().create(root, info, **kwargs)
        except ValidationError as e:
            return cls.get_errors([
                ErrorType(field=field, messages=messages)
                for field, messages in e.message_dict.items()
            ])

Error Types

ErrorType

Standard error type used in mutation responses.

from django_graphex.errors import ErrorType

ErrorType is a native ObjectType (graphene-free) with two fields:

Field GraphQL type Description
field String! The name of the field that failed validation
messages [String!]! One or more error messages for that field

Best Practices

Mutation Best Practices

  1. Leverage Pydantic Validation: Use Meta.pydantic_model to add custom validators
  2. Handle Permissions: Always check authentication and authorization
  3. Validate Input: Rely on the auto-generated or custom Pydantic model for robust input handling
  4. Return Meaningful Errors: Provide clear, actionable error messages
  5. Test Thoroughly: Test all CRUD operations and edge cases
  6. Document Operations: Provide clear descriptions for mutations
  7. Handle Files: Use proper file upload handling for media fields

Security Considerations

class CustomerMutation(DjangoModelMutation):
    class Meta:
        model = Customer
        # Don't expose server-owned business fields.
        exclude_fields = ('internal_notes', 'credit_limit')

    @classmethod
    def create(cls, root, info, **kwargs):
        if not info.context.user.has_perm('crm.add_customer'):
            raise GraphQLError("Permission denied")
        return super().create(root, info, **kwargs)

This comprehensive API reference covers the mutation system in django-graphex, providing developers with the tools needed to create robust, validated GraphQL mutations for their Django applications.