From d33fbbe6960d223a97b06f0d5a08810c6e56c189 Mon Sep 17 00:00:00 2001 From: Alexander Forselius Date: Fri, 19 Jan 2024 13:33:35 +0100 Subject: [PATCH 1/4] Working on DjangoFormObjectType --- .../forms/tests/test_objecttype.py | 117 ++++++++++++++++++ graphene_django/forms/types.py | 69 +++++++++++ 2 files changed, 186 insertions(+) create mode 100644 graphene_django/forms/tests/test_objecttype.py diff --git a/graphene_django/forms/tests/test_objecttype.py b/graphene_django/forms/tests/test_objecttype.py new file mode 100644 index 0000000..d11ae78 --- /dev/null +++ b/graphene_django/forms/tests/test_objecttype.py @@ -0,0 +1,117 @@ +from django import forms +from pytest import raises + +import graphene +from graphene_django import DjangoObjectType + +from ...tests.models import CHOICES, Film, Reporter +from ..types import DjangoFormFieldObjectType, DjangoFormInputObjectType, DjangoFormObjectType + +# Reporter a_choice CHOICES = ((1, "this"), (2, _("that"))) +THIS = CHOICES[0][0] +THIS_ON_CLIENT_CONVERTED = "A_1" + +# Film genre choices=[("do", "Documentary"), ("ac", "Action"), ("ot", "Other")], +DOCUMENTARY = "do" +DOCUMENTARY_ON_CLIENT_CONVERTED = "DO" + + +class FilmForm(forms.ModelForm): + class Meta: + model = Film + exclude = () + + +class ReporterType(DjangoObjectType): + class Meta: + model = Reporter + fields = "__all__" + + +class ReporterForm(forms.ModelForm): + class Meta: + model = Reporter + exclude = ("pets", "email", "fans") + + +class MyForm(forms.Form): + text_field = forms.CharField() + int_field = forms.IntegerField() + + +class ReporterFormType(DjangoFormObjectType): + class Meta: + form_class = ReporterForm + + +def test_needs_form_class(): + with raises(Exception) as exc: + class MyFormType(DjangoFormObjectType): + pass + + assert exc.value.args[0] == "form_class is required for DjangoFormObjectType" + + +def test_type_form_has_fields(): + class ReporterFormType(DjangoFormObjectType): + class Meta: + form_class = ReporterForm + only_fields = ("first_name", "last_name", "a_choice") + + fields = ["first_name", "last_name", "a_choice", "id"] + assert all(f in ReporterFormType._meta.fields for f in fields) + + +def test_type_form_has_fields(): + class MyFormFieldType(DjangoFormFieldObjectType): + class Meta: + form_class = MyForm + + fields = ["text_field", "int_field", "id"] + assert all(f in MyFormFieldType._meta.fields for f in fields) + + +def test_query_djangoformtype(): + class MyFormType(DjangoFormObjectType): + class Meta: + form_class = MyForm + + class MockQuery(graphene.ObjectType): + form = graphene.Field( + MyFormType + ) + + @staticmethod + def resolve_form(parent, info): + return MyFormType() + + schema = graphene.Schema(query=MockQuery) + + result = schema.execute( + """ + query { + form { + fields { + name + type + } + } + } + + """ + ) + assert result.errors is None + assert result.data == { + "form": { + "fields": [ + { + "name": "text_field", + "type": "CharField" + }, + { + "name": "int_field", + "type": "IntegerField" + } + ] + } + } diff --git a/graphene_django/forms/types.py b/graphene_django/forms/types.py index 0e311e5..c7f8cae 100644 --- a/graphene_django/forms/types.py +++ b/graphene_django/forms/types.py @@ -1,13 +1,82 @@ +from django.forms import Form import graphene from graphene import ID +from graphene.types.objecttype import ObjectType from graphene.types.inputobjecttype import InputObjectType from graphene.utils.str_converters import to_camel_case +from django.db.models import Model + from ..converter import BlankValueField from ..types import ErrorType # noqa Import ErrorType for backwards compatibility from .mutation import fields_for_form +class DjangoFormFieldObjectType(ObjectType): + class Meta: + form_class = Form + name = graphene.Field( + graphene.String + ) + + type = graphene.Field( + graphene.String + ) + + +class DjangoFormObjectType(ObjectType): + class Meta: + form_class = Form + object_type = Model + only_fields = [] + exclude_fields = [] + + fields = graphene.List( + DjangoFormFieldObjectType + ) + + @classmethod + def __init_subclass_with_meta__( + cls, + container=None, + _meta=None, + only_fields=(), + exclude_fields=(), + form_class=None, + object_type=None, + add_id_field_name=None, + add_id_field_type=None, + **options, + ): + + if not form_class: + raise Exception("form_class is required for DjangoFormObjectType") + + super().__init_subclass_with_meta__(container=container, _meta=_meta, **options) + + @staticmethod + def resolve_fields(parent, info): + form_class = parent._meta.form_class + form = form_class() + only_fields = parent._meta.only_fields + exclude_fields = parent._meta.exclude_fields + + form_fields = fields_for_form(form, only_fields, exclude_fields) + + result = [] + + type = field.__class__.__name__ + + for name, field in form_fields.items(): + result.append( + DjangoFormFieldObjectType( + name=name, + type=type + ) + ) + return result + + class DjangoFormInputObjectType(InputObjectType): @classmethod def __init_subclass_with_meta__( From ade2918d21a13121a8813e7f1f9dbede99b76c67 Mon Sep 17 00:00:00 2001 From: Alexander Forselius Date: Fri, 19 Jan 2024 13:35:14 +0100 Subject: [PATCH 2/4] Working on DjangoFormObjectType --- graphene_django/forms/tests/test_objecttype.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene_django/forms/tests/test_objecttype.py b/graphene_django/forms/tests/test_objecttype.py index d11ae78..577039a 100644 --- a/graphene_django/forms/tests/test_objecttype.py +++ b/graphene_django/forms/tests/test_objecttype.py @@ -88,7 +88,7 @@ def test_query_djangoformtype(): schema = graphene.Schema(query=MockQuery) result = schema.execute( - """ + """ query { form { fields { From f9a4305e612eef15f32476c8b825f160e2d7c8f2 Mon Sep 17 00:00:00 2001 From: Alexander Forselius Date: Fri, 19 Jan 2024 18:05:01 +0100 Subject: [PATCH 3/4] Implementing reading form fields --- .../forms/tests/test_objecttype.py | 43 ++++------------ graphene_django/forms/types.py | 50 +++++++------------ 2 files changed, 27 insertions(+), 66 deletions(-) diff --git a/graphene_django/forms/tests/test_objecttype.py b/graphene_django/forms/tests/test_objecttype.py index 577039a..1583ae4 100644 --- a/graphene_django/forms/tests/test_objecttype.py +++ b/graphene_django/forms/tests/test_objecttype.py @@ -5,7 +5,7 @@ import graphene from graphene_django import DjangoObjectType from ...tests.models import CHOICES, Film, Reporter -from ..types import DjangoFormFieldObjectType, DjangoFormInputObjectType, DjangoFormObjectType +from ..types import DjangoFormFieldObjectType, DjangoFormInputObjectType, DjangoFormObjectType, DjangoFormTypeOptions # Reporter a_choice CHOICES = ((1, "this"), (2, _("that"))) THIS = CHOICES[0][0] @@ -40,41 +40,16 @@ class MyForm(forms.Form): class ReporterFormType(DjangoFormObjectType): - class Meta: - form_class = ReporterForm - - -def test_needs_form_class(): - with raises(Exception) as exc: - class MyFormType(DjangoFormObjectType): - pass - - assert exc.value.args[0] == "form_class is required for DjangoFormObjectType" - - -def test_type_form_has_fields(): - class ReporterFormType(DjangoFormObjectType): - class Meta: - form_class = ReporterForm - only_fields = ("first_name", "last_name", "a_choice") - - fields = ["first_name", "last_name", "a_choice", "id"] - assert all(f in ReporterFormType._meta.fields for f in fields) - - -def test_type_form_has_fields(): - class MyFormFieldType(DjangoFormFieldObjectType): - class Meta: - form_class = MyForm - - fields = ["text_field", "int_field", "id"] - assert all(f in MyFormFieldType._meta.fields for f in fields) + form_class = ReporterForm + only_fields = ('pets', 'email') def test_query_djangoformtype(): class MyFormType(DjangoFormObjectType): - class Meta: - form_class = MyForm + form_class = MyForm + + only_fields = ('text_field', 'int_field') + exclude_fields = [] class MockQuery(graphene.ObjectType): form = graphene.Field( @@ -106,11 +81,11 @@ def test_query_djangoformtype(): "fields": [ { "name": "text_field", - "type": "CharField" + "type": "String" }, { "name": "int_field", - "type": "IntegerField" + "type": "Int" } ] } diff --git a/graphene_django/forms/types.py b/graphene_django/forms/types.py index c7f8cae..162cf87 100644 --- a/graphene_django/forms/types.py +++ b/graphene_django/forms/types.py @@ -8,8 +8,9 @@ from graphene.utils.str_converters import to_camel_case from django.db.models import Model from ..converter import BlankValueField -from ..types import ErrorType # noqa Import ErrorType for backwards compatibility +from ..types import DjangoObjectTypeOptions, ErrorType # noqa Import ErrorType for backwards compatibility from .mutation import fields_for_form +from graphene.types.objecttype import ObjectTypeOptions class DjangoFormFieldObjectType(ObjectType): @@ -24,50 +25,35 @@ class DjangoFormFieldObjectType(ObjectType): ) +class DjangoFormTypeOptions(ObjectTypeOptions): + form_class = Form + only_fields = () + exclude_fields = () + object_type = Model + + class DjangoFormObjectType(ObjectType): - class Meta: - form_class = Form - object_type = Model - only_fields = [] - exclude_fields = [] + form_class = Form + only_fields = () + exclude_fields = () fields = graphene.List( DjangoFormFieldObjectType ) - - @classmethod - def __init_subclass_with_meta__( - cls, - container=None, - _meta=None, - only_fields=(), - exclude_fields=(), - form_class=None, - object_type=None, - add_id_field_name=None, - add_id_field_type=None, - **options, - ): - - if not form_class: - raise Exception("form_class is required for DjangoFormObjectType") - - super().__init_subclass_with_meta__(container=container, _meta=_meta, **options) @staticmethod - def resolve_fields(parent, info): - form_class = parent._meta.form_class + def resolve_fields(parent, info): + form_class = parent.form_class form = form_class() - only_fields = parent._meta.only_fields - exclude_fields = parent._meta.exclude_fields + only_fields = parent.only_fields + exclude_fields = parent.exclude_fields form_fields = fields_for_form(form, only_fields, exclude_fields) - + result = [] - - type = field.__class__.__name__ for name, field in form_fields.items(): + type = field.__class__.__name__ result.append( DjangoFormFieldObjectType( name=name, From 07bd845bf602d74df2a6b8d6c0c0a556d077043f Mon Sep 17 00:00:00 2001 From: Alexander Forselius Date: Fri, 19 Jan 2024 21:42:08 +0100 Subject: [PATCH 4/4] Implementing reading form fields --- graphene_django/forms/tests/test_objecttype.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene_django/forms/tests/test_objecttype.py b/graphene_django/forms/tests/test_objecttype.py index 1583ae4..d6e83ea 100644 --- a/graphene_django/forms/tests/test_objecttype.py +++ b/graphene_django/forms/tests/test_objecttype.py @@ -47,7 +47,7 @@ class ReporterFormType(DjangoFormObjectType): def test_query_djangoformtype(): class MyFormType(DjangoFormObjectType): form_class = MyForm - + only_fields = ('text_field', 'int_field') exclude_fields = []