added filtering and parent node support

This commit is contained in:
Mardanov Timur Rustemovich 2018-11-29 16:24:36 +03:00
parent fca7fa647e
commit 15a6744aea
4 changed files with 142 additions and 61 deletions

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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