From d33fbbe6960d223a97b06f0d5a08810c6e56c189 Mon Sep 17 00:00:00 2001 From: Alexander Forselius Date: Fri, 19 Jan 2024 13:33:35 +0100 Subject: [PATCH] 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__(