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

Python 在Django Graphene中继中启用基于PK的过滤,同时保留全局ID 问题,python,django,graphql,relay,graphene-python,Python,Django,Graphql,Relay,Graphene Python,我在GraphQL服务器上使用django graphene和继电器。该实现在graphene.relay.Node类中引入了一个覆盖并隐藏Django的ID字段的属性 因此,我可以这样查询: { allBatches(id:"QmF0Y2hOb2RlOjE=") { edges { node { id pk } } } } class PKMixin(object): pk = graphene.Fi

我在GraphQL服务器上使用django graphene和继电器。该实现在
graphene.relay.Node
类中引入了一个覆盖并隐藏Django的ID字段的属性

因此,我可以这样查询:

{
    allBatches(id:"QmF0Y2hOb2RlOjE=") {
    edges {
      node {
        id
        pk
      }
    }
  }
}
class PKMixin(object):
    pk = graphene.Field(type=graphene.Int, source='pk')
得到这样的回答:

{
  "data": {
    "allBatches": {
      "edges": [
        {
          "node": {
            "id": "QmF0Y2hOb2RlOjE=",
            "pk": 1
          }
        }
      ]
    }
  }
}
但是,我失去的是通过对象本身的原始ID(或PK)字段进行过滤的能力:

{
    allBatches(id:1) {
    edges {
      node {
        id
        pk
      }
    }
  }
}
事实上,I无法通过ID过滤对象。 我可以想出两种可能的解决办法: 1.防止django graphene中继劫持和隐藏
id
字段,可能会强制它使用不同的字段名,如
gid
2.找到一种方法,将
pk
作为一个特殊字段包括在内,该字段既可以作为属性,也可以作为筛选器使用

解决方案1 我在1上没有取得任何进展,因为它似乎是
django graphene
(可能还有中继标准)规定了一个限制,该字段被称为
id
。我看到,
id
在多个地方被用作一个神奇的字符串,而且似乎没有一种标准的方法来更改字段名

解决方案2 在2上,我可以让属性使用
Mixin
,如下所示:

{
    allBatches(id:"QmF0Y2hOb2RlOjE=") {
    edges {
      node {
        id
        pk
      }
    }
  }
}
class PKMixin(object):
    pk = graphene.Field(type=graphene.Int, source='pk')
但是,我无法通过
django filter
使过滤工作,因为
FilterSet
没有声明字段
pk
,并出现以下错误

“Meta.fields”包含未在此筛选器集上定义的字段: 主键

更新日期: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

要求 我现在陷入困境。我有几个问题:

  • 这是一个不同寻常的要求吗?我问错了吗 问题:何时我希望保留按主键筛选的能力
  • 有没有标准模式来解决这个问题
  • Github的相关问题

    版本
    • 石墨烯django==2.1.0
    • django==1.9.12
    • django过滤器==1.0.1
    • python==2.7.13

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

    类PKMixin(对象): pk=graphene.Field(type=graphene.Int,source='id') 另外,如果您只想获取一条记录,那么无论如何都不应该通过连接字段。您应该在模式上定义类似于
    batchByPk
    字段的内容


    最后需要注意的是,目前graphene django的
    DjangoFilterConnectionField
    没有以有效的方式实现,因此您可能根本不想使用它。

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

    实际上
    pk
    应该是
    DetailView
    而不是
    ListView
    filter
    一起使用

    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')
    
    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,)
    
    
    
    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)
    
    
    nodes.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')
    
    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,)
    
    
    
    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)
    
    
    querys.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')
    
    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,)
    
    
    
    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
    中留下注释,以示类推。使用我的黑客解决方案
    失眠
    应用程序将用
    未知参数pk…
    警告我。但有效

    查询

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

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

    API总是返回带有派生全局ID的节点。作为客户端,如果需要查找节点,我可以使用相同的标识符。公开底层PK似乎没有必要,除非A)您正在与某个其他服务交互,该服务使用PK作为参考,或者B)从业务规则的角度来看,PK对客户端来说是重要的(即,客户端比较PK值以实施某些业务逻辑)。为什么您觉得公开PK是必要的?基本上,与PKS紧密耦合的遗留代码A)和B)对我们来说都是正确的。中带有ID的网站URL是合法的用例。我考虑过使用graphene,但是这是一个很强的限制,我可能需要离开这个想法,你能告诉我更多关于
    DjangoFilterConnectionField
    的效率吗?Github上是否有文档或问题,我可以阅读以了解更多信息?
    source='id'
    没有帮助-相同的错误:>'Meta.fields'包含未在此过滤器集上定义的字段:pk此错误持续存在,除非此时使用
    pk
    @rtindru声明显式过滤器集,Graphene中的所有连接代码都实现了限制/偏移分页,而不是真正的基于光标的分页,因此它没有该模式的任何真正好处——只是假装而已。