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
@classmethod
def connection_resolver(
def connection_resolver_original(
cls,
resolver,
connection,
@ -137,6 +137,49 @@ class DjangoConnectionField(ConnectionField):
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):
return partial(
self.connection_resolver,

View File

@ -3,7 +3,8 @@ from functools import partial
from graphene.types.argument import to_arguments
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):
@ -24,18 +25,15 @@ class DjangoFilterConnectionField(DjangoConnectionField):
self._base_args = None
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
def filterset_class(self):
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)
if self._extra_filter_meta:
meta.update(self._extra_filter_meta)
@ -46,6 +44,14 @@ class DjangoFilterConnectionField(DjangoConnectionField):
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
def filtering_args(self):
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)
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):
return partial(
self.connection_resolver,
@ -117,3 +91,56 @@ class DjangoFilterConnectionField(DjangoConnectionField):
self.filterset_class,
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
from .filterset import custom_filterset_factory, setup_filterset
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
from functools import reduce
from neomodel import Q
def get_filterset_class(filterset_class, **meta):
"""Get the class to be used as the FilterSet"""
@ -26,3 +11,24 @@ def get_filterset_class(filterset_class, **meta):
# return it
return setup_filterset(filterset_class)
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 django.utils.functional import SimpleLazyObject
from graphene import Field
from graphene import Field, Boolean
from graphene.relay import Connection, Node
from graphene.types.objecttype import ObjectType, ObjectTypeOptions
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):
_model_fields = get_model_fields(model)
@ -55,6 +58,7 @@ class DjangoObjectType(ObjectType):
exclude_fields=(),
filter_fields=None,
neomodel_filter_fields=None,
know_parent_fields=None,
connection=None,
connection_class=None,
use_connection=None,
@ -108,6 +112,7 @@ class DjangoObjectType(ObjectType):
_meta.fields = django_fields
_meta.connection = connection
_meta.neomodel_filter_fields = neomodel_filter_fields
_meta.know_parent_fields = know_parent_fields
super(DjangoObjectType, cls).__init_subclass_with_meta__(
_meta=_meta, interfaces=interfaces, **options