Я использую django-graphene с Relay на нашем сервере GraphQL. Реализация накладывает Требование глобального идентификатора в классе graphene.relay.Node, который переопределяет и скрывает поле ID Django.
В результате я могу запросить так:
{
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, так как кажется, что django-graphene (и, возможно, стандарт реле) накладывает ограничение на то, чтобы это поле называлось id. Я вижу, что id использовался как Magic String во многих местах, и, похоже, не существует стандартного способа изменить имя поля.
На 2 я могу заставить свойство работать с Mixin следующим образом:
class PKMixin(object):
pk = graphene.Field(type=graphene.Int, source='pk')
Однако я не могу заставить фильтрацию через django-filter работать, так как FilterSet не имеет объявленного поля pk и ломается со следующей ошибкой
'Meta.fields' contains fields that are not defined on this FilterSet: pk
Я пробовал следующее:
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"
}
}
}
But I have a fairly strong fear this will break something downstream, at the level of caching perhaps? Also this does not allow filtering by ID still since filtering depends on
DjangoFilterConnectionField
Я застрял в данный момент. У меня есть несколько вопросов:
https://github.com/graphql-python/graphene-django/issues/349
Устаревший код, тесно связанный с PK
В принципе, для нас верны и А), и Б).
URL-адреса веб-сайтов с идентификаторами являются законным вариантом использования.
Я думал об использовании графена, но это настолько сильное ограничение, что мне, вероятно, придется отказаться от этой идеи.






Вы пробовали решение 2, но вместо этого использовали id в качестве источника?
class PKMixin(object):
pk = graphene.Field(type=graphene.Int, source='id')
Кроме того, если вы хотите получить только одну запись, вам все равно не следует использовать поле подключения. Вместо этого вы должны определить в своей схеме что-то вроде поля batchByPk.
Последнее, о чем следует помнить, это то, что в настоящее время DjangoFilterConnectionField graphene-django не реализован эффективным образом, поэтому вы, возможно, даже не захотите его использовать.
Можете ли вы рассказать мне больше о том, почему DjangoFilterConnectionField неэффективен? Есть ли документация или проблемы на Github, которые я могу прочитать, чтобы узнать больше?
source='id' не помогает - та же ошибка: >'Meta.fields' содержит поля, которые не определены в этом FilterSet: pk Эта ошибка сохраняется до тех пор, пока не будет объявлен явный FilterSet с pk.
@rtindru на данный момент весь код подключения в Graphene реализует нумерацию страниц с ограничением / смещением, а не настоящую нумерацию страниц на основе курсора, поэтому он не имеет истинного преимущества шаблона - он просто имитирует его.
Я не уверен, что вы все еще хотите получить ответ или нет, но по крайней мере позвольте мне попытаться ответить на ваш вопрос. Поправьте, если мое понимание неверно. я просто готов помочь
На самом деле 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')
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 argument 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
}
}
]
}
}
}
API всегда возвращает узел с производным глобальным идентификатором. Как клиент, если мне нужно найти узел, я могу использовать этот же идентификатор. Предоставление базового PK кажется ненужным, за исключением случаев, когда A) есть какая-то другая служба, с которой вы взаимодействуете, которая использует PK в качестве ссылки, или B) PK иным образом важен для клиента с точки зрения бизнес-правил (т. е. клиент сравнивает значения PK с реализовать некоторую бизнес-логику). Почему вы считаете, что разоблачение ПК необходимо?