Django integration for Graphene enabling GraphQL APIs in Django applications
—
Form-based GraphQL mutations with Django form validation, error handling, and automatic input type generation. Provides seamless integration between Django forms and GraphQL mutations with comprehensive validation and error reporting.
Abstract base class for Django form-based mutations with customizable form handling and validation logic.
class BaseDjangoFormMutation(graphene.relay.ClientIDMutation):
"""
Base class for Django form-based mutations.
Provides foundation for form-based mutations with form instantiation,
validation, and error handling. Designed to be subclassed for specific
form types and validation requirements.
"""
@classmethod
def mutate_and_get_payload(cls, root, info, **input):
"""
Main mutation logic with form processing.
Parameters:
- root: GraphQL root object
- info: GraphQL execution info
- **input: Mutation input arguments
Returns:
Mutation result with form data or errors
"""
@classmethod
def get_form(cls, root, info, **input):
"""
Get form instance for validation.
Parameters:
- root: GraphQL root object
- info: GraphQL execution info
- **input: Form input data
Returns:
django.forms.Form: Form instance
"""
@classmethod
def get_form_kwargs(cls, root, info, **input):
"""
Get form constructor arguments.
Parameters:
- root: GraphQL root object
- info: GraphQL execution info
- **input: Form input data
Returns:
dict: Form constructor keyword arguments
"""
@classmethod
def perform_mutate(cls, form, info):
"""
Perform mutation with validated form.
Parameters:
- form: Validated Django form
- info: GraphQL execution info
Returns:
Mutation result object
"""Concrete form mutation with automatic error handling and input type generation from Django forms.
class DjangoFormMutation(BaseDjangoFormMutation):
"""
Concrete form mutation with error handling.
Automatically generates GraphQL input types from Django forms and
provides standardized error reporting through ErrorType objects.
"""
errors = graphene.List(ErrorType)
class Meta:
"""
Meta options for form mutations.
Attributes:
- form_class: Django form class to use
- only_fields: Fields to include in GraphQL input (list or tuple)
- exclude_fields: Fields to exclude from GraphQL input (list or tuple)
- return_field_name: Name for successful result field
"""
form_class = None
only_fields = None
exclude_fields = None
return_field_name = None
@classmethod
def __init_subclass_with_meta__(cls, form_class=None, only_fields=None,
exclude_fields=None, return_field_name=None,
**options):
"""
Configure form mutation subclass.
Parameters:
- form_class: Django form class
- only_fields: Fields to include
- exclude_fields: Fields to exclude
- return_field_name: Result field name
- **options: Additional mutation options
"""
@classmethod
def perform_mutate(cls, form, info):
"""
Perform mutation with validated form data.
Parameters:
- form: Validated Django form
- info: GraphQL execution info
Returns:
Mutation result with form data
"""Model form-based mutations with CRUD operations and automatic model instance handling.
class DjangoModelFormMutation(BaseDjangoFormMutation):
"""
Model form-based mutations with CRUD operations.
Automatically handles model instance creation and updates through
Django ModelForm validation with comprehensive error handling.
"""
errors = graphene.List(ErrorType)
class Meta:
"""
Meta options for model form mutations.
Inherits from DjangoFormMutation.Meta with additional model-specific
options for instance handling and field mapping.
"""
form_class = None
model = None
only_fields = None
exclude_fields = None
return_field_name = None
@classmethod
def get_form_kwargs(cls, root, info, **input):
"""
Get model form constructor arguments with instance handling.
Parameters:
- root: GraphQL root object
- info: GraphQL execution info
- **input: Form input data
Returns:
dict: Form constructor arguments including model instance
"""
@classmethod
def perform_mutate(cls, form, info):
"""
Perform mutation with model form save.
Parameters:
- form: Validated Django model form
- info: GraphQL execution info
Returns:
Mutation result with saved model instance
"""Global ID form fields for GraphQL integration with proper ID encoding and validation.
class GlobalIDFormField(forms.CharField):
"""Django form field for global IDs with automatic validation."""
def to_python(self, value):
"""
Convert global ID to Python value.
Parameters:
- value: Global ID string
Returns:
Decoded ID value
"""
class GlobalIDMultipleChoiceField(forms.MultipleChoiceField):
"""Multiple choice field for global IDs with batch processing."""
def to_python(self, value):
"""
Convert multiple global IDs to Python values.
Parameters:
- value: List of global ID strings
Returns:
List of decoded ID values
"""from django import forms
from graphene_django.forms.mutation import DjangoFormMutation
import graphene
class ContactForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)
def save(self):
# Custom save logic
data = self.cleaned_data
# Send email, save to database, etc.
return data
class ContactMutation(DjangoFormMutation):
class Meta:
form_class = ContactForm
@classmethod
def perform_mutate(cls, form, info):
result = form.save()
return cls(errors=[], **result)
class Mutation(graphene.ObjectType):
contact_form = ContactMutation.Field()
# GraphQL mutation:
# mutation {
# contactForm(input: {
# name: "John Doe"
# email: "john@example.com"
# message: "Hello world"
# }) {
# errors {
# field
# messages
# }
# name
# email
# }
# }from django.db import models
from django import forms
from graphene_django.forms.mutation import DjangoModelFormMutation
class User(models.Model):
username = models.CharField(max_length=150, unique=True)
email = models.EmailField()
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ['username', 'email', 'first_name', 'last_name']
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError("Email already exists")
return email
class CreateUserMutation(DjangoModelFormMutation):
user = graphene.Field('myapp.schema.UserType')
class Meta:
form_class = UserForm
return_field_name = 'user'
class UpdateUserMutation(DjangoModelFormMutation):
user = graphene.Field('myapp.schema.UserType')
class Meta:
form_class = UserForm
return_field_name = 'user'
@classmethod
def get_form_kwargs(cls, root, info, **input):
kwargs = super().get_form_kwargs(root, info, **input)
# Get existing user for update
user_id = input.get('id')
if user_id:
kwargs['instance'] = User.objects.get(pk=user_id)
return kwargsclass ProfileForm(forms.Form):
avatar = forms.ImageField()
bio = forms.CharField(widget=forms.Textarea, required=False)
def save(self, user):
cleaned_data = self.cleaned_data
user.profile.avatar = cleaned_data['avatar']
user.profile.bio = cleaned_data['bio']
user.profile.save()
return user.profile
class UpdateProfileMutation(DjangoFormMutation):
profile = graphene.Field('myapp.schema.ProfileType')
class Meta:
form_class = ProfileForm
@classmethod
def get_form_kwargs(cls, root, info, **input):
kwargs = super().get_form_kwargs(root, info, **input)
# Add files from request
if hasattr(info.context, 'FILES'):
kwargs['files'] = info.context.FILES
return kwargs
@classmethod
def perform_mutate(cls, form, info):
profile = form.save(info.context.user)
return cls(profile=profile, errors=[])class PasswordChangeForm(forms.Form):
old_password = forms.CharField(widget=forms.PasswordInput)
new_password = forms.CharField(widget=forms.PasswordInput)
confirm_password = forms.CharField(widget=forms.PasswordInput)
def __init__(self, user, *args, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
def clean_old_password(self):
old_password = self.cleaned_data['old_password']
if not self.user.check_password(old_password):
raise forms.ValidationError("Invalid current password")
return old_password
def clean(self):
cleaned_data = super().clean()
new_password = cleaned_data.get('new_password')
confirm_password = cleaned_data.get('confirm_password')
if new_password and confirm_password:
if new_password != confirm_password:
raise forms.ValidationError("Passwords don't match")
return cleaned_data
def save(self):
self.user.set_password(self.cleaned_data['new_password'])
self.user.save()
class ChangePasswordMutation(DjangoFormMutation):
success = graphene.Boolean()
class Meta:
form_class = PasswordChangeForm
exclude_fields = ('old_password',) # Don't expose in GraphQL
@classmethod
def get_form_kwargs(cls, root, info, **input):
kwargs = super().get_form_kwargs(root, info, **input)
kwargs['user'] = info.context.user
return kwargs
@classmethod
def perform_mutate(cls, form, info):
form.save()
return cls(success=True, errors=[])from graphene_django.forms.forms import GlobalIDFormField, GlobalIDMultipleChoiceField
class AssignTaskForm(forms.Form):
task_id = GlobalIDFormField()
assignee_ids = GlobalIDMultipleChoiceField()
due_date = forms.DateTimeField()
def save(self):
task = Task.objects.get(pk=self.cleaned_data['task_id'])
assignees = User.objects.filter(pk__in=self.cleaned_data['assignee_ids'])
task.assignees.set(assignees)
task.due_date = self.cleaned_data['due_date']
task.save()
return task
class AssignTaskMutation(DjangoFormMutation):
task = graphene.Field('myapp.schema.TaskType')
class Meta:
form_class = AssignTaskForm
@classmethod
def perform_mutate(cls, form, info):
task = form.save()
return cls(task=task, errors=[])class AddressForm(forms.Form):
street = forms.CharField(max_length=200)
city = forms.CharField(max_length=100)
postal_code = forms.CharField(max_length=10)
class UserWithAddressForm(forms.Form):
username = forms.CharField(max_length=150)
email = forms.EmailField()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Extract address data
address_data = {}
for key in list(kwargs.get('data', {}).keys()):
if key.startswith('address_'):
address_key = key.replace('address_', '')
address_data[address_key] = kwargs['data'].pop(key)
self.address_form = AddressForm(data=address_data)
def is_valid(self):
return super().is_valid() and self.address_form.is_valid()
def save(self):
user = User.objects.create(
username=self.cleaned_data['username'],
email=self.cleaned_data['email']
)
if self.address_form.is_valid():
Address.objects.create(
user=user,
**self.address_form.cleaned_data
)
return userInstall with Tessl CLI
npx tessl i tessl/pypi-graphene-django