6
votes

Comment filtrer une requête par une liste d'id dans GraphQL en utilisant graphene-django?

J'essaye d'effectuer une requête GraphQL en utilisant Django et Graphene. Pour interroger un seul objet en utilisant l'identifiant, j'ai fait ce qui suit:

class SampleType(DjangoObjectType):
  class Meta:
    model = Sample
    filter_fields = {
      'id': ['exact', 'in'],
     }
     interfaces = (graphene.relay.Node,)

class Query(object):
  samples = DjangoFilterConnectionField(SampleType)

  def resolve_sample_sets(self, info, **kwargs):
    return Sample.objects.all()

Et cela fonctionne très bien. Un problème survient lorsque j'essaie d'interroger avec plus d'un identifiant, comme suit:

argument should be a bytes-like object or ASCII string, not 'list'

Dans ce dernier cas, j'ai eu l'erreur suivante:

{
  samples(id_In:"U2FtcGxlU2V0VHlwZToxMjYw, U2FtcGxlU2V0VHlwZToxMjYx") {
    edges {
      nodes {
        name
      }
    }
  }
} 

Et ceci est une esquisse de la façon dont le type et la requête ont été définis dans django-graphene

{
  samples(id:"U2FtcGxlU2V0VHlwZToxMjYw") {
    edges {
      nodes {
        name
      }
    }
  }
}


2 commentaires

Tout d'abord, vous devez étendre SampleType avec un champ de liste - quelque chose comme ids = graphene.List (graphene.ID ())


@MarkChackerian pourriez-vous élaborer à ce sujet et fournir une solution? J'ai rencontré le même problème et je ne vois pas en quoi l'ajout du champ d'identifiants change quoi que ce soit. Il semble que graphene-django a des problèmes de validation d'entrée sur le terrain?


5 Réponses :


1
votes

J'ai également eu du mal à implémenter le filtre «in» - il semble être mal implémenté dans graphene-django en ce moment et ne fonctionne pas comme prévu. Voici les étapes pour le faire fonctionner:

  1. Supprimez le filtre "in" de vos filter_fields
  2. Ajoutez une valeur d'entrée à votre DjangoFilterConnectionField intitulée 'id__in' et faites-en une liste d'identifiants
  3. Renommez votre résolveur pour qu'il corresponde au champ "échantillons".
  4. Gérez le filtrage par 'id__in' dans votre résolveur pour le champ. Pour vous, cela ressemblera à ceci:
{
  samples(id_In: ["U2FtcGxlU2V0VHlwZToxMjYw", "U2FtcGxlU2V0VHlwZToxMjYx"]) {
    edges {
      nodes {
        name
      }
    }
  }
} 

Cela vous permettra d'appeler le filtre avec l'API suivante. Notez l'utilisation d'un tableau réel dans la signature (je pense que c'est une meilleure API que d'envoyer une chaîne de valeurs séparées par des virgules). Cette solution vous permet toujours d'ajouter d'autres filtres à la requête et ils s'enchaîneront correctement.

from base64 import b64decode

def get_pk_from_node_id(node_id: str):
    """Gets pk from node_id"""
    model_with_pk = b64decode(node_id).decode('utf-8')
    model_name, pk = model_with_pk.split(":")
    return pk


class SampleType(DjangoObjectType):
    class Meta:
        model = Sample
        filter_fields = {
            'id': ['exact'],
         }
        interfaces = (graphene.relay.Node,)


class Query(object):

    samples = DjangoFilterConnectionField(SampleType, id__in=graphene.List(graphene.ID))

    def resolve_samples(self, info, **kwargs):
        # filter_field for 'in' seems to not work, this hack works
        id__in = kwargs.get('id__in')
        if id__in:
            node_ids = kwargs.pop('id__in')
            pk_list = [get_pk_from_node_id(node_id) for node_id in node_ids]
            return Sample._default_manager.filter(id__in=pk_list)
        return Sample._default_manager.all()


0 commentaires

4
votes

GlobalIDMultipleChoiceFilter de django-graphene résout en quelque sorte ce problème, si vous mettez "in" dans le nom du champ. Vous pouvez créer des filtres comme

{
  books(author: ["<GlobalID1>", "<GlobalID2>"]) {
    edges {
      nodes {
        name
      }
    }
  }
}

et les utiliser par

from django_filters import FilterSet
from graphene_django.filter import GlobalIDMultipleChoiceFilter

class BookFilter(FilterSet):
    author = GlobalIDMultipleChoiceFilter()

Toujours pas parfait, mais le besoin de code personnalisé est minimisé. p>


0 commentaires

1
votes

Vous pouvez facilement utiliser un filtre, mettez simplement ceci avec vos nœuds.

class Query(graphene.ObjectType):
    all_report_files = DjangoFilterConnectionField(ReportFileNode, filterset_class=ReportFileFilter)

Ensuite, dans votre requête, utilisez simplement -

class ReportFileFilter(FilterSet):
    id = GlobalIDMultipleChoiceFilter()

Ceci est pour l'implémentation relais de graphql django.


0 commentaires

1
votes

Aucune des réponses existantes ne semblait fonctionner pour moi car elles ont été présentées, mais avec quelques légers changements, j'ai réussi à résoudre mon problème comme suit:

Vous pouvez créer une classe FilterSet personnalisée pour votre type d'objet et filtrez le champ à l'aide du GlobalIDMultipleChoiceFilter . par exemple:

from graphene import relay
from graphene_django import DjangoObjectType

class SampleType(DjangoObjectType):
    class Meta:
        model = Sample
        filterset_class = SampleFilter
        interfaces = (relay.Node,)

Quelque chose que j'ai croisé, c'est que vous ne pouvez pas avoir de filter_fields défini avec cette approche . Au lieu de cela, vous ne devez compter que sur la classe personnalisée FilterSet exclusivement, ce qui donne à votre type d’objet l’effet suivant:

from django_filters import FilterSet
from graphene_django.filter import GlobalIDFilter, GlobalIDMultipleChoiceFilter

class SampleFilter(FilterSet):
    id = GlobalIDFilter()
    id__in = GlobalIDMultipleChoiceFilter(field_name="id")

    class Meta:
        model = Sample
        fields = (
            "id_in",
            "id",
        )


0 commentaires

0
votes

Une autre façon est de dire au filtre Relay de graphene_django de s'occuper également d'une liste. Ce filtre est enregistré dans un mixin dans graphene_django et appliqué à tout filtre que vous définissez.

Voici donc ma solution:

from graphene_django.filter.filterset import (
    GlobalIDFilter,
    GrapheneFilterSetMixin,
)
from graphql_relay import from_global_id


class CustomGlobalIDFilter(GlobalIDFilter):
    """Allow __in lookup for IDs"""
    def filter(self, qs, value):
        if isinstance(value, list):
            value_lst = [from_global_id(v)[1] for v in value]
            return super(GlobalIDFilter, self).filter(qs, value_lst)
        else:
            return super().filter(qs, value)

# Fix the mixin defaults
GrapheneFilterSetMixin.FILTER_DEFAULTS.update({
    AutoField: {"filter_class": CustomGlobalIDFilter},
    OneToOneField: {"filter_class": CustomGlobalIDFilter},
    ForeignKey: {"filter_class": CustomGlobalIDFilter},
})


0 commentaires