2019-09-22 23:13:12 +03:00
|
|
|
from collections import OrderedDict
|
2021-02-23 07:10:30 +03:00
|
|
|
from functools import singledispatch, wraps
|
2020-04-06 15:21:07 +03:00
|
|
|
|
2016-09-18 02:29:00 +03:00
|
|
|
from django.db import models
|
2020-01-29 13:06:38 +03:00
|
|
|
from django.utils.encoding import force_str
|
2020-05-09 14:13:47 +03:00
|
|
|
from django.utils.functional import Promise
|
2020-03-13 13:04:25 +03:00
|
|
|
from django.utils.module_loading import import_string
|
2021-01-12 03:34:50 +03:00
|
|
|
|
2018-07-20 02:51:33 +03:00
|
|
|
from graphene import (
|
|
|
|
ID,
|
2020-05-09 14:13:47 +03:00
|
|
|
UUID,
|
2018-07-20 02:51:33 +03:00
|
|
|
Boolean,
|
2020-05-09 14:13:47 +03:00
|
|
|
Date,
|
|
|
|
DateTime,
|
2018-07-20 02:51:33 +03:00
|
|
|
Dynamic,
|
|
|
|
Enum,
|
|
|
|
Field,
|
|
|
|
Float,
|
|
|
|
Int,
|
|
|
|
List,
|
|
|
|
NonNull,
|
|
|
|
String,
|
|
|
|
Time,
|
2020-12-31 02:37:57 +03:00
|
|
|
Decimal,
|
2018-07-20 02:51:33 +03:00
|
|
|
)
|
2016-09-18 03:09:56 +03:00
|
|
|
from graphene.types.json import JSONString
|
2022-09-22 21:09:11 +03:00
|
|
|
from graphene.types.scalars import BigInt
|
2020-06-27 12:43:25 +03:00
|
|
|
from graphene.utils.str_converters import to_camel_case
|
2022-10-17 17:57:24 +03:00
|
|
|
from graphql import GraphQLError
|
|
|
|
|
|
|
|
try:
|
|
|
|
from graphql import assert_name
|
|
|
|
except ImportError:
|
|
|
|
# Support for older versions of graphql
|
|
|
|
from graphql import assert_valid_name as assert_name
|
2020-05-09 14:13:47 +03:00
|
|
|
from graphql.pyutils import register_description
|
2016-09-18 02:29:00 +03:00
|
|
|
|
2020-08-07 12:22:15 +03:00
|
|
|
from .compat import ArrayField, HStoreField, JSONField, PGJSONField, RangeField
|
2017-07-25 08:27:50 +03:00
|
|
|
from .fields import DjangoListField, DjangoConnectionField
|
2020-05-09 14:13:47 +03:00
|
|
|
from .settings import graphene_settings
|
2020-06-27 12:43:25 +03:00
|
|
|
from .utils.str_converters import to_const
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
|
2021-01-12 03:34:50 +03:00
|
|
|
class BlankValueField(Field):
|
2021-02-23 07:21:32 +03:00
|
|
|
def wrap_resolve(self, parent_resolver):
|
2021-01-12 03:34:50 +03:00
|
|
|
resolver = self.resolver or parent_resolver
|
|
|
|
|
|
|
|
# create custom resolver
|
|
|
|
def blank_field_wrapper(func):
|
|
|
|
@wraps(func)
|
|
|
|
def wrapped_resolver(*args, **kwargs):
|
|
|
|
return_value = func(*args, **kwargs)
|
|
|
|
if return_value == "":
|
|
|
|
return None
|
|
|
|
return return_value
|
|
|
|
|
|
|
|
return wrapped_resolver
|
|
|
|
|
|
|
|
return blank_field_wrapper(resolver)
|
|
|
|
|
|
|
|
|
2016-09-18 02:29:00 +03:00
|
|
|
def convert_choice_name(name):
|
2020-01-29 13:06:38 +03:00
|
|
|
name = to_const(force_str(name))
|
2016-10-14 05:01:01 +03:00
|
|
|
try:
|
2022-10-17 17:57:24 +03:00
|
|
|
assert_name(name)
|
2020-05-09 14:13:47 +03:00
|
|
|
except GraphQLError:
|
2016-10-14 05:01:01 +03:00
|
|
|
name = "A_%s" % name
|
|
|
|
return name
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
|
|
|
|
def get_choices(choices):
|
2017-01-04 19:23:17 +03:00
|
|
|
converted_names = []
|
2019-09-22 23:13:12 +03:00
|
|
|
if isinstance(choices, OrderedDict):
|
|
|
|
choices = choices.items()
|
2016-09-18 02:29:00 +03:00
|
|
|
for value, help_text in choices:
|
|
|
|
if isinstance(help_text, (tuple, list)):
|
2022-10-19 17:10:30 +03:00
|
|
|
yield from get_choices(help_text)
|
2016-09-18 02:29:00 +03:00
|
|
|
else:
|
2016-10-14 05:01:01 +03:00
|
|
|
name = convert_choice_name(value)
|
2017-01-05 12:49:26 +03:00
|
|
|
while name in converted_names:
|
2018-07-20 02:51:33 +03:00
|
|
|
name += "_" + str(len(converted_names))
|
2017-01-04 19:23:17 +03:00
|
|
|
converted_names.append(name)
|
2020-05-09 14:13:47 +03:00
|
|
|
description = str(
|
|
|
|
help_text
|
|
|
|
) # TODO: translatable description: https://github.com/graphql-python/graphql-core-next/issues/58
|
2016-09-18 02:29:00 +03:00
|
|
|
yield name, value, description
|
|
|
|
|
|
|
|
|
2019-09-22 23:13:12 +03:00
|
|
|
def convert_choices_to_named_enum_with_descriptions(name, choices):
|
|
|
|
choices = list(get_choices(choices))
|
|
|
|
named_choices = [(c[0], c[1]) for c in choices]
|
|
|
|
named_choices_descriptions = {c[0]: c[2] for c in choices}
|
|
|
|
|
2022-10-19 17:10:30 +03:00
|
|
|
class EnumWithDescriptionsType:
|
2019-09-22 23:13:12 +03:00
|
|
|
@property
|
|
|
|
def description(self):
|
2020-05-09 14:13:47 +03:00
|
|
|
return str(named_choices_descriptions[self.name])
|
2019-09-22 23:13:12 +03:00
|
|
|
|
2021-01-12 03:34:50 +03:00
|
|
|
return_type = Enum(name, list(named_choices), type=EnumWithDescriptionsType)
|
|
|
|
return return_type
|
2019-09-22 23:13:12 +03:00
|
|
|
|
|
|
|
|
2020-03-13 13:04:25 +03:00
|
|
|
def generate_enum_name(django_model_meta, field):
|
|
|
|
if graphene_settings.DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME:
|
|
|
|
# Try and import custom function
|
|
|
|
custom_func = import_string(
|
|
|
|
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME
|
|
|
|
)
|
|
|
|
name = custom_func(field)
|
2020-06-10 19:32:07 +03:00
|
|
|
elif graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V2_NAMING is True:
|
2022-10-19 17:10:30 +03:00
|
|
|
name = to_camel_case(f"{django_model_meta.object_name}_{field.name}")
|
2020-06-10 19:32:07 +03:00
|
|
|
else:
|
2020-03-13 13:04:25 +03:00
|
|
|
name = "{app_label}{object_name}{field_name}Choices".format(
|
|
|
|
app_label=to_camel_case(django_model_meta.app_label.title()),
|
|
|
|
object_name=django_model_meta.object_name,
|
|
|
|
field_name=to_camel_case(field.name.title()),
|
|
|
|
)
|
|
|
|
return name
|
|
|
|
|
|
|
|
|
|
|
|
def convert_choice_field_to_enum(field, name=None):
|
|
|
|
if name is None:
|
|
|
|
name = generate_enum_name(field.model._meta, field)
|
|
|
|
choices = field.choices
|
|
|
|
return convert_choices_to_named_enum_with_descriptions(name, choices)
|
|
|
|
|
|
|
|
|
2019-06-17 20:48:29 +03:00
|
|
|
def convert_django_field_with_choices(
|
|
|
|
field, registry=None, convert_choices_to_enum=True
|
|
|
|
):
|
2017-10-31 16:33:16 +03:00
|
|
|
if registry is not None:
|
2017-04-21 20:25:30 +03:00
|
|
|
converted = registry.get_converted_field(field)
|
|
|
|
if converted:
|
|
|
|
return converted
|
2018-07-20 02:51:33 +03:00
|
|
|
choices = getattr(field, "choices", None)
|
2019-06-17 20:48:29 +03:00
|
|
|
if choices and convert_choices_to_enum:
|
2021-01-12 03:34:50 +03:00
|
|
|
EnumCls = convert_choice_field_to_enum(field)
|
2019-08-01 11:07:52 +03:00
|
|
|
required = not (field.blank or field.null)
|
2021-01-12 03:34:50 +03:00
|
|
|
|
|
|
|
converted = EnumCls(
|
2020-06-06 19:08:10 +03:00
|
|
|
description=get_django_field_description(field), required=required
|
2021-01-12 03:34:50 +03:00
|
|
|
).mount_as(BlankValueField)
|
2017-04-21 20:25:30 +03:00
|
|
|
else:
|
|
|
|
converted = convert_django_field(field, registry)
|
2017-10-31 16:33:16 +03:00
|
|
|
if registry is not None:
|
2017-04-21 20:25:30 +03:00
|
|
|
registry.register_converted_field(field, converted)
|
|
|
|
return converted
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
|
2020-06-06 19:08:10 +03:00
|
|
|
def get_django_field_description(field):
|
2020-06-10 19:21:37 +03:00
|
|
|
return str(field.help_text) if field.help_text else None
|
2020-06-06 19:08:10 +03:00
|
|
|
|
|
|
|
|
2016-09-18 02:29:00 +03:00
|
|
|
@singledispatch
|
|
|
|
def convert_django_field(field, registry=None):
|
|
|
|
raise Exception(
|
2022-10-19 17:10:30 +03:00
|
|
|
"Don't know how to convert the Django field {} ({})".format(
|
|
|
|
field, field.__class__
|
|
|
|
)
|
2018-07-20 02:51:33 +03:00
|
|
|
)
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
|
|
|
|
@convert_django_field.register(models.CharField)
|
|
|
|
@convert_django_field.register(models.TextField)
|
|
|
|
@convert_django_field.register(models.EmailField)
|
|
|
|
@convert_django_field.register(models.SlugField)
|
|
|
|
@convert_django_field.register(models.URLField)
|
|
|
|
@convert_django_field.register(models.GenericIPAddressField)
|
|
|
|
@convert_django_field.register(models.FileField)
|
2018-06-06 00:39:51 +03:00
|
|
|
@convert_django_field.register(models.FilePathField)
|
2016-09-18 02:29:00 +03:00
|
|
|
def convert_field_to_string(field, registry=None):
|
2020-06-06 19:08:10 +03:00
|
|
|
return String(
|
|
|
|
description=get_django_field_description(field), required=not field.null
|
|
|
|
)
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
|
2021-06-11 23:41:02 +03:00
|
|
|
@convert_django_field.register(models.BigAutoField)
|
2016-09-18 02:29:00 +03:00
|
|
|
@convert_django_field.register(models.AutoField)
|
|
|
|
def convert_field_to_id(field, registry=None):
|
2020-06-06 19:08:10 +03:00
|
|
|
return ID(description=get_django_field_description(field), required=not field.null)
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
|
2021-06-11 23:41:02 +03:00
|
|
|
if hasattr(models, "SmallAutoField"):
|
|
|
|
|
|
|
|
@convert_django_field.register(models.SmallAutoField)
|
|
|
|
def convert_field_small_to_id(field, registry=None):
|
|
|
|
return convert_field_to_id(field, registry)
|
|
|
|
|
|
|
|
|
2017-07-25 09:42:40 +03:00
|
|
|
@convert_django_field.register(models.UUIDField)
|
|
|
|
def convert_field_to_uuid(field, registry=None):
|
2020-06-06 19:08:10 +03:00
|
|
|
return UUID(
|
|
|
|
description=get_django_field_description(field), required=not field.null
|
|
|
|
)
|
2017-07-25 09:42:40 +03:00
|
|
|
|
|
|
|
|
2022-09-22 21:09:11 +03:00
|
|
|
@convert_django_field.register(models.BigIntegerField)
|
|
|
|
def convert_big_int_field(field, registry=None):
|
|
|
|
return BigInt(description=field.help_text, required=not field.null)
|
|
|
|
|
|
|
|
|
2016-09-18 02:29:00 +03:00
|
|
|
@convert_django_field.register(models.PositiveIntegerField)
|
|
|
|
@convert_django_field.register(models.PositiveSmallIntegerField)
|
|
|
|
@convert_django_field.register(models.SmallIntegerField)
|
|
|
|
@convert_django_field.register(models.IntegerField)
|
|
|
|
def convert_field_to_int(field, registry=None):
|
2020-06-06 19:08:10 +03:00
|
|
|
return Int(description=get_django_field_description(field), required=not field.null)
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
|
2020-05-09 14:09:17 +03:00
|
|
|
@convert_django_field.register(models.NullBooleanField)
|
2016-09-18 02:29:00 +03:00
|
|
|
@convert_django_field.register(models.BooleanField)
|
|
|
|
def convert_field_to_boolean(field, registry=None):
|
2020-06-06 19:08:10 +03:00
|
|
|
return Boolean(
|
|
|
|
description=get_django_field_description(field), required=not field.null
|
|
|
|
)
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
|
|
|
|
@convert_django_field.register(models.DecimalField)
|
2020-12-31 02:37:57 +03:00
|
|
|
def convert_field_to_decimal(field, registry=None):
|
2022-09-22 21:13:30 +03:00
|
|
|
return Decimal(
|
|
|
|
description=get_django_field_description(field), required=not field.null
|
|
|
|
)
|
2020-12-31 02:37:57 +03:00
|
|
|
|
|
|
|
|
2016-09-18 02:29:00 +03:00
|
|
|
@convert_django_field.register(models.FloatField)
|
2017-06-23 14:28:29 +03:00
|
|
|
@convert_django_field.register(models.DurationField)
|
2016-09-18 02:29:00 +03:00
|
|
|
def convert_field_to_float(field, registry=None):
|
2020-06-06 19:08:10 +03:00
|
|
|
return Float(
|
|
|
|
description=get_django_field_description(field), required=not field.null
|
|
|
|
)
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
|
2017-12-05 23:04:29 +03:00
|
|
|
@convert_django_field.register(models.DateTimeField)
|
2017-12-18 20:33:42 +03:00
|
|
|
def convert_datetime_to_string(field, registry=None):
|
2020-06-06 19:08:10 +03:00
|
|
|
return DateTime(
|
|
|
|
description=get_django_field_description(field), required=not field.null
|
|
|
|
)
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
|
2017-12-05 23:04:29 +03:00
|
|
|
@convert_django_field.register(models.DateField)
|
|
|
|
def convert_date_to_string(field, registry=None):
|
2020-06-06 19:08:10 +03:00
|
|
|
return Date(
|
|
|
|
description=get_django_field_description(field), required=not field.null
|
|
|
|
)
|
2017-12-05 23:04:29 +03:00
|
|
|
|
|
|
|
|
2016-11-23 19:29:04 +03:00
|
|
|
@convert_django_field.register(models.TimeField)
|
2016-11-23 20:25:59 +03:00
|
|
|
def convert_time_to_string(field, registry=None):
|
2020-06-06 19:08:10 +03:00
|
|
|
return Time(
|
|
|
|
description=get_django_field_description(field), required=not field.null
|
|
|
|
)
|
2016-11-23 19:29:04 +03:00
|
|
|
|
|
|
|
|
2016-09-18 02:29:00 +03:00
|
|
|
@convert_django_field.register(models.OneToOneRel)
|
|
|
|
def convert_onetoone_field_to_djangomodel(field, registry=None):
|
2017-08-14 16:01:04 +03:00
|
|
|
model = field.related_model
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
def dynamic_type():
|
|
|
|
_type = registry.get_type_for_model(model)
|
|
|
|
if not _type:
|
|
|
|
return
|
|
|
|
|
2021-03-24 09:32:37 +03:00
|
|
|
return Field(_type, required=not field.null)
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
return Dynamic(dynamic_type)
|
|
|
|
|
|
|
|
|
|
|
|
@convert_django_field.register(models.ManyToManyField)
|
|
|
|
@convert_django_field.register(models.ManyToManyRel)
|
|
|
|
@convert_django_field.register(models.ManyToOneRel)
|
|
|
|
def convert_field_to_list_or_connection(field, registry=None):
|
2017-08-14 16:01:04 +03:00
|
|
|
model = field.related_model
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
def dynamic_type():
|
|
|
|
_type = registry.get_type_for_model(model)
|
|
|
|
if not _type:
|
|
|
|
return
|
|
|
|
|
2020-06-10 19:21:37 +03:00
|
|
|
if isinstance(field, models.ManyToManyField):
|
|
|
|
description = get_django_field_description(field)
|
|
|
|
else:
|
|
|
|
description = get_django_field_description(field.field)
|
2019-06-10 03:19:05 +03:00
|
|
|
|
2017-07-25 08:27:50 +03:00
|
|
|
# If there is a connection, we should transform the field
|
|
|
|
# into a DjangoConnectionField
|
|
|
|
if _type._meta.connection:
|
|
|
|
# Use a DjangoFilterConnectionField if there are
|
2019-03-25 06:42:06 +03:00
|
|
|
# defined filter_fields or a filterset_class in the
|
|
|
|
# DjangoObjectType Meta
|
|
|
|
if _type._meta.filter_fields or _type._meta.filterset_class:
|
2017-07-25 08:27:50 +03:00
|
|
|
from .filter.fields import DjangoFilterConnectionField
|
2018-07-20 02:51:33 +03:00
|
|
|
|
2019-08-01 19:31:18 +03:00
|
|
|
return DjangoFilterConnectionField(
|
|
|
|
_type, required=True, description=description
|
|
|
|
)
|
2017-07-25 08:27:50 +03:00
|
|
|
|
2019-08-01 19:31:18 +03:00
|
|
|
return DjangoConnectionField(_type, required=True, description=description)
|
2016-09-21 07:25:05 +03:00
|
|
|
|
2019-06-25 18:30:30 +03:00
|
|
|
return DjangoListField(
|
|
|
|
_type,
|
|
|
|
required=True, # A Set is always returned, never None.
|
|
|
|
description=description,
|
|
|
|
)
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
return Dynamic(dynamic_type)
|
|
|
|
|
|
|
|
|
|
|
|
@convert_django_field.register(models.OneToOneField)
|
|
|
|
@convert_django_field.register(models.ForeignKey)
|
|
|
|
def convert_field_to_djangomodel(field, registry=None):
|
2017-08-14 16:01:04 +03:00
|
|
|
model = field.related_model
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
def dynamic_type():
|
|
|
|
_type = registry.get_type_for_model(model)
|
|
|
|
if not _type:
|
|
|
|
return
|
|
|
|
|
2022-09-23 11:45:02 +03:00
|
|
|
class CustomField(Field):
|
|
|
|
def wrap_resolve(self, parent_resolver):
|
|
|
|
"""
|
2022-10-17 17:57:24 +03:00
|
|
|
Implements a custom resolver which go through the `get_node` method to ensure that
|
2022-09-23 11:45:02 +03:00
|
|
|
it goes through the `get_queryset` method of the DjangoObjectType.
|
|
|
|
"""
|
|
|
|
resolver = super().wrap_resolve(parent_resolver)
|
|
|
|
|
|
|
|
def custom_resolver(root, info, **args):
|
|
|
|
fk_obj = resolver(root, info, **args)
|
2022-10-17 17:57:24 +03:00
|
|
|
if not isinstance(fk_obj, model):
|
|
|
|
# In case the resolver is a custom one that overwrites
|
|
|
|
# the default Django resolver
|
|
|
|
# This happens, for example, when using custom awaitable resolvers.
|
|
|
|
return fk_obj
|
|
|
|
return _type.get_node(info, fk_obj.pk)
|
2022-09-23 11:45:02 +03:00
|
|
|
|
|
|
|
return custom_resolver
|
|
|
|
|
|
|
|
return CustomField(
|
2020-06-06 19:08:10 +03:00
|
|
|
_type,
|
|
|
|
description=get_django_field_description(field),
|
|
|
|
required=not field.null,
|
|
|
|
)
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
return Dynamic(dynamic_type)
|
|
|
|
|
|
|
|
|
|
|
|
@convert_django_field.register(ArrayField)
|
|
|
|
def convert_postgres_array_to_list(field, registry=None):
|
2020-06-10 19:52:45 +03:00
|
|
|
inner_type = convert_django_field(field.base_field)
|
|
|
|
if not isinstance(inner_type, (List, NonNull)):
|
|
|
|
inner_type = (
|
|
|
|
NonNull(type(inner_type))
|
|
|
|
if inner_type.kwargs["required"]
|
|
|
|
else type(inner_type)
|
|
|
|
)
|
2020-06-06 19:08:10 +03:00
|
|
|
return List(
|
2020-06-27 13:05:56 +03:00
|
|
|
inner_type,
|
2020-06-06 19:08:10 +03:00
|
|
|
description=get_django_field_description(field),
|
|
|
|
required=not field.null,
|
|
|
|
)
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
|
|
|
|
@convert_django_field.register(HStoreField)
|
2020-08-07 12:22:15 +03:00
|
|
|
@convert_django_field.register(PGJSONField)
|
2016-09-18 02:29:00 +03:00
|
|
|
@convert_django_field.register(JSONField)
|
2020-08-07 12:22:15 +03:00
|
|
|
def convert_pg_and_json_field_to_string(field, registry=None):
|
2020-06-06 19:08:10 +03:00
|
|
|
return JSONString(
|
|
|
|
description=get_django_field_description(field), required=not field.null
|
|
|
|
)
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
|
|
|
|
@convert_django_field.register(RangeField)
|
2019-09-22 23:10:21 +03:00
|
|
|
def convert_postgres_range_to_string(field, registry=None):
|
2016-09-18 02:29:00 +03:00
|
|
|
inner_type = convert_django_field(field.base_field)
|
|
|
|
if not isinstance(inner_type, (List, NonNull)):
|
2020-06-10 19:52:45 +03:00
|
|
|
inner_type = (
|
|
|
|
NonNull(type(inner_type))
|
|
|
|
if inner_type.kwargs["required"]
|
|
|
|
else type(inner_type)
|
|
|
|
)
|
2020-06-06 19:08:10 +03:00
|
|
|
return List(
|
|
|
|
inner_type,
|
|
|
|
description=get_django_field_description(field),
|
|
|
|
required=not field.null,
|
|
|
|
)
|
2020-05-09 14:13:47 +03:00
|
|
|
|
|
|
|
|
|
|
|
# Register Django lazy()-wrapped values as GraphQL description/help_text.
|
|
|
|
# This is needed for using lazy translations, see https://github.com/graphql-python/graphql-core-next/issues/58.
|
|
|
|
register_description(Promise)
|