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

Custom argument on DjangoObjectType

Open msukmanowsky opened this issue 4 years ago • 5 comments

Hi all 👋🏻,

I've seen #333 but still have a question about how to add a custom argument that'd enable a more complex filter on a DjangoObjectType.

Using graphene-django==2.8.0.

class ThingNode(DjangoObjectType):
    class Meta:
        model = Thing
        interfaces = (graphene.relay.Node,)

    @classmethod
    def get_queryset(cls, queryset, info):
        # How do I access my_custom_param here to enable a custom filter?
        if my_custom_param:
            # Heavily modify the queryset here with filter + exclude
            queryset.filter().filter().exclude()
        else:
            # Something else
            queryset.filter().filter()
        return queryset


class Query(graphene.ObjectType):

    things = DjangoFilterConnectionField(ThingNode, my_custom_param=graphene.Boolean())

msukmanowsky avatar Jan 17 '20 04:01 msukmanowsky

Did find a way to support this but it feels like I'm dipping into internals that I shouldn't be?

class ThingNode(DjangoObjectType):
    class Meta:
        model = Thing
        interfaces = (graphene.relay.Node,)

    @classmethod
    def get_queryset(cls, queryset, info):
        field_asts = info.field_asts
        if field_asts:
            args = field_asts[0].arguments
            my_custom_param = [a for a in args if a.name.value == "myCustomParam"]
            my_custom_param = my_custom_param[0].value.value if my_custom_param else False
        else:
            my_custom_param = False

        if my_custom_param:
            # Heavily modify the queryset here with filter + exclude
            queryset.filter().filter().exclude()
        else:
            # Something else
            queryset.filter().filter()
        return queryset


class Query(graphene.ObjectType):

    things = DjangoFilterConnectionField(ThingNode, my_custom_param=graphene.Boolean())

msukmanowsky avatar Jan 17 '20 14:01 msukmanowsky

Sorry to clarify something, I'm aware I can overwrite Query.resolve_things to handle my_custom_param, but then I lose the pagination helpers that DjangoFilterConnectionField provides.

Is there a middle ground there that lets you override the resolver and still let DjangoFilterConnectionField handle pagination?

msukmanowsky avatar Jan 19 '20 22:01 msukmanowsky

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar May 19 '20 00:05 stale[bot]

Is this still relevant? If so, what is blocking it? Is there anything you can do to help move it forward?

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

stale[bot] avatar Aug 27 '20 00:08 stale[bot]

@msukmanowsky, Thank you! Your answer is the solution i was looking for.

pythonkdzencode avatar Jun 04 '21 13:06 pythonkdzencode

Is there a middle ground there that lets you override the resolver and still let DjangoFilterConnectionField handle pagination?

Did you ever come up with a solution along these lines, @msukmanowsky?

ethagnawl avatar May 31 '23 02:05 ethagnawl

Unfortunately not, @ethagnawl.

msukmanowsky avatar May 31 '23 03:05 msukmanowsky

Thanks for the prompt response, @msukmanowsky!

ethagnawl avatar May 31 '23 03:05 ethagnawl

I'm curious to know if any of the maintainers or anyone else in the community has thoughts on how best to approach this. (I'm also happy to move this conversation to a more appropriate venue!) I'm still coming up to speed on Graphene and GraphQL but, unless I'm missing something, it seems like this would be a very common issue.

ethagnawl avatar Jun 01 '23 23:06 ethagnawl

Do the examples from the docs on filtering here https://docs.graphene-python.org/projects/django/en/latest/filtering/#custom-filtersets help @msukmanowsky and @ethagnawl? It seems like the original question could be solved with a custom django_filters.FilterSet class that includes a new field for my_custom_param, on which you filter your queryset. The docs from django-filter here on how to define your own filtering method for a FilterSet may also be handy.

Let me know if I'm misunderstanding, or there's some scenario that's not supported by that.

sjdemartini avatar Jun 01 '23 23:06 sjdemartini

Thank you for following up and providing these links, @sjdemartini!

I need to read more into the django-filter options. This is my first time using that library and, based on my initial round of research, I was under the impression that this would not be an option in my case because the values I'd need to filter (VectorField) on don't have existing Filter subclasses or backing Django "form fields", which prevents me from easily creating my own. I actually started a "discussion" in the django-filter repository about this yesterday.

To take a step back, though, what I'd ideally be able to do in accordance with what's being discussed above is something like:

def resolve_all_items(parent, info, **kwargs):
    if kwargs.get("embedding_distance") and kwargs.get("embedding"):
        queryset = Item.alias(
            distance=CosineDistance("embedding", kwargs.pop("embedding"))
        ).filter(distance__lt=kwargs.pop("embedding_distance"))
    else:
        queryset = Item.objects.none()

    # tap into the existing, default behavior which handles filtering of all other fields, pagination, etc.
    return default_resolver(parent, info, queryset, kwargs)       

ethagnawl avatar Jun 02 '23 14:06 ethagnawl

@ethagnawl I'm not familiar with pgvector/VectorField or the nuances of your specific use-case, but you can add filters to a FilterSet that do not correspond to a Django field or form. In graphene-django v3 there's also a handy TypedFilter utility to specify the Graphene Type for your FilterSet filters (see the docs here; they haven't been published properly to the official docs site yet).

For instance, here's a random example of using TypedFilter:

from graphene_django.filter import TypedFilter

class MyCustomFilterSet(django_filters.FilterSet):
    my_custom_param = TypedFilter(graphene.Int, method="my_custom_param_filter")

    class Meta:
        model = MyModel
        fields = "__all__"

    def my_custom_param_filter(self, queryset, name, value: int):
        if my_custom_param > 5:
            return queryset
        return queryset.filter(something_else=value)

It seems like you could add a filter field of graphene.String type (or graphene.List perhaps?) and do what you need to. The specifics of doing things with VectorField seem distinct from the original issue posted, so I'm thinking this issue can perhaps be closed.

sjdemartini avatar Jun 02 '23 15:06 sjdemartini

This is very helpful and, on its face, seems like it should work. Thank you, @sjdemartini!

The specifics of doing things with VectorField seem distinct from the original issue posted, so I'm thinking this issue can perhaps be closed.

Agreed as far as I'm concerned. Apologies for hijacking the thread.

ethagnawl avatar Jun 02 '23 15:06 ethagnawl

All good, thanks for discussing! I'll close this out, and if there's something outstanding from the original post that seems unaddressed, feel free to follow up @msukmanowsky.

sjdemartini avatar Jun 02 '23 15:06 sjdemartini