mirror of
https://github.com/graphql-python/graphene-django.git
synced 2025-07-08 22:33:13 +03:00
Update django form mutation to more granularly handle fields
Set up forms Update docs Nearly working tests
This commit is contained in:
parent
a987035ef3
commit
4246dab3d6
|
@ -63,9 +63,15 @@ DjangoFormMutation
|
||||||
class MyForm(forms.Form):
|
class MyForm(forms.Form):
|
||||||
name = forms.CharField()
|
name = forms.CharField()
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
self.cleaned_data["constructed_output"] = "an item"
|
||||||
|
|
||||||
class MyMutation(DjangoFormMutation):
|
class MyMutation(DjangoFormMutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
form_class = MyForm
|
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.
|
``MyMutation`` will automatically receive an ``input`` argument. This argument should be a ``dict`` where the key is ``name`` and the value is a string.
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# from django import forms
|
# from django import forms
|
||||||
|
import warnings
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
|
@ -71,6 +72,31 @@ class DjangoFormMutationOptions(MutationOptions):
|
||||||
|
|
||||||
|
|
||||||
class DjangoFormMutation(BaseDjangoFormMutation):
|
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:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
@ -78,15 +104,37 @@ class DjangoFormMutation(BaseDjangoFormMutation):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __init_subclass_with_meta__(
|
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:
|
if not form_class:
|
||||||
raise Exception("form_class is required for DjangoFormMutation")
|
raise Exception("form_class is required for DjangoFormMutation")
|
||||||
|
|
||||||
form = form_class()
|
form = form_class()
|
||||||
input_fields = fields_for_form(form, only_fields, exclude_fields)
|
if (any([fields, exclude, input_fields])
|
||||||
output_fields = fields_for_form(form, only_fields, exclude_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 = DjangoFormMutationOptions(cls)
|
||||||
_meta.form_class = form_class
|
_meta.form_class = form_class
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import pytest
|
import pytest
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.test import TestCase
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from py.test import raises
|
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 import DjangoObjectType
|
||||||
from graphene_django.tests.models import Pet
|
from graphene_django.tests.models import Pet
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ def pet_type():
|
||||||
|
|
||||||
class MyForm(forms.Form):
|
class MyForm(forms.Form):
|
||||||
text = forms.CharField()
|
text = forms.CharField()
|
||||||
|
another = forms.CharField(required=False)
|
||||||
|
|
||||||
def clean_text(self):
|
def clean_text(self):
|
||||||
text = self.cleaned_data["text"]
|
text = self.cleaned_data["text"]
|
||||||
|
@ -29,6 +31,9 @@ class MyForm(forms.Form):
|
||||||
raise ValidationError("Invalid input")
|
raise ValidationError("Invalid input")
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
def clean_another(self):
|
||||||
|
self.cleaned_data["another"] = self.cleaned_data["another"] or "defaultvalue"
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -68,6 +73,83 @@ def test_has_input_fields():
|
||||||
form_class = MyForm
|
form_class = MyForm
|
||||||
|
|
||||||
assert "text" in MyMutation.Input._meta.fields
|
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):
|
def test_mutation_error_camelcased(pet_type, graphene_settings):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user