Update django form mutation to more granularly handle fields

Set up forms

Update docs

Nearly working tests
This commit is contained in:
Jeff Tratner 2020-04-15 15:45:28 -07:00 committed by Jeff Tratner
parent a987035ef3
commit 4246dab3d6
No known key found for this signature in database
GPG Key ID: 59A47B7A1FEE75A1
3 changed files with 140 additions and 4 deletions

View File

@ -63,9 +63,15 @@ DjangoFormMutation
class MyForm(forms.Form):
name = forms.CharField()
def clean(self):
self.cleaned_data["constructed_output"] = "an item"
class MyMutation(DjangoFormMutation):
class Meta:
form_class = MyForm
input_fields = "__all__"
constructed_output = graphene.String()
``MyMutation`` will automatically receive an ``input`` argument. This argument should be a ``dict`` where the key is ``name`` and the value is a string.

View File

@ -1,4 +1,5 @@
# from django import forms
import warnings
from collections import OrderedDict
import graphene
@ -71,6 +72,31 @@ class DjangoFormMutationOptions(MutationOptions):
class DjangoFormMutation(BaseDjangoFormMutation):
"""Create a mutation based on a Django Form.
The form's fields will, by default, be set as inputs. Specify ``input_fields`` to limit to a
subset.
You can use ``fields`` and ``exclude`` to limit output fields. Use ``fields = '__all__'`` to
select all fields.
Fields are considered to be required based on the ``required`` attribute of the form.
Meta fields:
form_class (class): the model to base form off of
input_fields (List[str], optional): limit the input fields of the form to be used (by default uses all of them)
fields (List[str], optional): only output the subset of fields as output (based on ``cleaned_data``), use
``__all__`` to get all fields
exclude (List[str], optional): remove specified fields from output (uses ``cleaned_data``)
The default output of the mutation will use ``form.cleaned_data`` as params.
Override ``perform_mutate(cls, form, info) -> DjangoFormMutation`` to customize this behavior.
NOTE: ``only_fields`` and ``exclude_fields`` are still supported for backwards compatibility
but are deprecated and will be removed in a future version.
"""
class Meta:
abstract = True
@ -78,15 +104,37 @@ class DjangoFormMutation(BaseDjangoFormMutation):
@classmethod
def __init_subclass_with_meta__(
cls, form_class=None, only_fields=(), exclude_fields=(), **options
cls, form_class=None, only_fields=(), exclude_fields=(),
fields=None, exclude=(), input_fields=None,
**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)
if (any([fields, exclude, input_fields])
and (only_fields or exclude_fields)):
raise Exception("Cannot specify legacy `only_fields` or `exclude_fields` params with"
" `only`, `exclude`, or `input_fields` params")
if only_fields or exclude_fields:
warnings.warn(
"only_fields/exclude_fields have been deprecated, use "
"input_fields or only/exclude (for output fields)"
"instead",
DeprecationWarning
)
if not fields or exclude:
warnings.warn(
"a future version of graphene-django will require fields or exclude."
" Set fields='__all__' to allow all fields through.",
DeprecationWarning
)
if not input_fields and input_fields is not None:
input_fields = {}
else:
input_fields = fields_for_form(form, only_fields or input_fields, exclude_fields)
output_fields = fields_for_form(form, only_fields or fields, exclude_fields or exclude)
_meta = DjangoFormMutationOptions(cls)
_meta.form_class = form_class

View File

@ -1,9 +1,10 @@
import pytest
from django import forms
from django.test import TestCase
from django.core.exceptions import ValidationError
from py.test import raises
from graphene import Field, ObjectType, Schema, String
from graphene import Field, Int, ObjectType, Schema, String
from graphene_django import DjangoObjectType
from graphene_django.tests.models import Pet
@ -22,6 +23,7 @@ def pet_type():
class MyForm(forms.Form):
text = forms.CharField()
another = forms.CharField(required=False)
def clean_text(self):
text = self.cleaned_data["text"]
@ -29,6 +31,9 @@ class MyForm(forms.Form):
raise ValidationError("Invalid input")
return text
def clean_another(self):
self.cleaned_data["another"] = self.cleaned_data["another"] or "defaultvalue"
def save(self):
pass
@ -68,6 +73,83 @@ def test_has_input_fields():
form_class = MyForm
assert "text" in MyMutation.Input._meta.fields
assert "another" in MyMutation.Input._meta.fields
def test_no_input_fields():
class MyMutation(DjangoFormMutation):
class Meta:
form_class = MyForm
input_fields = []
assert set(MyMutation.Input._meta.fields.keys()) == set(["client_mutation_id"])
def test_filtering_input_fields():
class MyMutation(DjangoFormMutation):
class Meta:
form_class = MyForm
input_fields = ["text"]
assert "text" in MyMutation.Input._meta.fields
assert "another" not in MyMutation.Input._meta.fields
def test_select_output_fields():
class MyMutation(DjangoFormMutation):
class Meta:
form_class = MyForm
fields = ["text"]
assert "text" in MyMutation._meta.fields
assert "another" not in MyMutation._meta.fields
def test_filtering_output_fields_exclude():
class FormWithWeirdOutput(MyForm):
"""Weird form that has extra cleaned_data we want to expose"""
text = forms.CharField()
another = forms.CharField(required=False)
def clean(self):
super(FormWithWeirdOutput, self).clean()
self.cleaned_data["some_integer"] = 5
return self.cleaned_data
class MyMutation(DjangoFormMutation):
class Meta:
form_class = FormWithWeirdOutput
exclude = ["text"]
some_integer = Int()
assert "text" in MyMutation.Input._meta.fields
assert "another" in MyMutation.Input._meta.fields
assert "text" not in MyMutation._meta.fields
assert "another" in MyMutation._meta.fields
assert "some_integer" in MyMutation._meta.fields
class Mutation(ObjectType):
my_mutation = MyMutation.Field()
schema = Schema(query=MockQuery, mutation=Mutation)
result = schema.execute(
""" mutation MyMutation {
myMutation(input: { text: "VALID_INPUT" }) {
errors {
field
messages
}
another
someInteger
}
}
"""
)
assert result.errors is None
assert result.data["myMutation"]["errors"] == []
assert result.data["myMutation"]["someInteger"] == 5
assert result.data["myMutation"]["another"] == "defaultvalue"
def test_mutation_error_camelcased(pet_type, graphene_settings):