graphene-django/graphene_django/forms/mutation.py

193 lines
5.7 KiB
Python
Raw Normal View History

# from django import forms
2017-10-02 21:03:20 +03:00
from collections import OrderedDict
2017-07-18 18:15:25 +03:00
import graphene
2017-10-02 21:03:20 +03:00
from graphene import Field, InputField
from graphene.relay.mutation import ClientIDMutation
from graphene.types.mutation import MutationOptions
2018-07-20 02:51:33 +03:00
# from graphene.types.inputobjecttype import (
# InputObjectTypeOptions,
# InputObjectType,
# )
2017-10-02 21:03:20 +03:00
from graphene.types.utils import yank_fields_from_attrs
from graphene_django.constants import MUTATION_ERRORS_FLAG
2017-07-18 18:15:25 +03:00
from graphene_django.registry import get_global_registry
from ..types import ErrorType
from .converter import convert_form_field
2017-07-18 18:15:25 +03:00
2017-10-02 21:03:20 +03:00
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 = (
2018-07-20 02:51:33 +03:00
name
in exclude_fields # or
2017-10-02 21:03:20 +03:00
# name in already_created_fields
2017-07-18 18:15:25 +03:00
)
2017-10-02 21:03:20 +03:00
if is_not_in_only or is_excluded:
continue
2017-07-18 18:15:25 +03:00
2017-10-02 21:03:20 +03:00
fields[name] = convert_form_field(field)
return fields
2017-07-18 18:15:25 +03:00
2017-11-29 23:12:02 +03:00
class BaseDjangoFormMutation(ClientIDMutation):
2017-10-02 21:03:20 +03:00
class Meta:
abstract = True
2017-07-18 18:15:25 +03:00
@classmethod
2017-10-02 21:03:20 +03:00
def mutate_and_get_payload(cls, root, info, **input):
2017-11-29 23:12:02 +03:00
form = cls.get_form(root, info, **input)
2017-07-18 18:15:25 +03:00
if form.is_valid():
2017-10-02 21:03:20 +03:00
return cls.perform_mutate(form, info)
2017-07-18 18:15:25 +03:00
else:
errors = ErrorType.from_errors(form.errors)
_set_errors_flag_to_context(info)
2017-07-18 18:15:25 +03:00
return cls(errors=errors, **form.data)
2017-07-18 18:15:25 +03:00
2017-11-29 23:12:02 +03:00
@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):
2018-07-20 02:51:33 +03:00
kwargs = {"data": input}
2017-11-29 23:12:02 +03:00
2018-07-20 02:51:33 +03:00
pk = input.pop("id", None)
2017-11-29 23:12:02 +03:00
if pk:
instance = cls._meta.model._default_manager.get(pk=pk)
2018-07-20 02:51:33 +03:00
kwargs["instance"] = instance
2017-11-29 23:12:02 +03:00
return kwargs
2017-07-18 19:20:59 +03:00
2017-11-29 23:12:02 +03:00
class DjangoFormMutationOptions(MutationOptions):
2017-10-02 21:03:20 +03:00
form_class = None
2017-07-18 18:15:25 +03:00
2017-11-29 23:12:02 +03:00
class DjangoFormMutation(BaseDjangoFormMutation):
2017-10-02 21:03:20 +03:00
class Meta:
abstract = True
2017-07-18 18:15:25 +03:00
errors = graphene.List(ErrorType)
2017-10-02 21:03:20 +03:00
@classmethod
2018-07-20 02:51:33 +03:00
def __init_subclass_with_meta__(
cls, form_class=None, only_fields=(), exclude_fields=(), **options
):
2017-07-18 18:15:25 +03:00
2017-10-02 21:03:20 +03:00
if not form_class:
2018-07-20 02:51:33 +03:00
raise Exception("form_class is required for DjangoFormMutation")
2017-07-18 18:15:25 +03:00
2017-10-02 21:03:20 +03:00
form = form_class()
input_fields = fields_for_form(form, only_fields, exclude_fields)
output_fields = fields_for_form(form, only_fields, exclude_fields)
2017-07-18 18:15:25 +03:00
2017-11-29 23:12:02 +03:00
_meta = DjangoFormMutationOptions(cls)
2017-10-02 21:03:20 +03:00
_meta.form_class = form_class
2018-07-20 02:51:33 +03:00
_meta.fields = yank_fields_from_attrs(output_fields, _as=Field)
2017-07-18 18:15:25 +03:00
2018-07-20 02:51:33 +03:00
input_fields = yank_fields_from_attrs(input_fields, _as=InputField)
super().__init_subclass_with_meta__(
2018-07-20 02:51:33 +03:00
_meta=_meta, input_fields=input_fields, **options
2017-07-18 18:15:25 +03:00
)
2017-10-02 21:03:20 +03:00
@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)
2017-07-18 18:15:25 +03:00
2017-11-29 23:12:02 +03:00
class DjangoModelDjangoFormMutationOptions(DjangoFormMutationOptions):
2017-10-02 21:03:20 +03:00
model = None
return_field_name = None
2017-07-18 18:15:25 +03:00
2017-11-29 23:12:02 +03:00
class DjangoModelFormMutation(BaseDjangoFormMutation):
2017-10-02 21:03:20 +03:00
class Meta:
abstract = True
2017-07-18 18:15:25 +03:00
errors = graphene.List(graphene.NonNull(ErrorType), required=True)
2017-07-18 18:15:25 +03:00
2017-10-02 21:03:20 +03:00
@classmethod
2018-07-20 02:51:33 +03:00
def __init_subclass_with_meta__(
cls,
form_class=None,
model=None,
return_field_name=None,
only_fields=(),
exclude_fields=(),
**options,
2018-07-20 02:51:33 +03:00
):
2017-07-18 18:15:25 +03:00
2017-10-02 21:03:20 +03:00
if not form_class:
2018-07-20 02:51:33 +03:00
raise Exception("form_class is required for DjangoModelFormMutation")
2017-07-18 18:15:25 +03:00
2017-10-02 21:03:20 +03:00
if not model:
model = form_class._meta.model
if not model:
2018-07-20 02:51:33 +03:00
raise Exception("model is required for DjangoModelFormMutation")
2017-10-02 21:03:20 +03:00
form = form_class()
input_fields = fields_for_form(form, only_fields, exclude_fields)
if "id" not in exclude_fields:
input_fields["id"] = graphene.ID()
2017-10-02 21:03:20 +03:00
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:]
2017-10-02 21:03:20 +03:00
output_fields = OrderedDict()
output_fields[return_field_name] = graphene.Field(model_type)
2017-11-29 23:12:02 +03:00
_meta = DjangoModelDjangoFormMutationOptions(cls)
2017-10-02 21:03:20 +03:00
_meta.form_class = form_class
_meta.model = model
_meta.return_field_name = return_field_name
2018-07-20 02:51:33 +03:00
_meta.fields = yank_fields_from_attrs(output_fields, _as=Field)
2017-10-02 21:03:20 +03:00
2018-07-20 02:51:33 +03:00
input_fields = yank_fields_from_attrs(input_fields, _as=InputField)
super().__init_subclass_with_meta__(
2018-07-20 02:51:33 +03:00
_meta=_meta, input_fields=input_fields, **options
2017-11-30 00:31:26 +03:00
)
2017-07-18 18:15:25 +03:00
@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)
2017-07-18 18:15:25 +03:00
@classmethod
2017-10-02 21:03:20 +03:00
def perform_mutate(cls, form, info):
2017-07-18 18:15:25 +03:00
obj = form.save()
2017-10-02 21:03:20 +03:00
kwargs = {cls._meta.return_field_name: obj}
2017-10-02 21:15:29 +03:00
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)