mirror of
				https://github.com/graphql-python/graphene-django.git
				synced 2025-10-30 23:47:30 +03:00 
			
		
		
		
	Merge pull request #450 from graphql-python/form_mutations
Form mutations
This commit is contained in:
		
						commit
						4e7b269b76
					
				
							
								
								
									
										68
									
								
								docs/form-mutations.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								docs/form-mutations.rst
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,68 @@ | ||||||
|  | Integration with Django forms | ||||||
|  | ============================= | ||||||
|  | 
 | ||||||
|  | Graphene-Django comes with mutation classes that will convert the fields on Django forms into inputs on a mutation. | ||||||
|  | *Note: the API is experimental and will likely change in the future.* | ||||||
|  | 
 | ||||||
|  | FormMutation | ||||||
|  | ------------ | ||||||
|  | 
 | ||||||
|  | .. code:: python | ||||||
|  | 
 | ||||||
|  |     class MyForm(forms.Form): | ||||||
|  |         name = forms.CharField() | ||||||
|  | 
 | ||||||
|  |     class MyMutation(FormMutation): | ||||||
|  |         class Meta: | ||||||
|  |             form_class = MyForm | ||||||
|  | 
 | ||||||
|  | ``MyMutation`` will automatically receive an ``input`` argument. This argument should be a ``dict`` where the key is ``name`` and the value is a string. | ||||||
|  | 
 | ||||||
|  | ModelFormMutation | ||||||
|  | ----------------- | ||||||
|  | 
 | ||||||
|  | ``ModelFormMutation`` will pull the fields from a ``ModelForm``. | ||||||
|  | 
 | ||||||
|  | .. code:: python | ||||||
|  | 
 | ||||||
|  |     class Pet(models.Model): | ||||||
|  |         name = models.CharField() | ||||||
|  | 
 | ||||||
|  |     class PetForm(forms.ModelForm): | ||||||
|  |         class Meta: | ||||||
|  |             model = Pet | ||||||
|  |             fields = ('name',) | ||||||
|  | 
 | ||||||
|  |     # This will get returned when the mutation completes successfully | ||||||
|  |     class PetType(DjangoObjectType): | ||||||
|  |         class Meta: | ||||||
|  |             model = Pet | ||||||
|  | 
 | ||||||
|  |     class PetMutation(DjangoModelFormMutation): | ||||||
|  |         class Meta: | ||||||
|  |             form_class = PetForm | ||||||
|  | 
 | ||||||
|  | ``PetMutation`` will grab the fields from ``PetForm`` and turn them into inputs. If the form is valid then the mutation | ||||||
|  | will lookup the ``DjangoObjectType`` for the ``Pet`` model and return that under the key ``pet``. Otherwise it will | ||||||
|  | return a list of errors. | ||||||
|  | 
 | ||||||
|  | You can change the input name (default is ``input``) and the return field name (default is the model name lowercase). | ||||||
|  | 
 | ||||||
|  | .. code:: python | ||||||
|  | 
 | ||||||
|  |     class PetMutation(DjangoModelFormMutation): | ||||||
|  |         class Meta: | ||||||
|  |             form_class = PetForm | ||||||
|  |             input_field_name = 'data' | ||||||
|  |             return_field_name = 'my_pet' | ||||||
|  | 
 | ||||||
|  | Form validation | ||||||
|  | --------------- | ||||||
|  | 
 | ||||||
|  | Form mutations will call ``is_valid()`` on your forms. | ||||||
|  | 
 | ||||||
|  | If the form is valid then ``form_valid(form, info)`` is called on the mutation. Override this method to change how | ||||||
|  | the form is saved or to return a different Graphene object type. | ||||||
|  | 
 | ||||||
|  | If the form is *not* valid then a list of errors will be returned. These errors have two fields: ``field``, a string | ||||||
|  | containing the name of the invalid form field, and ``messages``, a list of strings with the validation messages. | ||||||
|  | @ -12,4 +12,5 @@ Contents: | ||||||
|    authorization |    authorization | ||||||
|    debug |    debug | ||||||
|    rest-framework |    rest-framework | ||||||
|  |    form-mutations | ||||||
|    introspection |    introspection | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ def get_filtering_args_from_filterset(filterset_class, type): | ||||||
|         a Graphene Field. These arguments will be available to |         a Graphene Field. These arguments will be available to | ||||||
|         filter against in the GraphQL |         filter against in the GraphQL | ||||||
|     """ |     """ | ||||||
|     from ..form_converter import convert_form_field |     from ..forms.converter import convert_form_field | ||||||
| 
 | 
 | ||||||
|     args = {} |     args = {} | ||||||
|     for name, filter_field in six.iteritems(filterset_class.base_filters): |     for name, filter_field in six.iteritems(filterset_class.base_filters): | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								graphene_django/forms/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								graphene_django/forms/__init__.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | from .forms import GlobalIDFormField, GlobalIDMultipleChoiceField  # noqa | ||||||
|  | @ -1,25 +1,25 @@ | ||||||
| from django import forms | from django import forms | ||||||
| from django.forms.fields import BaseTemporalField | from django.core.exceptions import ImproperlyConfigured | ||||||
| 
 | 
 | ||||||
| from graphene import ID, Boolean, Float, Int, List, String, UUID | from graphene import ID, Boolean, Float, Int, List, String, UUID, Date, DateTime, Time | ||||||
| from graphene.types.datetime import Date, DateTime, Time |  | ||||||
| 
 | 
 | ||||||
| from .forms import GlobalIDFormField, GlobalIDMultipleChoiceField | from .forms import GlobalIDFormField, GlobalIDMultipleChoiceField | ||||||
| from .utils import import_single_dispatch | from ..utils import import_single_dispatch | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| singledispatch = import_single_dispatch() | singledispatch = import_single_dispatch() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @singledispatch | @singledispatch | ||||||
| def convert_form_field(field): | def convert_form_field(field): | ||||||
|     raise Exception( |     raise ImproperlyConfigured( | ||||||
|         "Don't know how to convert the Django form field %s (%s) " |         "Don't know how to convert the Django form field %s (%s) " | ||||||
|         "to Graphene type" % |         "to Graphene type" % | ||||||
|         (field, field.__class__) |         (field, field.__class__) | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @convert_form_field.register(BaseTemporalField) | @convert_form_field.register(forms.fields.BaseTemporalField) | ||||||
| @convert_form_field.register(forms.CharField) | @convert_form_field.register(forms.CharField) | ||||||
| @convert_form_field.register(forms.EmailField) | @convert_form_field.register(forms.EmailField) | ||||||
| @convert_form_field.register(forms.SlugField) | @convert_form_field.register(forms.SlugField) | ||||||
							
								
								
									
										193
									
								
								graphene_django/forms/mutation.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								graphene_django/forms/mutation.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,193 @@ | ||||||
|  | # 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.registry import get_global_registry | ||||||
|  | 
 | ||||||
|  | from .converter import convert_form_field | ||||||
|  | from .types import ErrorType | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 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(field=key, messages=value) | ||||||
|  |                 for key, value in form.errors.items() | ||||||
|  |             ] | ||||||
|  | 
 | ||||||
|  |             return cls(errors=errors) | ||||||
|  | 
 | ||||||
|  |     @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 DjangoFormInputObjectTypeOptions(InputObjectTypeOptions): | ||||||
|  | #     form_class = None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # class DjangoFormInputObjectType(InputObjectType): | ||||||
|  | #     class Meta: | ||||||
|  | #         abstract = True | ||||||
|  | 
 | ||||||
|  | #     @classmethod | ||||||
|  | #     def __init_subclass_with_meta__(cls, form_class=None, | ||||||
|  | #                                     only_fields=(), exclude_fields=(), _meta=None, **options): | ||||||
|  | #         if not _meta: | ||||||
|  | #             _meta = DjangoFormInputObjectTypeOptions(cls) | ||||||
|  | #         assert isinstance(form_class, forms.Form), ( | ||||||
|  | #             'form_class must be an instance of django.forms.Form' | ||||||
|  | #         ) | ||||||
|  | #         _meta.form_class = form_class | ||||||
|  | #         form = form_class() | ||||||
|  | #         fields = fields_for_form(form, only_fields, exclude_fields) | ||||||
|  | #         super(DjangoFormInputObjectType, cls).__init_subclass_with_meta__(_meta=_meta, fields=fields, **options) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 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(DjangoFormMutation, cls).__init_subclass_with_meta__(_meta=_meta, input_fields=input_fields, **options) | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def perform_mutate(cls, form, info): | ||||||
|  |         form.save() | ||||||
|  |         return cls(errors=[]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class DjangoModelDjangoFormMutationOptions(DjangoFormMutationOptions): | ||||||
|  |     model = None | ||||||
|  |     return_field_name = None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class DjangoModelFormMutation(BaseDjangoFormMutation): | ||||||
|  |     class Meta: | ||||||
|  |         abstract = True | ||||||
|  | 
 | ||||||
|  |     errors = graphene.List(ErrorType) | ||||||
|  | 
 | ||||||
|  |     @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) | ||||||
|  |         input_fields['id'] = graphene.ID() | ||||||
|  | 
 | ||||||
|  |         registry = get_global_registry() | ||||||
|  |         model_type = registry.get_type_for_model(model) | ||||||
|  |         return_field_name = return_field_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(DjangoModelFormMutation, cls).__init_subclass_with_meta__( | ||||||
|  |             _meta=_meta, | ||||||
|  |             input_fields=input_fields, | ||||||
|  |             **options | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def perform_mutate(cls, form, info): | ||||||
|  |         obj = form.save() | ||||||
|  |         kwargs = {cls._meta.return_field_name: obj} | ||||||
|  |         return cls(errors=[], **kwargs) | ||||||
							
								
								
									
										0
									
								
								graphene_django/forms/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								graphene_django/forms/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -2,10 +2,9 @@ from django import forms | ||||||
| from py.test import raises | from py.test import raises | ||||||
| 
 | 
 | ||||||
| import graphene | import graphene | ||||||
| from graphene import ID, List, NonNull | from graphene import String, Int, Boolean, Float, ID, UUID, List, NonNull, DateTime, Date, Time | ||||||
| 
 | 
 | ||||||
| from ..form_converter import convert_form_field | from ..converter import convert_form_field | ||||||
| from .models import Reporter |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def assert_conversion(django_field, graphene_field, *args): | def assert_conversion(django_field, graphene_field, *args): | ||||||
|  | @ -24,80 +23,80 @@ def test_should_unknown_django_field_raise_exception(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_date_convert_date(): | def test_should_date_convert_date(): | ||||||
|     assert_conversion(forms.DateField, graphene.types.datetime.Date) |     assert_conversion(forms.DateField, Date) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_time_convert_time(): | def test_should_time_convert_time(): | ||||||
|     assert_conversion(forms.TimeField, graphene.types.datetime.Time) |     assert_conversion(forms.TimeField, Time) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_date_time_convert_date_time(): | def test_should_date_time_convert_date_time(): | ||||||
|     assert_conversion(forms.DateTimeField, graphene.types.datetime.DateTime) |     assert_conversion(forms.DateTimeField, DateTime) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_char_convert_string(): | def test_should_char_convert_string(): | ||||||
|     assert_conversion(forms.CharField, graphene.String) |     assert_conversion(forms.CharField, String) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_email_convert_string(): | def test_should_email_convert_string(): | ||||||
|     assert_conversion(forms.EmailField, graphene.String) |     assert_conversion(forms.EmailField, String) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_slug_convert_string(): | def test_should_slug_convert_string(): | ||||||
|     assert_conversion(forms.SlugField, graphene.String) |     assert_conversion(forms.SlugField, String) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_url_convert_string(): | def test_should_url_convert_string(): | ||||||
|     assert_conversion(forms.URLField, graphene.String) |     assert_conversion(forms.URLField, String) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_choice_convert_string(): | def test_should_choice_convert_string(): | ||||||
|     assert_conversion(forms.ChoiceField, graphene.String) |     assert_conversion(forms.ChoiceField, String) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_base_field_convert_string(): | def test_should_base_field_convert_string(): | ||||||
|     assert_conversion(forms.Field, graphene.String) |     assert_conversion(forms.Field, String) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_regex_convert_string(): | def test_should_regex_convert_string(): | ||||||
|     assert_conversion(forms.RegexField, graphene.String, '[0-9]+') |     assert_conversion(forms.RegexField, String, '[0-9]+') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_uuid_convert_string(): | def test_should_uuid_convert_string(): | ||||||
|     if hasattr(forms, 'UUIDField'): |     if hasattr(forms, 'UUIDField'): | ||||||
|         assert_conversion(forms.UUIDField, graphene.UUID) |         assert_conversion(forms.UUIDField, UUID) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_integer_convert_int(): | def test_should_integer_convert_int(): | ||||||
|     assert_conversion(forms.IntegerField, graphene.Int) |     assert_conversion(forms.IntegerField, Int) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_boolean_convert_boolean(): | def test_should_boolean_convert_boolean(): | ||||||
|     field = assert_conversion(forms.BooleanField, graphene.Boolean) |     field = assert_conversion(forms.BooleanField, Boolean) | ||||||
|     assert isinstance(field.type, NonNull) |     assert isinstance(field.type, NonNull) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_nullboolean_convert_boolean(): | def test_should_nullboolean_convert_boolean(): | ||||||
|     field = assert_conversion(forms.NullBooleanField, graphene.Boolean) |     field = assert_conversion(forms.NullBooleanField, Boolean) | ||||||
|     assert not isinstance(field.type, NonNull) |     assert not isinstance(field.type, NonNull) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_float_convert_float(): | def test_should_float_convert_float(): | ||||||
|     assert_conversion(forms.FloatField, graphene.Float) |     assert_conversion(forms.FloatField, Float) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_decimal_convert_float(): | def test_should_decimal_convert_float(): | ||||||
|     assert_conversion(forms.DecimalField, graphene.Float) |     assert_conversion(forms.DecimalField, Float) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_multiple_choice_convert_connectionorlist(): | def test_should_multiple_choice_convert_connectionorlist(): | ||||||
|     field = forms.ModelMultipleChoiceField(Reporter.objects.all()) |     field = forms.ModelMultipleChoiceField(queryset=None) | ||||||
|     graphene_type = convert_form_field(field) |     graphene_type = convert_form_field(field) | ||||||
|     assert isinstance(graphene_type, List) |     assert isinstance(graphene_type, List) | ||||||
|     assert graphene_type.of_type == ID |     assert graphene_type.of_type == ID | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_manytoone_convert_connectionorlist(): | def test_should_manytoone_convert_connectionorlist(): | ||||||
|     field = forms.ModelChoiceField(Reporter.objects.all()) |     field = forms.ModelChoiceField(queryset=None) | ||||||
|     graphene_type = convert_form_field(field) |     graphene_type = convert_form_field(field) | ||||||
|     assert isinstance(graphene_type, graphene.ID) |     assert isinstance(graphene_type, ID) | ||||||
							
								
								
									
										113
									
								
								graphene_django/forms/tests/test_mutation.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								graphene_django/forms/tests/test_mutation.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,113 @@ | ||||||
|  | from django import forms | ||||||
|  | from django.test import TestCase | ||||||
|  | from py.test import raises | ||||||
|  | 
 | ||||||
|  | from graphene_django.tests.models import Pet, Film, FilmDetails | ||||||
|  | from ..mutation import DjangoFormMutation, DjangoModelFormMutation | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class MyForm(forms.Form): | ||||||
|  |     text = forms.CharField() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PetForm(forms.ModelForm): | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         model = Pet | ||||||
|  |         fields = ('name',) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_needs_form_class(): | ||||||
|  |     with raises(Exception) as exc: | ||||||
|  |         class MyMutation(DjangoFormMutation): | ||||||
|  |             pass | ||||||
|  | 
 | ||||||
|  |     assert exc.value.args[0] == 'form_class is required for DjangoFormMutation' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_has_output_fields(): | ||||||
|  |     class MyMutation(DjangoFormMutation): | ||||||
|  |         class Meta: | ||||||
|  |             form_class = MyForm | ||||||
|  | 
 | ||||||
|  |     assert 'errors' in MyMutation._meta.fields | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_has_input_fields(): | ||||||
|  |     class MyMutation(DjangoFormMutation): | ||||||
|  |         class Meta: | ||||||
|  |             form_class = MyForm | ||||||
|  | 
 | ||||||
|  |     assert 'text' in MyMutation.Input._meta.fields | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ModelFormMutationTests(TestCase): | ||||||
|  | 
 | ||||||
|  |     def test_default_meta_fields(self): | ||||||
|  |         class PetMutation(DjangoModelFormMutation): | ||||||
|  |             class Meta: | ||||||
|  |                 form_class = PetForm | ||||||
|  | 
 | ||||||
|  |         self.assertEqual(PetMutation._meta.model, Pet) | ||||||
|  |         self.assertEqual(PetMutation._meta.return_field_name, 'pet') | ||||||
|  |         self.assertIn('pet', PetMutation._meta.fields) | ||||||
|  | 
 | ||||||
|  |     def test_return_field_name_is_camelcased(self): | ||||||
|  |         class PetMutation(DjangoModelFormMutation): | ||||||
|  |             class Meta: | ||||||
|  |                 form_class = PetForm | ||||||
|  |                 model = FilmDetails | ||||||
|  | 
 | ||||||
|  |         self.assertEqual(PetMutation._meta.model, FilmDetails) | ||||||
|  |         self.assertEqual(PetMutation._meta.return_field_name, 'filmDetails') | ||||||
|  | 
 | ||||||
|  |     def test_custom_return_field_name(self): | ||||||
|  |         class PetMutation(DjangoModelFormMutation): | ||||||
|  |             class Meta: | ||||||
|  |                 form_class = PetForm | ||||||
|  |                 model = Film | ||||||
|  |                 return_field_name = 'animal' | ||||||
|  | 
 | ||||||
|  |         self.assertEqual(PetMutation._meta.model, Film) | ||||||
|  |         self.assertEqual(PetMutation._meta.return_field_name, 'animal') | ||||||
|  |         self.assertIn('animal', PetMutation._meta.fields) | ||||||
|  | 
 | ||||||
|  |     def test_model_form_mutation_mutate(self): | ||||||
|  |         class PetMutation(DjangoModelFormMutation): | ||||||
|  |             class Meta: | ||||||
|  |                 form_class = PetForm | ||||||
|  | 
 | ||||||
|  |         pet = Pet.objects.create(name='Axel') | ||||||
|  | 
 | ||||||
|  |         result = PetMutation.mutate_and_get_payload(None, None, id=pet.pk, name='Mia') | ||||||
|  | 
 | ||||||
|  |         self.assertEqual(Pet.objects.count(), 1) | ||||||
|  |         pet.refresh_from_db() | ||||||
|  |         self.assertEqual(pet.name, 'Mia') | ||||||
|  |         self.assertEqual(result.errors, []) | ||||||
|  | 
 | ||||||
|  |     def test_model_form_mutation_updates_existing_(self): | ||||||
|  |         class PetMutation(DjangoModelFormMutation): | ||||||
|  |             class Meta: | ||||||
|  |                 form_class = PetForm | ||||||
|  | 
 | ||||||
|  |         result = PetMutation.mutate_and_get_payload(None, None, name='Mia') | ||||||
|  | 
 | ||||||
|  |         self.assertEqual(Pet.objects.count(), 1) | ||||||
|  |         pet = Pet.objects.get() | ||||||
|  |         self.assertEqual(pet.name, 'Mia') | ||||||
|  |         self.assertEqual(result.errors, []) | ||||||
|  | 
 | ||||||
|  |     def test_model_form_mutation_mutate_invalid_form(self): | ||||||
|  |         class PetMutation(DjangoModelFormMutation): | ||||||
|  |             class Meta: | ||||||
|  |                 form_class = PetForm | ||||||
|  | 
 | ||||||
|  |         result = PetMutation.mutate_and_get_payload(None, None) | ||||||
|  | 
 | ||||||
|  |         # A pet was not created | ||||||
|  |         self.assertEqual(Pet.objects.count(), 0) | ||||||
|  | 
 | ||||||
|  |         self.assertEqual(len(result.errors), 1) | ||||||
|  |         self.assertEqual(result.errors[0].field, 'name') | ||||||
|  |         self.assertEqual(result.errors[0].messages, ['This field is required.']) | ||||||
							
								
								
									
										6
									
								
								graphene_django/forms/types.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								graphene_django/forms/types.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | ||||||
|  | import graphene | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ErrorType(graphene.ObjectType): | ||||||
|  |     field = graphene.String() | ||||||
|  |     messages = graphene.List(graphene.String) | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user