Django integration for Graphene enabling GraphQL APIs in Django applications
—
Django REST Framework serializer integration for GraphQL mutations with comprehensive validation, model operations, and seamless DRF feature support. Enables reuse of existing DRF serializers in GraphQL mutations with full validation and error handling.
Mutation class using Django REST Framework serializers with automatic input type generation and comprehensive model operations.
class SerializerMutation(graphene.relay.ClientIDMutation):
"""
Mutation class using Django REST Framework serializers.
Automatically generates GraphQL input types from DRF serializers and
provides standardized error reporting with full DRF validation support.
"""
errors = graphene.List(ErrorType)
class Meta:
"""
Meta options for serializer mutations.
Attributes:
- serializer_class: DRF serializer class to use
- model_operations: Operations to support ('create', 'update', or both)
- lookup_field: Field for object lookup in updates (default: 'id')
- return_field_name: Name for successful result field
"""
serializer_class = None
model_operations = ['create', 'update']
lookup_field = 'id'
return_field_name = None
@classmethod
def __init_subclass_with_meta__(cls, serializer_class=None, model_operations=None,
lookup_field=None, return_field_name=None, **options):
"""
Configure serializer mutation subclass.
Parameters:
- serializer_class: DRF serializer class
- model_operations: Supported operations list
- lookup_field: Field for instance lookup
- return_field_name: Result field name
- **options: Additional mutation options
"""
@classmethod
def mutate_and_get_payload(cls, root, info, **input):
"""
Main mutation logic with serializer processing.
Parameters:
- root: GraphQL root object
- info: GraphQL execution info
- **input: Mutation input arguments
Returns:
Mutation result with serializer data or errors
"""
@classmethod
def get_serializer_kwargs(cls, root, info, **input):
"""
Get serializer constructor arguments.
Parameters:
- root: GraphQL root object
- info: GraphQL execution info
- **input: Serializer input data
Returns:
dict: Serializer constructor keyword arguments
"""
@classmethod
def get_serializer(cls, root, info, **input):
"""
Get serializer instance for validation.
Parameters:
- root: GraphQL root object
- info: GraphQL execution info
- **input: Serializer input data
Returns:
rest_framework.serializers.Serializer: Serializer instance
"""
@classmethod
def perform_mutate(cls, serializer, info):
"""
Perform mutation with validated serializer.
Parameters:
- serializer: Validated DRF serializer
- info: GraphQL execution info
Returns:
Mutation result object
"""Dictionary type for key-value pairs in GraphQL schemas with flexible data representation.
class DictType(graphene.UnmountedType):
"""
Dictionary type for key-value pairs.
Represents dictionary/mapping data structures in GraphQL with
flexible key-value pair support for dynamic data.
"""
key = graphene.String()
value = graphene.String()
@classmethod
def serialize(cls, value):
"""
Serialize dictionary to GraphQL format.
Parameters:
- value: Dictionary to serialize
Returns:
List of key-value pairs
"""from rest_framework import serializers
from graphene_django.rest_framework.mutation import SerializerMutation
import graphene
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username', 'email', 'first_name', 'last_name']
def validate_email(self, value):
if User.objects.filter(email=value).exists():
raise serializers.ValidationError("Email already exists")
return value
class CreateUserMutation(SerializerMutation):
user = graphene.Field('myapp.schema.UserType')
class Meta:
serializer_class = UserSerializer
model_operations = ['create']
return_field_name = 'user'
class Mutation(graphene.ObjectType):
create_user = CreateUserMutation.Field()
# GraphQL mutation:
# mutation {
# createUser(input: {
# username: "johndoe"
# email: "john@example.com"
# firstName: "John"
# lastName: "Doe"
# }) {
# user {
# id
# username
# email
# }
# errors {
# field
# messages
# }
# }
# }class UpdateUserMutation(SerializerMutation):
user = graphene.Field('myapp.schema.UserType')
class Meta:
serializer_class = UserSerializer
model_operations = ['update']
lookup_field = 'id'
return_field_name = 'user'
@classmethod
def get_serializer_kwargs(cls, root, info, **input):
kwargs = super().get_serializer_kwargs(root, info, **input)
# Get instance for update
instance_id = input.get('id')
if instance_id:
try:
kwargs['instance'] = User.objects.get(pk=instance_id)
except User.DoesNotExist:
raise Exception("User not found")
return kwargs
# GraphQL mutation:
# mutation {
# updateUser(input: {
# id: "1"
# email: "newemail@example.com"
# firstName: "Jane"
# }) {
# user {
# id
# email
# firstName
# }
# errors {
# field
# messages
# }
# }
# }class AddressSerializer(serializers.ModelSerializer):
class Meta:
model = Address
fields = ['street', 'city', 'postal_code', 'country']
class UserWithAddressSerializer(serializers.ModelSerializer):
address = AddressSerializer()
class Meta:
model = User
fields = ['username', 'email', 'address']
def create(self, validated_data):
address_data = validated_data.pop('address')
user = User.objects.create(**validated_data)
Address.objects.create(user=user, **address_data)
return user
def update(self, instance, validated_data):
address_data = validated_data.pop('address', None)
# Update user fields
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
# Update address
if address_data:
address, created = Address.objects.get_or_create(user=instance)
for attr, value in address_data.items():
setattr(address, attr, value)
address.save()
return instance
class CreateUserWithAddressMutation(SerializerMutation):
user = graphene.Field('myapp.schema.UserType')
class Meta:
serializer_class = UserWithAddressSerializer
model_operations = ['create']
return_field_name = 'user'class UserProfileSerializer(serializers.ModelSerializer):
full_name = serializers.SerializerMethodField()
posts_count = serializers.SerializerMethodField()
avatar_url = serializers.SerializerMethodField()
class Meta:
model = User
fields = ['username', 'email', 'full_name', 'posts_count', 'avatar_url']
read_only_fields = ['full_name', 'posts_count', 'avatar_url']
def get_full_name(self, obj):
return f"{obj.first_name} {obj.last_name}".strip()
def get_posts_count(self, obj):
return obj.post_set.count()
def get_avatar_url(self, obj):
if obj.profile.avatar:
return obj.profile.avatar.url
return None
def update(self, instance, validated_data):
# Custom update logic
instance.username = validated_data.get('username', instance.username)
instance.email = validated_data.get('email', instance.email)
instance.save()
# Trigger profile update
instance.profile.last_updated = timezone.now()
instance.profile.save()
return instance
class UpdateUserProfileMutation(SerializerMutation):
user = graphene.Field('myapp.schema.UserType')
class Meta:
serializer_class = UserProfileSerializer
model_operations = ['update']
return_field_name = 'user'class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['title', 'content', 'category', 'tags', 'published']
def validate_title(self, value):
if len(value) < 5:
raise serializers.ValidationError("Title must be at least 5 characters")
return value
def validate(self, data):
if data.get('published') and not data.get('content'):
raise serializers.ValidationError({
'content': 'Content is required for published posts'
})
return data
def create(self, validated_data):
validated_data['author'] = self.context['request'].user
return super().create(validated_data)
class CreatePostMutation(SerializerMutation):
post = graphene.Field('myapp.schema.PostType')
class Meta:
serializer_class = PostSerializer
model_operations = ['create']
return_field_name = 'post'
@classmethod
def get_serializer_kwargs(cls, root, info, **input):
kwargs = super().get_serializer_kwargs(root, info, **input)
kwargs['context'] = {'request': info.context}
return kwargsclass DocumentSerializer(serializers.ModelSerializer):
file = serializers.FileField()
class Meta:
model = Document
fields = ['title', 'description', 'file', 'category']
def validate_file(self, value):
# Validate file size (5MB limit)
if value.size > 5 * 1024 * 1024:
raise serializers.ValidationError("File size cannot exceed 5MB")
# Validate file type
allowed_types = ['application/pdf', 'image/jpeg', 'image/png']
if value.content_type not in allowed_types:
raise serializers.ValidationError("File type not allowed")
return value
def create(self, validated_data):
validated_data['uploaded_by'] = self.context['request'].user
return super().create(validated_data)
class UploadDocumentMutation(SerializerMutation):
document = graphene.Field('myapp.schema.DocumentType')
class Meta:
serializer_class = DocumentSerializer
model_operations = ['create']
return_field_name = 'document'
@classmethod
def get_serializer_kwargs(cls, root, info, **input):
kwargs = super().get_serializer_kwargs(root, info, **input)
kwargs['context'] = {'request': info.context}
# Handle file upload
if hasattr(info.context, 'FILES'):
kwargs['data'] = {**input, **info.context.FILES}
return kwargsclass ProjectSerializer(serializers.ModelSerializer):
team_members = serializers.PrimaryKeyRelatedField(
many=True,
queryset=User.objects.all()
)
class Meta:
model = Project
fields = ['name', 'description', 'team_members', 'deadline']
def create(self, validated_data):
team_members = validated_data.pop('team_members', [])
project = Project.objects.create(**validated_data)
project.team_members.set(team_members)
return project
def update(self, instance, validated_data):
team_members = validated_data.pop('team_members', None)
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
if team_members is not None:
instance.team_members.set(team_members)
return instance
class CreateProjectMutation(SerializerMutation):
project = graphene.Field('myapp.schema.ProjectType')
class Meta:
serializer_class = ProjectSerializer
model_operations = ['create', 'update']
return_field_name = 'project'class CustomSerializerMutation(SerializerMutation):
@classmethod
def mutate_and_get_payload(cls, root, info, **input):
try:
return super().mutate_and_get_payload(root, info, **input)
except serializers.ValidationError as e:
# Custom error formatting
errors = []
if hasattr(e, 'detail'):
for field, messages in e.detail.items():
if isinstance(messages, list):
for message in messages:
errors.append(ErrorType(field=field, messages=[str(message)]))
else:
errors.append(ErrorType(field=field, messages=[str(messages)]))
return cls(errors=errors)
except Exception as e:
# Log unexpected errors
logger.error(f"Mutation error: {e}")
return cls(errors=[ErrorType(field='__all__', messages=[str(e)])])Install with Tessl CLI
npx tessl i tessl/pypi-graphene-django