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 } } } }
5 Réponses :
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:
{ 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()
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>
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.
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", )
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}, })
Tout d'abord, vous devez étendre
SampleType
avec un champ de liste - quelque chose commeids = 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?