graphene-django/graphene_django/types.py

196 lines
6.1 KiB
Python
Raw Normal View History

from collections import OrderedDict
import six
2019-03-10 00:39:04 +03:00
from django.db.models import Model
from django.utils.functional import SimpleLazyObject
import graphene
from graphene import Field
from graphene.relay import Connection, Node
from graphene.types.objecttype import ObjectType, ObjectTypeOptions
from graphene.types.utils import yank_fields_from_attrs
from .converter import convert_django_field_with_choices
from .registry import Registry, get_global_registry
from .settings import graphene_settings
from .utils import (
DJANGO_FILTER_INSTALLED,
camelize,
get_model_fields,
is_valid_django_model,
)
2019-03-10 00:39:04 +03:00
if six.PY3:
from typing import Type
def construct_fields(
model, registry, only_fields, exclude_fields, convert_choices_to_enum
):
_model_fields = get_model_fields(model)
fields = OrderedDict()
for name, field in _model_fields:
is_not_in_only = only_fields and name not in only_fields
# is_already_created = name in options.fields
2017-07-25 09:42:40 +03:00
is_excluded = name in exclude_fields # or is_already_created
2016-11-10 10:56:14 +03:00
# https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.ForeignKey.related_query_name
2018-07-20 02:51:33 +03:00
is_no_backref = str(name).endswith("+")
2016-11-10 10:56:14 +03:00
if is_not_in_only or is_excluded or is_no_backref:
# We skip this field if we specify only_fields and is not
2016-11-10 10:56:14 +03:00
# in there. Or when we exclude this field in exclude_fields.
# Or when there is no back reference.
continue
_convert_choices_to_enum = convert_choices_to_enum
if not isinstance(_convert_choices_to_enum, bool):
# then `convert_choices_to_enum` is a list of field names to convert
if name in _convert_choices_to_enum:
_convert_choices_to_enum = True
else:
_convert_choices_to_enum = False
converted = convert_django_field_with_choices(
field, registry, convert_choices_to_enum=_convert_choices_to_enum
)
fields[name] = converted
return fields
class DjangoObjectTypeOptions(ObjectTypeOptions):
model = None # type: Model
registry = None # type: Registry
connection = None # type: Type[Connection]
filter_fields = ()
filterset_class = None
class DjangoObjectType(ObjectType):
@classmethod
2018-07-20 02:51:33 +03:00
def __init_subclass_with_meta__(
cls,
model=None,
registry=None,
skip_registry=False,
only_fields=(),
exclude_fields=(),
filter_fields=None,
filterset_class=None,
2018-07-20 02:51:33 +03:00
connection=None,
connection_class=None,
use_connection=None,
interfaces=(),
convert_choices_to_enum=True,
2018-07-20 02:51:33 +03:00
_meta=None,
**options
):
assert is_valid_django_model(model), (
'You need to pass a valid Django Model in {}.Meta, received "{}".'
).format(cls.__name__, model)
if not registry:
registry = get_global_registry()
assert isinstance(registry, Registry), (
2018-07-20 02:51:33 +03:00
"The attribute registry in {} needs to be an instance of "
'Registry, received "{}".'
).format(cls.__name__, registry)
if filter_fields and filterset_class:
raise Exception("Can't set both filter_fields and filterset_class")
2019-03-25 17:03:54 +03:00
if not DJANGO_FILTER_INSTALLED and (filter_fields or filterset_class):
raise Exception(
(
"Can only set filter_fields or filterset_class if "
"Django-Filter is installed"
)
)
django_fields = yank_fields_from_attrs(
construct_fields(
model, registry, only_fields, exclude_fields, convert_choices_to_enum
),
_as=Field,
)
if use_connection is None and interfaces:
2018-07-20 02:51:33 +03:00
use_connection = any(
(issubclass(interface, Node) for interface in interfaces)
)
if use_connection and not connection:
# We create the connection automatically
if not connection_class:
connection_class = Connection
connection = connection_class.create_type(
2018-07-20 02:51:33 +03:00
"{}Connection".format(cls.__name__), node=cls
)
if connection is not None:
2017-07-25 09:42:40 +03:00
assert issubclass(connection, Connection), (
"The connection must be a Connection. Received {}"
).format(connection.__name__)
if not _meta:
_meta = DjangoObjectTypeOptions(cls)
_meta.model = model
_meta.registry = registry
_meta.filter_fields = filter_fields
_meta.filterset_class = filterset_class
_meta.fields = django_fields
_meta.connection = connection
2018-07-20 02:51:33 +03:00
super(DjangoObjectType, cls).__init_subclass_with_meta__(
_meta=_meta, interfaces=interfaces, **options
)
2017-07-25 09:42:40 +03:00
if not skip_registry:
registry.register(cls)
2017-07-28 19:43:27 +03:00
def resolve_id(self, info):
return self.pk
@classmethod
2017-07-28 19:43:27 +03:00
def is_type_of(cls, root, info):
if isinstance(root, SimpleLazyObject):
root._setup()
root = root._wrapped
if isinstance(root, cls):
return True
if not is_valid_django_model(type(root)):
2018-07-20 02:51:33 +03:00
raise Exception(('Received incompatible instance "{}".').format(root))
if cls._meta.model._meta.proxy:
model = root._meta.model
else:
model = root._meta.model._meta.concrete_model
2019-03-27 07:24:13 +03:00
return model == cls._meta.model
@classmethod
def get_queryset(cls, queryset, info):
return queryset
@classmethod
2017-07-28 19:43:27 +03:00
def get_node(cls, info, id):
queryset = cls.get_queryset(cls._meta.model.objects, info)
try:
return queryset.get(pk=id)
except cls._meta.model.DoesNotExist:
return None
class ErrorType(ObjectType):
field = graphene.String(required=True)
messages = graphene.List(graphene.NonNull(graphene.String), required=True)
@classmethod
def from_errors(cls, errors):
data = camelize(errors) if graphene_settings.CAMELCASE_ERRORS else errors
return [cls(field=key, messages=value) for key, value in data.items()]