mirror of
				https://github.com/graphql-python/graphene-django.git
				synced 2025-10-30 15:37:28 +03:00 
			
		
		
		
	* adding optional_fields to enforce fields to be optional * adding support for all * adding unit tests * Update graphene_django/rest_framework/mutation.py Co-authored-by: Kien Dang <kiend@pm.me> * linting * linting * add missing import --------- Co-authored-by: Kien Dang <kiend@pm.me>
		
			
				
	
	
		
			189 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			189 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from collections import OrderedDict
 | |
| from enum import Enum
 | |
| 
 | |
| from django.shortcuts import get_object_or_404
 | |
| from rest_framework import serializers
 | |
| 
 | |
| import graphene
 | |
| from graphene.relay.mutation import ClientIDMutation
 | |
| from graphene.types import Field, InputField
 | |
| from graphene.types.mutation import MutationOptions
 | |
| from graphene.types.objecttype import yank_fields_from_attrs
 | |
| 
 | |
| from ..types import ErrorType
 | |
| from .serializer_converter import convert_serializer_field
 | |
| 
 | |
| 
 | |
| class SerializerMutationOptions(MutationOptions):
 | |
|     lookup_field = None
 | |
|     model_class = None
 | |
|     model_operations = ["create", "update"]
 | |
|     serializer_class = None
 | |
|     optional_fields = ()
 | |
| 
 | |
| 
 | |
| def fields_for_serializer(
 | |
|     serializer,
 | |
|     only_fields,
 | |
|     exclude_fields,
 | |
|     is_input=False,
 | |
|     convert_choices_to_enum=True,
 | |
|     lookup_field=None,
 | |
|     optional_fields=(),
 | |
| ):
 | |
|     fields = OrderedDict()
 | |
|     for name, field in serializer.fields.items():
 | |
|         is_not_in_only = only_fields and name not in only_fields
 | |
|         is_excluded = any(
 | |
|             [
 | |
|                 name in exclude_fields,
 | |
|                 field.write_only
 | |
|                 and not is_input,  # don't show write_only fields in Query
 | |
|                 field.read_only
 | |
|                 and is_input
 | |
|                 and lookup_field != name,  # don't show read_only fields in Input
 | |
|                 isinstance(
 | |
|                     field, serializers.HiddenField
 | |
|                 ),  # don't show hidden fields in Input
 | |
|             ]
 | |
|         )
 | |
| 
 | |
|         if is_not_in_only or is_excluded:
 | |
|             continue
 | |
|         is_optional = name in optional_fields or "__all__" in optional_fields
 | |
| 
 | |
|         fields[name] = convert_serializer_field(
 | |
|             field,
 | |
|             is_input=is_input,
 | |
|             convert_choices_to_enum=convert_choices_to_enum,
 | |
|             force_optional=is_optional,
 | |
|         )
 | |
|     return fields
 | |
| 
 | |
| 
 | |
| class SerializerMutation(ClientIDMutation):
 | |
|     class Meta:
 | |
|         abstract = True
 | |
| 
 | |
|     errors = graphene.List(
 | |
|         ErrorType, description="May contain more than one error for same field."
 | |
|     )
 | |
| 
 | |
|     @classmethod
 | |
|     def __init_subclass_with_meta__(
 | |
|         cls,
 | |
|         lookup_field=None,
 | |
|         serializer_class=None,
 | |
|         model_class=None,
 | |
|         model_operations=("create", "update"),
 | |
|         only_fields=(),
 | |
|         exclude_fields=(),
 | |
|         convert_choices_to_enum=True,
 | |
|         _meta=None,
 | |
|         optional_fields=(),
 | |
|         **options
 | |
|     ):
 | |
|         if not serializer_class:
 | |
|             raise Exception("serializer_class is required for the SerializerMutation")
 | |
| 
 | |
|         if "update" not in model_operations and "create" not in model_operations:
 | |
|             raise Exception('model_operations must contain "create" and/or "update"')
 | |
| 
 | |
|         serializer = serializer_class()
 | |
|         if model_class is None:
 | |
|             serializer_meta = getattr(serializer_class, "Meta", None)
 | |
|             if serializer_meta:
 | |
|                 model_class = getattr(serializer_meta, "model", None)
 | |
| 
 | |
|         if lookup_field is None and model_class:
 | |
|             lookup_field = model_class._meta.pk.name
 | |
| 
 | |
|         input_fields = fields_for_serializer(
 | |
|             serializer,
 | |
|             only_fields,
 | |
|             exclude_fields,
 | |
|             is_input=True,
 | |
|             convert_choices_to_enum=convert_choices_to_enum,
 | |
|             lookup_field=lookup_field,
 | |
|             optional_fields=optional_fields,
 | |
|         )
 | |
|         output_fields = fields_for_serializer(
 | |
|             serializer,
 | |
|             only_fields,
 | |
|             exclude_fields,
 | |
|             is_input=False,
 | |
|             convert_choices_to_enum=convert_choices_to_enum,
 | |
|             lookup_field=lookup_field,
 | |
|         )
 | |
| 
 | |
|         if not _meta:
 | |
|             _meta = SerializerMutationOptions(cls)
 | |
|         _meta.lookup_field = lookup_field
 | |
|         _meta.model_operations = model_operations
 | |
|         _meta.serializer_class = serializer_class
 | |
|         _meta.model_class = model_class
 | |
|         _meta.fields = yank_fields_from_attrs(output_fields, _as=Field)
 | |
| 
 | |
|         input_fields = yank_fields_from_attrs(input_fields, _as=InputField)
 | |
|         super().__init_subclass_with_meta__(
 | |
|             _meta=_meta, input_fields=input_fields, **options
 | |
|         )
 | |
| 
 | |
|     @classmethod
 | |
|     def get_serializer_kwargs(cls, root, info, **input):
 | |
|         lookup_field = cls._meta.lookup_field
 | |
|         model_class = cls._meta.model_class
 | |
|         if model_class:
 | |
|             for input_dict_key, maybe_enum in input.items():
 | |
|                 if isinstance(maybe_enum, Enum):
 | |
|                     input[input_dict_key] = maybe_enum.value
 | |
|             if "update" in cls._meta.model_operations and lookup_field in input:
 | |
|                 instance = get_object_or_404(
 | |
|                     model_class, **{lookup_field: input[lookup_field]}
 | |
|                 )
 | |
|                 partial = True
 | |
|             elif "create" in cls._meta.model_operations:
 | |
|                 instance = None
 | |
|                 partial = False
 | |
|             else:
 | |
|                 raise Exception(
 | |
|                     'Invalid update operation. Input parameter "{}" required.'.format(
 | |
|                         lookup_field
 | |
|                     )
 | |
|                 )
 | |
| 
 | |
|             return {
 | |
|                 "instance": instance,
 | |
|                 "data": input,
 | |
|                 "context": {"request": info.context},
 | |
|                 "partial": partial,
 | |
|             }
 | |
| 
 | |
|         return {"data": input, "context": {"request": info.context}}
 | |
| 
 | |
|     @classmethod
 | |
|     def mutate_and_get_payload(cls, root, info, **input):
 | |
|         kwargs = cls.get_serializer_kwargs(root, info, **input)
 | |
|         serializer = cls._meta.serializer_class(**kwargs)
 | |
| 
 | |
|         if serializer.is_valid():
 | |
|             return cls.perform_mutate(serializer, info)
 | |
|         else:
 | |
|             errors = ErrorType.from_errors(serializer.errors)
 | |
| 
 | |
|             return cls(errors=errors)
 | |
| 
 | |
|     @classmethod
 | |
|     def perform_mutate(cls, serializer, info):
 | |
|         obj = serializer.save()
 | |
| 
 | |
|         kwargs = {}
 | |
|         for f, field in serializer.fields.items():
 | |
|             if not field.write_only:
 | |
|                 if isinstance(field, serializers.SerializerMethodField):
 | |
|                     kwargs[f] = field.to_representation(obj)
 | |
|                 else:
 | |
|                     kwargs[f] = field.get_attribute(obj)
 | |
| 
 | |
|         return cls(errors=None, **kwargs)
 |