mirror of
https://github.com/graphql-python/graphene-django.git
synced 2025-12-08 02:34:16 +03:00
refine_queryset lets DjangoObjectType define exactly how the queryset should be refined before being returned to the user. For instance, some objects could be filtered out according to a predicate, or some fields could be prefetched depending on what the initial query requested. get_connection_parameters lets DjangoObjectType define the name and value of parameters that can be passed to any DjangoConnectionField that uses them. Both these additions come as building blocks to allow custom refinements and filters without having to go through django-filter. Moreover, such filters can also be further optimized than previously allowed, as the GraphQL info object is available in refine_queryset.
144 lines
4.7 KiB
Python
144 lines
4.7 KiB
Python
from collections import OrderedDict
|
|
|
|
from django.utils.functional import SimpleLazyObject
|
|
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 .utils import DJANGO_FILTER_INSTALLED, get_model_fields, is_valid_django_model
|
|
|
|
|
|
def construct_fields(model, registry, only_fields, exclude_fields):
|
|
_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
|
|
is_excluded = name in exclude_fields # or is_already_created
|
|
# https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.ForeignKey.related_query_name
|
|
is_no_backref = str(name).endswith("+")
|
|
if is_not_in_only or is_excluded or is_no_backref:
|
|
# We skip this field if we specify only_fields and is not
|
|
# in there. Or when we exclude this field in exclude_fields.
|
|
# Or when there is no back reference.
|
|
continue
|
|
converted = convert_django_field_with_choices(field, registry)
|
|
fields[name] = converted
|
|
|
|
return fields
|
|
|
|
|
|
class DjangoObjectTypeOptions(ObjectTypeOptions):
|
|
model = None # type: Model
|
|
registry = None # type: Registry
|
|
connection = None # type: Type[Connection]
|
|
|
|
filter_fields = ()
|
|
|
|
|
|
class DjangoObjectType(ObjectType):
|
|
@classmethod
|
|
def __init_subclass_with_meta__(
|
|
cls,
|
|
model=None,
|
|
registry=None,
|
|
skip_registry=False,
|
|
only_fields=(),
|
|
exclude_fields=(),
|
|
filter_fields=None,
|
|
connection=None,
|
|
connection_class=None,
|
|
use_connection=None,
|
|
interfaces=(),
|
|
_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), (
|
|
"The attribute registry in {} needs to be an instance of "
|
|
'Registry, received "{}".'
|
|
).format(cls.__name__, registry)
|
|
|
|
if not DJANGO_FILTER_INSTALLED and filter_fields:
|
|
raise Exception("Can only set filter_fields if Django-Filter is installed")
|
|
|
|
django_fields = yank_fields_from_attrs(
|
|
construct_fields(model, registry, only_fields, exclude_fields), _as=Field
|
|
)
|
|
|
|
if use_connection is None and interfaces:
|
|
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(
|
|
"{}Connection".format(cls.__name__), node=cls
|
|
)
|
|
|
|
if connection is not None:
|
|
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.fields = django_fields
|
|
_meta.connection = connection
|
|
|
|
super(DjangoObjectType, cls).__init_subclass_with_meta__(
|
|
_meta=_meta, interfaces=interfaces, **options
|
|
)
|
|
|
|
if not skip_registry:
|
|
registry.register(cls)
|
|
|
|
@classmethod
|
|
def refine_queryset(cls, qs, info, **kwargs):
|
|
return qs
|
|
|
|
@classmethod
|
|
def get_connection_parameters(cls):
|
|
return {}
|
|
|
|
def resolve_id(self, info):
|
|
return self.pk
|
|
|
|
@classmethod
|
|
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)):
|
|
raise Exception(('Received incompatible instance "{}".').format(root))
|
|
|
|
model = root._meta.model._meta.concrete_model
|
|
return model == cls._meta.model
|
|
|
|
@classmethod
|
|
def get_node(cls, info, id):
|
|
try:
|
|
return cls.refine_queryset(cls._meta.model.objects, info).get(pk=id)
|
|
except cls._meta.model.DoesNotExist:
|
|
return None
|