2016-09-18 02:29:00 +03:00
|
|
|
from collections import OrderedDict
|
|
|
|
|
2019-06-25 11:40:29 +03:00
|
|
|
import six
|
2019-03-10 00:39:04 +03:00
|
|
|
from django.db.models import Model
|
2016-10-16 03:40:12 +03:00
|
|
|
from django.utils.functional import SimpleLazyObject
|
2019-06-25 11:40:29 +03:00
|
|
|
|
2019-03-31 14:01:43 +03:00
|
|
|
import graphene
|
2017-07-25 08:27:26 +03:00
|
|
|
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
|
2016-09-18 03:09:56 +03:00
|
|
|
|
|
|
|
from .converter import convert_django_field_with_choices
|
|
|
|
from .registry import Registry, get_global_registry
|
2019-06-25 11:40:29 +03:00
|
|
|
from .settings import graphene_settings
|
|
|
|
from .utils import (
|
|
|
|
DJANGO_FILTER_INSTALLED,
|
|
|
|
camelize,
|
|
|
|
get_model_fields,
|
|
|
|
is_valid_django_model,
|
|
|
|
)
|
2016-09-18 02:29:00 +03:00
|
|
|
|
2019-03-10 00:39:04 +03:00
|
|
|
if six.PY3:
|
|
|
|
from typing import Type
|
|
|
|
|
|
|
|
|
2019-06-17 20:48:29 +03:00
|
|
|
def construct_fields(
|
|
|
|
model, registry, only_fields, exclude_fields, convert_choices_to_enum
|
|
|
|
):
|
2017-07-25 08:27:26 +03:00
|
|
|
_model_fields = get_model_fields(model)
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
fields = OrderedDict()
|
2016-12-13 16:23:08 +03:00
|
|
|
for name, field in _model_fields:
|
2017-07-25 08:27:26 +03:00
|
|
|
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:
|
2016-09-18 02:29:00 +03:00
|
|
|
# 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.
|
2016-09-18 02:29:00 +03:00
|
|
|
continue
|
2019-06-17 20:48:29 +03:00
|
|
|
|
|
|
|
_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
|
|
|
|
)
|
2016-09-18 02:29:00 +03:00
|
|
|
fields[name] = converted
|
|
|
|
|
|
|
|
return fields
|
|
|
|
|
|
|
|
|
2017-07-25 08:27:26 +03:00
|
|
|
class DjangoObjectTypeOptions(ObjectTypeOptions):
|
|
|
|
model = None # type: Model
|
|
|
|
registry = None # type: Registry
|
|
|
|
connection = None # type: Type[Connection]
|
|
|
|
|
|
|
|
filter_fields = ()
|
2019-03-25 06:42:06 +03:00
|
|
|
filterset_class = None
|
2017-07-25 08:27:26 +03:00
|
|
|
|
|
|
|
|
|
|
|
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,
|
2019-03-25 06:42:06 +03:00
|
|
|
filterset_class=None,
|
2018-07-20 02:51:33 +03:00
|
|
|
connection=None,
|
|
|
|
connection_class=None,
|
|
|
|
use_connection=None,
|
|
|
|
interfaces=(),
|
2019-06-17 20:48:29 +03:00
|
|
|
convert_choices_to_enum=True,
|
2018-07-20 02:51:33 +03:00
|
|
|
_meta=None,
|
|
|
|
**options
|
|
|
|
):
|
2017-07-25 08:27:26 +03:00
|
|
|
assert is_valid_django_model(model), (
|
2016-09-18 02:29:00 +03:00
|
|
|
'You need to pass a valid Django Model in {}.Meta, received "{}".'
|
2017-07-25 08:27:26 +03:00
|
|
|
).format(cls.__name__, model)
|
|
|
|
|
|
|
|
if not registry:
|
|
|
|
registry = get_global_registry()
|
2016-09-18 02:29:00 +03:00
|
|
|
|
2017-07-25 08:27:26 +03:00
|
|
|
assert isinstance(registry, Registry), (
|
2018-07-20 02:51:33 +03:00
|
|
|
"The attribute registry in {} needs to be an instance of "
|
2017-07-25 08:27:26 +03:00
|
|
|
'Registry, received "{}".'
|
|
|
|
).format(cls.__name__, registry)
|
2016-09-18 02:29:00 +03:00
|
|
|
|
2019-03-25 06:42:06 +03:00
|
|
|
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
|
|
|
|
2019-03-25 06:42:06 +03:00
|
|
|
if not DJANGO_FILTER_INSTALLED and (filter_fields or filterset_class):
|
2019-06-11 06:54:30 +03:00
|
|
|
raise Exception(
|
|
|
|
(
|
|
|
|
"Can only set filter_fields or filterset_class if "
|
|
|
|
"Django-Filter is installed"
|
|
|
|
)
|
|
|
|
)
|
2016-09-18 02:29:00 +03:00
|
|
|
|
2017-07-25 08:27:26 +03:00
|
|
|
django_fields = yank_fields_from_attrs(
|
2019-06-17 20:48:29 +03:00
|
|
|
construct_fields(
|
|
|
|
model, registry, only_fields, exclude_fields, convert_choices_to_enum
|
|
|
|
),
|
|
|
|
_as=Field,
|
2016-09-18 02:29:00 +03:00
|
|
|
)
|
|
|
|
|
2017-07-25 08:27:26 +03:00
|
|
|
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)
|
|
|
|
)
|
2017-07-25 08:27:26 +03:00
|
|
|
|
|
|
|
if use_connection and not connection:
|
|
|
|
# We create the connection automatically
|
2017-10-31 00:35:29 +03:00
|
|
|
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
|
|
|
|
)
|
2017-07-25 08:27:26 +03:00
|
|
|
|
|
|
|
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__)
|
2016-09-18 02:29:00 +03:00
|
|
|
|
2018-01-21 22:33:52 +03:00
|
|
|
if not _meta:
|
|
|
|
_meta = DjangoObjectTypeOptions(cls)
|
|
|
|
|
2017-07-25 08:27:26 +03:00
|
|
|
_meta.model = model
|
|
|
|
_meta.registry = registry
|
|
|
|
_meta.filter_fields = filter_fields
|
2019-03-25 06:42:06 +03:00
|
|
|
_meta.filterset_class = filterset_class
|
2017-07-25 08:27:26 +03:00
|
|
|
_meta.fields = django_fields
|
|
|
|
_meta.connection = connection
|
2016-09-18 02:29:00 +03:00
|
|
|
|
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
|
|
|
|
2017-07-25 08:27:26 +03:00
|
|
|
if not skip_registry:
|
|
|
|
registry.register(cls)
|
2016-09-18 03:09:56 +03:00
|
|
|
|
2017-07-28 19:43:27 +03:00
|
|
|
def resolve_id(self, info):
|
2016-10-13 09:52:58 +03:00
|
|
|
return self.pk
|
|
|
|
|
2016-09-18 02:29:00 +03:00
|
|
|
@classmethod
|
2017-07-28 19:43:27 +03:00
|
|
|
def is_type_of(cls, root, info):
|
2016-10-16 03:40:12 +03:00
|
|
|
if isinstance(root, SimpleLazyObject):
|
|
|
|
root._setup()
|
|
|
|
root = root._wrapped
|
2016-09-18 02:29:00 +03:00
|
|
|
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))
|
2018-01-15 06:36:52 +03:00
|
|
|
|
2019-03-27 07:09:25 +03:00
|
|
|
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
|
|
|
|
2016-09-18 02:29:00 +03:00
|
|
|
return model == cls._meta.model
|
|
|
|
|
2019-03-31 14:01:17 +03:00
|
|
|
@classmethod
|
|
|
|
def get_queryset(cls, queryset, info):
|
|
|
|
return queryset
|
|
|
|
|
2016-09-18 02:29:00 +03:00
|
|
|
@classmethod
|
2017-07-28 19:43:27 +03:00
|
|
|
def get_node(cls, info, id):
|
2019-03-31 14:01:17 +03:00
|
|
|
queryset = cls.get_queryset(cls._meta.model.objects, info)
|
2016-09-18 02:29:00 +03:00
|
|
|
try:
|
2019-03-31 14:01:17 +03:00
|
|
|
return queryset.get(pk=id)
|
2016-09-18 02:29:00 +03:00
|
|
|
except cls._meta.model.DoesNotExist:
|
|
|
|
return None
|
2019-03-31 14:01:43 +03:00
|
|
|
|
|
|
|
|
|
|
|
class ErrorType(ObjectType):
|
|
|
|
field = graphene.String(required=True)
|
|
|
|
messages = graphene.List(graphene.NonNull(graphene.String), required=True)
|
2019-06-25 11:40:29 +03:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_errors(cls, errors):
|
|
|
|
data = (
|
|
|
|
camelize(errors)
|
|
|
|
if graphene_settings.DJANGO_GRAPHENE_CAMELCASE_ERRORS
|
|
|
|
else errors
|
|
|
|
)
|
|
|
|
return [ErrorType(field=key, messages=value) for key, value in data.items()]
|