mirror of
https://github.com/graphql-python/graphene-django.git
synced 2025-07-14 02:02:20 +03:00
added filtering and parent node support
This commit is contained in:
parent
fca7fa647e
commit
15a6744aea
|
@ -97,7 +97,7 @@ class DjangoConnectionField(ConnectionField):
|
||||||
return connection
|
return connection
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def connection_resolver(
|
def connection_resolver_original(
|
||||||
cls,
|
cls,
|
||||||
resolver,
|
resolver,
|
||||||
connection,
|
connection,
|
||||||
|
@ -137,6 +137,49 @@ class DjangoConnectionField(ConnectionField):
|
||||||
|
|
||||||
return on_resolve(iterable)
|
return on_resolve(iterable)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def connection_resolver(
|
||||||
|
cls,
|
||||||
|
resolver,
|
||||||
|
connection,
|
||||||
|
default_manager,
|
||||||
|
max_limit,
|
||||||
|
enforce_first_or_last,
|
||||||
|
root,
|
||||||
|
info,
|
||||||
|
**args
|
||||||
|
):
|
||||||
|
_parent = args.get('know_parent', False)
|
||||||
|
|
||||||
|
if not _parent:
|
||||||
|
if hasattr(info.parent_type._meta, 'know_parent_fields'):
|
||||||
|
options = info.parent_type._meta.know_parent_fields
|
||||||
|
assert isinstance(options, (list, tuple)), \
|
||||||
|
"know_parent_fields should be list or tuple"
|
||||||
|
_parent = info.field_name in options
|
||||||
|
|
||||||
|
def new_resolver(root, info, **kwargs):
|
||||||
|
qs = resolver(root, info, **kwargs)
|
||||||
|
if qs is None:
|
||||||
|
qs = default_manager.filter()
|
||||||
|
if _parent and root is not None:
|
||||||
|
instances = []
|
||||||
|
for instance in qs:
|
||||||
|
setattr(instance, '_parent', root)
|
||||||
|
instances.append(instance)
|
||||||
|
return instances
|
||||||
|
return qs
|
||||||
|
|
||||||
|
return cls.connection_resolver_original(
|
||||||
|
new_resolver,
|
||||||
|
connection,
|
||||||
|
default_manager,
|
||||||
|
max_limit,
|
||||||
|
enforce_first_or_last,
|
||||||
|
root,
|
||||||
|
info,
|
||||||
|
**args)
|
||||||
|
|
||||||
def get_resolver(self, parent_resolver):
|
def get_resolver(self, parent_resolver):
|
||||||
return partial(
|
return partial(
|
||||||
self.connection_resolver,
|
self.connection_resolver,
|
||||||
|
|
|
@ -3,7 +3,8 @@ from functools import partial
|
||||||
|
|
||||||
from graphene.types.argument import to_arguments
|
from graphene.types.argument import to_arguments
|
||||||
from ..fields import DjangoConnectionField
|
from ..fields import DjangoConnectionField
|
||||||
from .utils import get_filtering_args_from_filterset, get_filterset_class
|
from .utils import get_filterset_class, get_filtering_args_from_filterset, \
|
||||||
|
make_qs
|
||||||
|
|
||||||
|
|
||||||
class DjangoFilterConnectionField(DjangoConnectionField):
|
class DjangoFilterConnectionField(DjangoConnectionField):
|
||||||
|
@ -24,18 +25,15 @@ class DjangoFilterConnectionField(DjangoConnectionField):
|
||||||
self._base_args = None
|
self._base_args = None
|
||||||
super(DjangoFilterConnectionField, self).__init__(type, *args, **kwargs)
|
super(DjangoFilterConnectionField, self).__init__(type, *args, **kwargs)
|
||||||
|
|
||||||
@property
|
|
||||||
def args(self):
|
|
||||||
return to_arguments(self._base_args or OrderedDict(), self.filtering_args)
|
|
||||||
|
|
||||||
@args.setter
|
|
||||||
def args(self, args):
|
|
||||||
self._base_args = args
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filterset_class(self):
|
def filterset_class(self):
|
||||||
if not self._filterset_class:
|
if not self._filterset_class:
|
||||||
fields = self._fields or self.node_type._meta.filter_fields
|
if hasattr(self.node_type._meta, 'neomodel_filter_fields'):
|
||||||
|
fields = self.node_type._meta.neomodel_filter_fields
|
||||||
|
elif hasattr(self, '_fields'):
|
||||||
|
fields = self._fields
|
||||||
|
else:
|
||||||
|
fields = []
|
||||||
meta = dict(model=self.model, fields=fields)
|
meta = dict(model=self.model, fields=fields)
|
||||||
if self._extra_filter_meta:
|
if self._extra_filter_meta:
|
||||||
meta.update(self._extra_filter_meta)
|
meta.update(self._extra_filter_meta)
|
||||||
|
@ -46,6 +44,14 @@ class DjangoFilterConnectionField(DjangoConnectionField):
|
||||||
|
|
||||||
return self._filterset_class
|
return self._filterset_class
|
||||||
|
|
||||||
|
@property
|
||||||
|
def args(self):
|
||||||
|
return to_arguments(self._base_args or OrderedDict(), self.filtering_args)
|
||||||
|
|
||||||
|
@args.setter
|
||||||
|
def args(self, args):
|
||||||
|
self._base_args = args
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filtering_args(self):
|
def filtering_args(self):
|
||||||
return get_filtering_args_from_filterset(self.filterset_class, self.node_type)
|
return get_filtering_args_from_filterset(self.filterset_class, self.node_type)
|
||||||
|
@ -74,38 +80,6 @@ class DjangoFilterConnectionField(DjangoConnectionField):
|
||||||
queryset.query.set_limits(low, high)
|
queryset.query.set_limits(low, high)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def connection_resolver(
|
|
||||||
cls,
|
|
||||||
resolver,
|
|
||||||
connection,
|
|
||||||
default_manager,
|
|
||||||
max_limit,
|
|
||||||
enforce_first_or_last,
|
|
||||||
filterset_class,
|
|
||||||
filtering_args,
|
|
||||||
root,
|
|
||||||
info,
|
|
||||||
**args
|
|
||||||
):
|
|
||||||
filter_kwargs = {k: v for k, v in args.items() if k in filtering_args}
|
|
||||||
qs = filterset_class(
|
|
||||||
data=filter_kwargs,
|
|
||||||
queryset=default_manager.get_queryset(),
|
|
||||||
request=info.context,
|
|
||||||
).qs
|
|
||||||
|
|
||||||
return super(DjangoFilterConnectionField, cls).connection_resolver(
|
|
||||||
resolver,
|
|
||||||
connection,
|
|
||||||
qs,
|
|
||||||
max_limit,
|
|
||||||
enforce_first_or_last,
|
|
||||||
root,
|
|
||||||
info,
|
|
||||||
**args
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_resolver(self, parent_resolver):
|
def get_resolver(self, parent_resolver):
|
||||||
return partial(
|
return partial(
|
||||||
self.connection_resolver,
|
self.connection_resolver,
|
||||||
|
@ -117,3 +91,56 @@ class DjangoFilterConnectionField(DjangoConnectionField):
|
||||||
self.filterset_class,
|
self.filterset_class,
|
||||||
self.filtering_args,
|
self.filtering_args,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def connection_resolver(cls,
|
||||||
|
resolver,
|
||||||
|
connection,
|
||||||
|
default_manager,
|
||||||
|
max_limit,
|
||||||
|
enforce_first_or_last,
|
||||||
|
filterset_class,
|
||||||
|
filtering_args,
|
||||||
|
root,
|
||||||
|
info,
|
||||||
|
**args):
|
||||||
|
|
||||||
|
order = args.get('order', None)
|
||||||
|
_parent = args.get('know_parent', False)
|
||||||
|
|
||||||
|
if not _parent:
|
||||||
|
if hasattr(info.parent_type._meta, 'know_parent_fields'):
|
||||||
|
options = info.parent_type._meta.know_parent_fields
|
||||||
|
assert isinstance(options, (list, tuple)), \
|
||||||
|
"know_parent_fields should be list or tuple"
|
||||||
|
_parent = info.field_name in options
|
||||||
|
|
||||||
|
def new_resolver(root, info, **args):
|
||||||
|
filters = dict(filter(lambda x: '__' in x[0], args.items()))
|
||||||
|
qs = resolver(root, info, **args)
|
||||||
|
if qs is None:
|
||||||
|
qs = default_manager.filter()
|
||||||
|
|
||||||
|
if filters:
|
||||||
|
qs = qs.filter(make_qs(filters))
|
||||||
|
|
||||||
|
if order:
|
||||||
|
qs = qs.order_by(order)
|
||||||
|
|
||||||
|
if _parent and root is not None:
|
||||||
|
instances = []
|
||||||
|
for instance in qs:
|
||||||
|
setattr(instance, '_parent', root)
|
||||||
|
instances.append(instance)
|
||||||
|
return instances
|
||||||
|
return qs
|
||||||
|
|
||||||
|
return DjangoConnectionField.connection_resolver(
|
||||||
|
new_resolver,
|
||||||
|
connection,
|
||||||
|
default_manager,
|
||||||
|
max_limit,
|
||||||
|
enforce_first_or_last,
|
||||||
|
root,
|
||||||
|
info,
|
||||||
|
**args)
|
||||||
|
|
|
@ -1,23 +1,8 @@
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from .filterset import custom_filterset_factory, setup_filterset
|
from .filterset import custom_filterset_factory, setup_filterset
|
||||||
|
from functools import reduce
|
||||||
|
from neomodel import Q
|
||||||
def get_filtering_args_from_filterset(filterset_class, type):
|
|
||||||
""" Inspect a FilterSet and produce the arguments to pass to
|
|
||||||
a Graphene Field. These arguments will be available to
|
|
||||||
filter against in the GraphQL
|
|
||||||
"""
|
|
||||||
from ..forms.converter import convert_form_field
|
|
||||||
|
|
||||||
args = {}
|
|
||||||
for name, filter_field in six.iteritems(filterset_class.base_filters):
|
|
||||||
field_type = convert_form_field(filter_field.field).Argument()
|
|
||||||
field_type.description = filter_field.label
|
|
||||||
args[name] = field_type
|
|
||||||
|
|
||||||
return args
|
|
||||||
|
|
||||||
|
|
||||||
def get_filterset_class(filterset_class, **meta):
|
def get_filterset_class(filterset_class, **meta):
|
||||||
"""Get the class to be used as the FilterSet"""
|
"""Get the class to be used as the FilterSet"""
|
||||||
|
@ -26,3 +11,24 @@ def get_filterset_class(filterset_class, **meta):
|
||||||
# return it
|
# return it
|
||||||
return setup_filterset(filterset_class)
|
return setup_filterset(filterset_class)
|
||||||
return custom_filterset_factory(**meta)
|
return custom_filterset_factory(**meta)
|
||||||
|
|
||||||
|
|
||||||
|
def make_qs(filters):
|
||||||
|
for item in filters.items():
|
||||||
|
if '__equal' in item[0]:
|
||||||
|
filters.pop(item[0])
|
||||||
|
filters[item[0].split("__")[0]] = item[1]
|
||||||
|
return reduce(lambda init, nx: init & Q(**{nx[0]: nx[1]}), filters.items(), Q())
|
||||||
|
|
||||||
|
|
||||||
|
def get_filtering_args_from_filterset(filterset_class, type):
|
||||||
|
from ..forms.converter import convert_form_field
|
||||||
|
args = {}
|
||||||
|
fields = type._meta.model.defined_properties()
|
||||||
|
filterset_fields = filterset_class.Meta.fields or []
|
||||||
|
for name in filterset_fields:
|
||||||
|
field_type = convert_form_field(fields[name]).Argument()
|
||||||
|
field_type.description = "filter"
|
||||||
|
for modificator in type._meta.neomodel_filter_fields[name]:
|
||||||
|
args["{}__{}".format(name, modificator)] = field_type
|
||||||
|
return args
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.utils.functional import SimpleLazyObject
|
from django.utils.functional import SimpleLazyObject
|
||||||
from graphene import Field
|
from graphene import Field, Boolean
|
||||||
from graphene.relay import Connection, Node
|
from graphene.relay import Connection, Node
|
||||||
from graphene.types.objecttype import ObjectType, ObjectTypeOptions
|
from graphene.types.objecttype import ObjectType, ObjectTypeOptions
|
||||||
from graphene.types.utils import yank_fields_from_attrs
|
from graphene.types.utils import yank_fields_from_attrs
|
||||||
|
@ -15,6 +15,9 @@ from neomodel import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
KnowParent = dict(know_parent=Boolean(default_value=True))
|
||||||
|
|
||||||
|
|
||||||
def construct_fields(model, registry, only_fields, exclude_fields):
|
def construct_fields(model, registry, only_fields, exclude_fields):
|
||||||
_model_fields = get_model_fields(model)
|
_model_fields = get_model_fields(model)
|
||||||
|
|
||||||
|
@ -55,6 +58,7 @@ class DjangoObjectType(ObjectType):
|
||||||
exclude_fields=(),
|
exclude_fields=(),
|
||||||
filter_fields=None,
|
filter_fields=None,
|
||||||
neomodel_filter_fields=None,
|
neomodel_filter_fields=None,
|
||||||
|
know_parent_fields=None,
|
||||||
connection=None,
|
connection=None,
|
||||||
connection_class=None,
|
connection_class=None,
|
||||||
use_connection=None,
|
use_connection=None,
|
||||||
|
@ -108,6 +112,7 @@ class DjangoObjectType(ObjectType):
|
||||||
_meta.fields = django_fields
|
_meta.fields = django_fields
|
||||||
_meta.connection = connection
|
_meta.connection = connection
|
||||||
_meta.neomodel_filter_fields = neomodel_filter_fields
|
_meta.neomodel_filter_fields = neomodel_filter_fields
|
||||||
|
_meta.know_parent_fields = know_parent_fields
|
||||||
|
|
||||||
super(DjangoObjectType, cls).__init_subclass_with_meta__(
|
super(DjangoObjectType, cls).__init_subclass_with_meta__(
|
||||||
_meta=_meta, interfaces=interfaces, **options
|
_meta=_meta, interfaces=interfaces, **options
|
||||||
|
|
Loading…
Reference in New Issue
Block a user