|
| 1 | +import graphene |
| 2 | + |
| 3 | +from graphene import ID |
| 4 | +from graphene.types.inputobjecttype import InputObjectType |
| 5 | +from graphene.utils.str_converters import to_camel_case |
| 6 | + |
| 7 | +from .mutation import fields_for_form |
1 | 8 | from ..types import ErrorType # noqa Import ErrorType for backwards compatability
|
| 9 | +from ..converter import BlankValueField |
| 10 | + |
| 11 | + |
| 12 | +class DjangoFormInputObjectType(InputObjectType): |
| 13 | + @classmethod |
| 14 | + def __init_subclass_with_meta__( |
| 15 | + cls, |
| 16 | + container=None, |
| 17 | + _meta=None, |
| 18 | + only_fields=(), |
| 19 | + exclude_fields=(), |
| 20 | + form_class=None, |
| 21 | + object_type=None, |
| 22 | + add_id_field_name=None, |
| 23 | + add_id_field_type=None, |
| 24 | + **options, |
| 25 | + ): |
| 26 | + """Retrieve fields from django form (Meta.form_class). Received |
| 27 | + fields are set to cls (they will be converted to input fields |
| 28 | + by InputObjectType). Type of fields with choices (converted |
| 29 | + to enum) is set to custom scalar type (using Meta.object_type) |
| 30 | + to dynamically convert enum values back. |
| 31 | +
|
| 32 | + class MyDjangoFormInput(DjangoFormInputObjectType): |
| 33 | + # any other fields can be placed here and other inputobjectforms as well |
| 34 | +
|
| 35 | + class Meta: |
| 36 | + form_class = MyDjangoModelForm |
| 37 | + object_type = MyModelType |
| 38 | +
|
| 39 | + class SomeMutation(graphene.Mutation): |
| 40 | + class Arguments: |
| 41 | + data = MyDjangoFormInput(required=True) |
| 42 | +
|
| 43 | + @staticmethod |
| 44 | + def mutate(_root, _info, data): |
| 45 | + form_inst = MyDjangoModelForm(data=data) |
| 46 | + if form_inst.is_valid(): |
| 47 | + django_model_instance = form_inst.save(commit=False) |
| 48 | + # ... etc ... |
| 49 | + """ |
| 50 | + |
| 51 | + if not form_class: |
| 52 | + raise Exception("form_class is required for DjangoFormInputObjectType") |
| 53 | + |
| 54 | + form = form_class() |
| 55 | + form_fields = fields_for_form(form, only_fields, exclude_fields) |
| 56 | + |
| 57 | + for name, field in form_fields.items(): |
| 58 | + if ( |
| 59 | + object_type |
| 60 | + and name in object_type._meta.fields |
| 61 | + and isinstance(object_type._meta.fields[name], BlankValueField) |
| 62 | + ): |
| 63 | + # Field type BlankValueField here means that field |
| 64 | + # with choises have been converted to enum |
| 65 | + # (BlankValueField is using only for that task ?) |
| 66 | + setattr(cls, name, cls.get_enum_cnv_cls_instance(name, object_type)) |
| 67 | + elif ( |
| 68 | + object_type |
| 69 | + and name in object_type._meta.fields |
| 70 | + and object_type._meta.convert_choices_to_enum is False |
| 71 | + and form.fields[name].__class__.__name__ == "TypedChoiceField" |
| 72 | + ): |
| 73 | + # FIXME |
| 74 | + # in case if convert_choices_to_enum is False |
| 75 | + # form field class is converted to String but original |
| 76 | + # model field type is needed here... (.converter.py bug?) |
| 77 | + # This is temp workaround to get field type from ObjectType field |
| 78 | + # TEST: test_enum_not_converted_and_field_type_as_in_model |
| 79 | + setattr(cls, name, object_type._meta.fields[name].type()) |
| 80 | + else: |
| 81 | + # set input field according to django form field |
| 82 | + setattr(cls, name, field) |
| 83 | + |
| 84 | + # explicitly adding id field (absent in django form fields) |
| 85 | + # with name and type from Meta or 'id' with graphene.ID by default |
| 86 | + if add_id_field_name: |
| 87 | + setattr(cls, add_id_field_name, add_id_field_type or ID(required=False)) |
| 88 | + elif "id" not in exclude_fields: |
| 89 | + cls.id = ID(required=False) |
| 90 | + |
| 91 | + super(DjangoFormInputObjectType, cls).__init_subclass_with_meta__( |
| 92 | + container=container, _meta=_meta, **options |
| 93 | + ) |
| 94 | + |
| 95 | + @staticmethod |
| 96 | + def get_enum_cnv_cls_instance(field_name, object_type): |
| 97 | + """Saves args in context to convert enum values in |
| 98 | + Dynamically created Scalar derived class |
| 99 | + """ |
| 100 | + |
| 101 | + @staticmethod |
| 102 | + def parse_value(value): |
| 103 | + # field_name & object_type have been saved in context (closure) |
| 104 | + field = object_type._meta.fields[field_name] |
| 105 | + if isinstance(field.type, graphene.NonNull): |
| 106 | + val_before_convert = field.type._of_type[value].value |
| 107 | + else: |
| 108 | + val_before_convert = field.type[value].value |
| 109 | + return graphene.String.parse_value(val_before_convert) |
| 110 | + |
| 111 | + cls_doc = "String scalar to convert choice value back from enum to original" |
| 112 | + scalar_type = type( |
| 113 | + ( |
| 114 | + f"{field_name[0].upper()}{to_camel_case(field_name[1:])}" |
| 115 | + "EnumBackConvString" |
| 116 | + ), |
| 117 | + (graphene.String,), |
| 118 | + {"parse_value": parse_value, "__doc__": cls_doc}, |
| 119 | + ) |
| 120 | + return scalar_type() |
0 commit comments