Включить фильтрацию на основе PK в Django Graphene Relay с сохранением глобальных идентификаторов

Проблема

Я использую 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

Я не добился прогресса в 1, так как кажется, что django-graphene (и, возможно, стандарт реле) накладывает ограничение на то, чтобы это поле называлось id. Я вижу, что id использовался как Magic String во многих местах, и, похоже, не существует стандартного способа изменить имя поля.

Решение 2

На 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

Обновление 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"
    }
  }
}

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

Запрос

Я застрял в данный момент. У меня есть несколько вопросов:

  1. Это необычное требование для начала? Я неправильно спрашиваю вопрос, когда я хочу сохранить возможность фильтрации по pk
  2. Есть ли стандартный шаблон для решения этой проблемы?

Связанная проблема на Github

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

Версии

  • графен-джанго == 2.1.0
  • Джанго == 1.9.12
  • Джанго-фильтр == 1.0.1
  • питон == 2.7.13

API всегда возвращает узел с производным глобальным идентификатором. Как клиент, если мне нужно найти узел, я могу использовать этот же идентификатор. Предоставление базового PK кажется ненужным, за исключением случаев, когда A) есть какая-то другая служба, с которой вы взаимодействуете, которая использует PK в качестве ссылки, или B) PK иным образом важен для клиента с точки зрения бизнес-правил (т. е. клиент сравнивает значения PK с реализовать некоторую бизнес-логику). Почему вы считаете, что разоблачение ПК необходимо?

Daniel Rearden 23.01.2019 15:04

Устаревший код, тесно связанный с PK

rtindru 23.01.2019 15:05

В принципе, для нас верны и А), и Б).

rtindru 24.01.2019 07:05

URL-адреса веб-сайтов с идентификаторами являются законным вариантом использования.

Andrew Ingram 24.01.2019 11:16

Я думал об использовании графена, но это настолько сильное ограничение, что мне, вероятно, придется отказаться от этой идеи.

Jura Brazdil 26.09.2019 16:05
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
15
5
2 960
2

Ответы 2

Вы пробовали решение 2, но вместо этого использовали id в качестве источника?

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

Кроме того, если вы хотите получить только одну запись, вам все равно не следует использовать поле подключения. Вместо этого вы должны определить в своей схеме что-то вроде поля batchByPk.

Последнее, о чем следует помнить, это то, что в настоящее время DjangoFilterConnectionField graphene-django не реализован эффективным образом, поэтому вы, возможно, даже не захотите его использовать.

Можете ли вы рассказать мне больше о том, почему DjangoFilterConnectionField неэффективен? Есть ли документация или проблемы на Github, которые я могу прочитать, чтобы узнать больше?

rtindru 24.01.2019 11:28
source='id' не помогает - та же ошибка: >'Meta.fields' содержит поля, которые не определены в этом FilterSet: pk Эта ошибка сохраняется до тех пор, пока не будет объявлен явный FilterSet с pk.
rtindru 24.01.2019 11:32

@rtindru на данный момент весь код подключения в Graphene реализует нумерацию страниц с ограничением / смещением, а не настоящую нумерацию страниц на основе курсора, поэтому он не имеет истинного преимущества шаблона - он просто имитирует его.

Andrew Ingram 24.01.2019 14:52

Я не уверен, что вы все еще хотите получить ответ или нет, но по крайней мере позвольте мне попытаться ответить на ваш вопрос. Поправьте, если мое понимание неверно. я просто готов помочь

На самом деле 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
          }
        }
      ]
    }
  }
}

Другие вопросы по теме