mirror of
				https://github.com/graphql-python/graphene-django.git
				synced 2025-11-04 09:57:53 +03:00 
			
		
		
		
	* Use ruff format to replace black * Adjust ruff config to be compatible with ruff-format https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules * Format * Replace black with ruff format in Makefile
		
			
				
	
	
		
			190 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			190 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# from django import forms
 | 
						|
from collections import OrderedDict
 | 
						|
 | 
						|
import graphene
 | 
						|
from graphene import Field, InputField
 | 
						|
from graphene.relay.mutation import ClientIDMutation
 | 
						|
from graphene.types.mutation import MutationOptions
 | 
						|
 | 
						|
# from graphene.types.inputobjecttype import (
 | 
						|
#     InputObjectTypeOptions,
 | 
						|
#     InputObjectType,
 | 
						|
# )
 | 
						|
from graphene.types.utils import yank_fields_from_attrs
 | 
						|
from graphene_django.constants import MUTATION_ERRORS_FLAG
 | 
						|
from graphene_django.registry import get_global_registry
 | 
						|
 | 
						|
from ..types import ErrorType
 | 
						|
from .converter import convert_form_field
 | 
						|
 | 
						|
 | 
						|
def fields_for_form(form, only_fields, exclude_fields):
 | 
						|
    fields = OrderedDict()
 | 
						|
    for name, field in form.fields.items():
 | 
						|
        is_not_in_only = only_fields and name not in only_fields
 | 
						|
        is_excluded = (
 | 
						|
            name in exclude_fields  # or
 | 
						|
            # name in already_created_fields
 | 
						|
        )
 | 
						|
 | 
						|
        if is_not_in_only or is_excluded:
 | 
						|
            continue
 | 
						|
 | 
						|
        fields[name] = convert_form_field(field)
 | 
						|
    return fields
 | 
						|
 | 
						|
 | 
						|
class BaseDjangoFormMutation(ClientIDMutation):
 | 
						|
    class Meta:
 | 
						|
        abstract = True
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def mutate_and_get_payload(cls, root, info, **input):
 | 
						|
        form = cls.get_form(root, info, **input)
 | 
						|
 | 
						|
        if form.is_valid():
 | 
						|
            return cls.perform_mutate(form, info)
 | 
						|
        else:
 | 
						|
            errors = ErrorType.from_errors(form.errors)
 | 
						|
            _set_errors_flag_to_context(info)
 | 
						|
 | 
						|
            return cls(errors=errors, **form.data)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def get_form(cls, root, info, **input):
 | 
						|
        form_kwargs = cls.get_form_kwargs(root, info, **input)
 | 
						|
        return cls._meta.form_class(**form_kwargs)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def get_form_kwargs(cls, root, info, **input):
 | 
						|
        kwargs = {"data": input}
 | 
						|
 | 
						|
        pk = input.pop("id", None)
 | 
						|
        if pk:
 | 
						|
            instance = cls._meta.model._default_manager.get(pk=pk)
 | 
						|
            kwargs["instance"] = instance
 | 
						|
 | 
						|
        return kwargs
 | 
						|
 | 
						|
 | 
						|
class DjangoFormMutationOptions(MutationOptions):
 | 
						|
    form_class = None
 | 
						|
 | 
						|
 | 
						|
class DjangoFormMutation(BaseDjangoFormMutation):
 | 
						|
    class Meta:
 | 
						|
        abstract = True
 | 
						|
 | 
						|
    errors = graphene.List(ErrorType)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def __init_subclass_with_meta__(
 | 
						|
        cls, form_class=None, only_fields=(), exclude_fields=(), **options
 | 
						|
    ):
 | 
						|
        if not form_class:
 | 
						|
            raise Exception("form_class is required for DjangoFormMutation")
 | 
						|
 | 
						|
        form = form_class()
 | 
						|
        input_fields = fields_for_form(form, only_fields, exclude_fields)
 | 
						|
        output_fields = fields_for_form(form, only_fields, exclude_fields)
 | 
						|
 | 
						|
        _meta = DjangoFormMutationOptions(cls)
 | 
						|
        _meta.form_class = form_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 perform_mutate(cls, form, info):
 | 
						|
        if hasattr(form, "save"):
 | 
						|
            # `save` method won't exist on plain Django forms, but this mutation can
 | 
						|
            # in theory be used with `ModelForm`s as well and we do want to save them.
 | 
						|
            form.save()
 | 
						|
        return cls(errors=[], **form.cleaned_data)
 | 
						|
 | 
						|
 | 
						|
class DjangoModelDjangoFormMutationOptions(DjangoFormMutationOptions):
 | 
						|
    model = None
 | 
						|
    return_field_name = None
 | 
						|
 | 
						|
 | 
						|
class DjangoModelFormMutation(BaseDjangoFormMutation):
 | 
						|
    class Meta:
 | 
						|
        abstract = True
 | 
						|
 | 
						|
    errors = graphene.List(graphene.NonNull(ErrorType), required=True)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def __init_subclass_with_meta__(
 | 
						|
        cls,
 | 
						|
        form_class=None,
 | 
						|
        model=None,
 | 
						|
        return_field_name=None,
 | 
						|
        only_fields=(),
 | 
						|
        exclude_fields=(),
 | 
						|
        **options,
 | 
						|
    ):
 | 
						|
        if not form_class:
 | 
						|
            raise Exception("form_class is required for DjangoModelFormMutation")
 | 
						|
 | 
						|
        if not model:
 | 
						|
            model = form_class._meta.model
 | 
						|
 | 
						|
        if not model:
 | 
						|
            raise Exception("model is required for DjangoModelFormMutation")
 | 
						|
 | 
						|
        form = form_class()
 | 
						|
        input_fields = fields_for_form(form, only_fields, exclude_fields)
 | 
						|
        if "id" not in exclude_fields:
 | 
						|
            input_fields["id"] = graphene.ID()
 | 
						|
 | 
						|
        registry = get_global_registry()
 | 
						|
        model_type = registry.get_type_for_model(model)
 | 
						|
        if not model_type:
 | 
						|
            raise Exception(f"No type registered for model: {model.__name__}")
 | 
						|
 | 
						|
        if not return_field_name:
 | 
						|
            model_name = model.__name__
 | 
						|
            return_field_name = model_name[:1].lower() + model_name[1:]
 | 
						|
 | 
						|
        output_fields = OrderedDict()
 | 
						|
        output_fields[return_field_name] = graphene.Field(model_type)
 | 
						|
 | 
						|
        _meta = DjangoModelDjangoFormMutationOptions(cls)
 | 
						|
        _meta.form_class = form_class
 | 
						|
        _meta.model = model
 | 
						|
        _meta.return_field_name = return_field_name
 | 
						|
        _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 mutate_and_get_payload(cls, root, info, **input):
 | 
						|
        form = cls.get_form(root, info, **input)
 | 
						|
 | 
						|
        if form.is_valid():
 | 
						|
            return cls.perform_mutate(form, info)
 | 
						|
        else:
 | 
						|
            errors = ErrorType.from_errors(form.errors)
 | 
						|
            _set_errors_flag_to_context(info)
 | 
						|
 | 
						|
            return cls(errors=errors)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def perform_mutate(cls, form, info):
 | 
						|
        obj = form.save()
 | 
						|
        kwargs = {cls._meta.return_field_name: obj}
 | 
						|
        return cls(errors=[], **kwargs)
 | 
						|
 | 
						|
 | 
						|
def _set_errors_flag_to_context(info):
 | 
						|
    # This is not ideal but necessary to keep the response errors empty
 | 
						|
    if info and info.context:
 | 
						|
        setattr(info.context, MUTATION_ERRORS_FLAG, True)
 |