在Django Graphene Relay中启用基于PK的过滤,同时保留全局ID

时间:2019-01-23 13:43:06

标签: python django graphql relay graphene-python

问题

我在GraphQL服务器上将django-graphene与Relay一起使用。该实现在graphene.relay.Node类中强加了一个Global ID requirement,该类覆盖和隐藏Django的ID字段

结果,我可以这样查询:

{
    allBatches(id:"QmF0Y2hOb2RlOjE=") {
    edges {
      node {
        id
        pk
      }
    }
  }
}

并获得此回复:

{
  "data": {
    "allBatches": {
      "edges": [
        {
          "node": {
            "id": "QmF0Y2hOb2RlOjE=",
            "pk": 1
          }
        }
      ]
    }
  }
}

但是,我失去的是能够按对象本身的原始ID(或PK)字段进行过滤的功能:

{
    allBatches(id:1) {
    edges {
      node {
        id
        pk
      }
    }
  }
}

事实上,我根本无法通过ID过滤对象。 我可以想到两种可能的解决方法: 1.防止django-graphene-relay劫持和遮盖id字段,也许强迫它使用其他字段名称,例如gid 2.找到一种方法来将pk包含为一个特殊字段,该字段既可以作为属性也可以作为过滤器使用

解决方案1 ​​

我在1上没有取得任何进展,因为似乎django-graphene(也许是中继标准)强加了对该字段称为id的限制。我看到id已在多个地方用作魔术字符串,而且似乎没有改变字段名称的标准方法。

解决方案2

在2上,我可以使该属性与Mixin一起使用,如下所示:

class PKMixin(object):
    pk = graphene.Field(type=graphene.Int, source='pk')

但是,由于django-filter没有声明字段FilterSet并因以下错误而中断,因此我无法通过pk获得过滤功能

  

'Meta.fields'包含未在此FilterSet上定义的字段:   pk

于2更新

我尝试了以下操作:

class PKFilteringNode(Node):

    @classmethod
    def get_node_from_global_id(cls, info, global_id, only_type=None):
        # So long as only_type is set; if we detect that the global_id is a pk and not a global ID;
        # then coerce it to be a proper global ID before fetching
        if only_type:
            try:
                int(global_id)
                global_id = cls.to_global_id(only_type._meta.name, global_id)
                return super(PKFilteringNode, cls).get_node_from_global_id(info, global_id, only_type)
            except ValueError:
                pass
        return super(PKFilteringNode, cls).get_node_from_global_id(info, global_id, only_type)

现在我可以让GraphQL做到这一点:

{
  batchA: batch(id: "QmF0Y2hOb2RlOjE=") {
    id
    name
  }
  batchB: batch(id: 1) {
    id
    name
  }
}
{
  "data": {
    "batchA": {
      "id": "QmF0Y2hOb2RlOjE=",
      "name": "Default Batch"
    },
    "batchB": {
      "id": "QmF0Y2hOb2RlOjE=",
      "name": "Default Batch"
    }
  }
}
  

但是我非常担心这会破坏下游的东西,   在缓存级别上?   同样,这仍然不允许通过ID进行过滤,因为过滤取决于   DjangoFilterConnectionField

请求

我现在被困住了。我有几个问题:

  1. 这是一个不寻常的要求吗?我问错了吗 我想保留按pk过滤功能
  2. 的问题
  3. 是否有解决此问题的标准模式?

有关Github的问题

  

https://github.com/graphql-python/graphene-django/issues/349

版本

  • graphene-django == 2.1.0
  • django == 1.9.12
  • django-filter == 1.0.1
  • python == 2.7.13

2 个答案:

答案 0 :(得分:0)

您是否尝试过解决方案2,但是使用id作为源?

class PKMixin(object):
    pk = graphene.Field(type=graphene.Int, source='id')

此外,如果您只想获取一条记录,则无论如何都不应通过连接字段。您应该在架构上定义类似batchByPk字段的内容。

要了解的最后一件事是,当前没有以有效的方式实现graphene-django的DjangoFilterConnectionField,因此您甚至都不想使用它。

答案 1 :(得分:0)

我不确定您是否仍然想要答案,但至少让我尝试回答您的问题。如果我的理解错误,请更正。我只是愿意帮助

实际上pk应该是DetailView,而不是ListViewfilter一起使用。

requirements.txt

graphene-django==2.7.1
django==3.0.1
django-filter==2.2.0
python==3.8.1

models.py

from django.contrib.auth import get_user_model
from django.db import models

User = get_user_model()


class Objection(models.Model):
    detail = models.TextField(null=True, blank=True)
    hidden = models.BooleanField(default=False)
    report = models.BooleanField(default=False)
    created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='objections',
                                   related_query_name='objection')

nodes.py

import django_filters
import graphene
from graphene import relay
from graphene_django import DjangoObjectType

from multy_herr.objections.models import Objection


class ObjectionFilter(django_filters.FilterSet):
    pk = django_filters.NumberFilter(field_name='pk')

    class Meta:
        model = Objection
        fields = [
            'pk',
        ]


class ObjectionNode(DjangoObjectType):
    pk = graphene.Field(type=graphene.Int, source='id')

    class Meta:
        model = Objection
        fields = [
            'id',
            'pk',
            'detail',
            'hidden',
            'report',
        ]
        filter_fields = {
            'pk': ['exact'],
            'detail': ['icontains', 'istartswith'],
            'created_by__name': ['icontains', ],
            'hidden': ['exact'],
            'report': ['exact'],
        }
        interfaces = (relay.Node,)


queries.py

import graphene
from graphene import relay
from graphene_django.filter import DjangoFilterConnectionField

from multy_herr.objections.grapheql.nodes import ObjectionNode, ObjectionFilter
from multy_herr.objections.models import Objection


class ObjectionQuery(graphene.ObjectType):
    objection = relay.Node.Field(ObjectionNode)
    all_objections = DjangoFilterConnectionField(ObjectionNode,
                                                 filterset_class=ObjectionFilter)

    def resolve_all_objections(self, info, **kwargs):
        if info.context.user.is_authenticated is False:
            return Objection.objects.none()
        return Objection.objects.filter(created_by=info.context.user)

在这里,我在query中留下类推。使用我的恶意解决方案Insomnia,应用程序将通过Unknown arguemnt pk ...向我发出警告。但是有效

query

query{
#   objection(id: "T2JqZWN0aW9uTm9kZTo1"){
#     id
#     report
#     hidden
#   }
  allObjections(pk: 5){
    edges{
      node{
        id
        pk
        hidden
        report
      }
    }
  }
}

response

{
  "data": {
    "allObjections": {
      "edges": [
        {
          "node": {
            "id": "T2JqZWN0aW9uTm9kZTo1",
            "pk": 5,
            "hidden": false,
            "report": false
          }
        }
      ]
    }
  }
}