graphene-django-extras icon indicating copy to clipboard operation
graphene-django-extras copied to clipboard

Custom Resolver for DjangoFilterPaginateListField/DjangoFilterListField/DjangoListObjectField

Open louisdvs opened this issue 5 years ago • 7 comments

Just wondering if it is possible to define custom resolver for the fields exposed by graphene-django-extras.

My project has two Apps (with separate databases) but with relationships across apps.

I would like to use a custom resolver to select related objects where the connection is not defined in the ORM but still have access to pagination and filtering.

I tried to do this as I would in Graphene-Django but the resolver function isn't called when used with any of the fields provided by this library. I tried it with graphene.field and the resolver function is run.

class SiteObjectType(DjangoObjectType):
     class Meta:
        description = "Site model type definition "
        model = models.Site
   
    vehicles = DjangoListObjectField(types.Vehicle)

    def resolve_vehicles(self, info, **kwargs):
        # something like this to select the relevant vehicle objects
        related_vehicles = Vehicles.object.filter(id__in="self.id")
        return related_vehicles

Is this expected behaviour and is there any way around it?

Thanks for the great work on this extension!

louisdvs avatar Jul 30 '18 01:07 louisdvs

I'm having the same issue, trying to filter on a search parameter to then receive paginated results, but as the resolver is skipped - as is my search!

webel avatar Aug 20 '18 09:08 webel

Hi @webel and @louisdvs, thanks for ask and sorry for delay. Right now, in my free time, I'm working on a new version of graphene-django-extras with many new features implemented and most of the issues reported corrected (including this).

eamigo86 avatar Aug 30 '18 12:08 eamigo86

Thanks for the response @eamigo86.

As a temp solution, I've used DjangoFilterPaginateListField for all of my paginated fields. It just means that I have had to add a custom field for TotalCount that is resolved on every child element (not great) but will get me started.

Type:

class SiteType(DjangoObjectType):

    total_count = graphene.Int()

    def resolve_total_count(self, info):
        count = models.Site.objects.count()
        return count

    class Meta:
        description = "Site model type definition "
        model = models.Site
        filter_fields = {
            'id': ['exact', ],
            'name': ['exact', ],
            'description': ['icontains', ],
        }

Query:

site = DjangoObjectField(
        types.SiteType,
        description="")
all_sites = DjangoFilterPaginateListField(
        types.SiteType,
        pagination=LimitOffsetGraphqlPagination(default_limit=25),
        description='paginated list of sites')

louisdvs avatar Sep 06 '18 00:09 louisdvs

Do you plan to implement this someday? Or, if you have an implementation example, I will implement it.

bianchiidentity avatar Dec 29 '18 00:12 bianchiidentity

I ran into the same issue and I solved it by subclassing the field like so

from graphene_django_extras import DjangoFilterListField
from functools import partial
from graphene.types.structures import Structure


class CustomDjangoFilterListField(DjangoFilterListField):
    def __init__(
        self,
        _type,
        fields=None,
        extra_filter_meta=None,
        filterset_class=None,
        *args,
        **kwargs,
    ):
        super().__init__(
            _type,
            fields=None,
            extra_filter_meta=None,
            filterset_class=None,
            *args,
            **kwargs,
        )

    @staticmethod
    def list_resolver(
        manager, filterset_class, filtering_args, constraint_func, root, info, **kwargs
    ):
        queryset = DjangoFilterListField.list_resolver(
            manager, filterset_class, filtering_args, root, info, **kwargs
        )
        if constraint_func:
            return constraint_func(queryset, info)
        else:
            return queryset

    def get_resolver(self, parent_resolver):
        current_type = self.type
        while isinstance(current_type, Structure):
            current_type = current_type.of_type
        try:
            constraint_func = None
            constraint_func = current_type.constraint_func
        except Exception:
            pass
        return partial(
            self.list_resolver,
            current_type._meta.model._default_manager,
            self.filterset_class,
            self.filtering_args,
            constraint_func,
        )

So if you want to use it in a model, you can do so like:

class CodeQuestionTestCaseType(DjangoObjectType):
    class Meta:
        model = CodeQuestionTestCase
        filter_fields = {"input": ("iexact",), "output": ("iexact",)}

    @classmethod
    def constraint_func(self,queryset,info):
    	if info.context.user.user_type == "STUDENT":
    		return None
    	return queryset

I don't really know if it's the best method but this let me do a lot of checks before returning data to the user

dxmxnlord avatar Mar 22 '20 19:03 dxmxnlord

Any News ?

siyanew avatar May 24 '20 14:05 siyanew

Hi @webel and @louisdvs, thanks for ask and sorry for delay. Right now, in my free time, I'm working on a new version of graphene-django-extras with many new features implemented and most of the issues reported corrected (including this).

@eamigo86 You said this nearly 3 years ago, have you been able to make much progress. Would love to get updates on this! Thanks!

ryarasi avatar May 27 '21 10:05 ryarasi