Используя graphene-django, можно ли определить круговую связь между двумя узлами?

Используя следующий надуманный пример:

from django.db import models
from django_filters import FilterSet, OrderingFilter
from graphene import ObjectType, Schema, relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField

class Recipe(models.Model):
    name = models.CharField(max_length=50)
    ingredients = models.ManyToManyField('Ingredient', related_name='recipes')

class Ingredient(models.Model):
    name = models.CharField(max_length=50)

class RecipeFilter(FilterSet):

    order_by = OrderingFilter(fields=[('name', 'name')])

    class Meta:
        fields = {'name': ['icontains']}
        model = Recipe

class IngredientFilter(FilterSet):

    order_by = OrderingFilter(fields=[('name', 'name')])

    class Meta:
        fields = {'name': ['icontains']}
        model = Ingredient

class RecipeNode(DjangoObjectType):

    ingredients = DjangoFilterConnectionField(IngredientNode, filterset_class=IngredientFilter)

    class Meta:
        interfaces = [relay.Node]
        model = Recipe
        only_fields = ['name']

class IngredientNode(DjangoObjectType):

    recipes = DjangoFilterConnectionField(RecipeNode, filterset_class=RecipeFilter)

    class Meta:
        interfaces = [relay.Node]
        model = Ingredient
        only_fields = ['name']

class Queries(ObjectType):

    all_recipes = DjangoFilterConnectionField(RecipeNode, filterset_class=RecipeFilter)
    all_ingredients = DjangoFilterConnectionField(IngredientNode, filterset_class=IngredientFilter)

schema = Schema(query=Queries)

Как я могу определить циклическую связь между RecipeNode и IngredientNode, чтобы я мог выполнить следующий запрос GraphQL:

{
  allRecipes(name_Icontains: "gg") {
    edges {
      node {
        name
        ingredients(name_Icontains: "gg") {
          edges {
            node {
              name
            }
          }
        }
      }
    }
  }
  allIngredients(name_Icontains: "gg") {
    edges {
      node {
        name
        recipes(name_Icontains: "gg") {
          edges {
            node {
              name
            }
          }
        }
      }
    }
  }
}

В настоящее время я не могу ссылаться на IngredientNode из RecipeNode, так как он еще не определен. Если я попытаюсь использовать лямбду, как я видел в другом месте, я получаю AttributeError: 'function' object has no attribute '_meta'.

class IngredientNode(DjangoObjectType):

    recipes = DjangoFilterConnectionField(lambda: RecipeNode, filterset_class=RecipeFilter)

    class Meta:
        interfaces = [relay.Node]
        model = Ingredient
        only_fields = ['name']

Если я попытаюсь установить атрибут постфактум, я не смогу запросить ingredients из рецепта. Ошибки нет, Graphiql просто ведет себя так, как будто ingredients никогда не определялся.

class RecipeNode(DjangoObjectType):

    class Meta:
        interfaces = [relay.Node]
        model = Recipe
        only_fields = ['name']

class IngredientNode(DjangoObjectType):

    recipes = DjangoFilterConnectionField(RecipeNode, filterset_class=RecipeFilter)

    class Meta:
        interfaces = [relay.Node]
        model = Ingredient
        only_fields = ['name']

RecipeNode.ingredients = DjangoFilterConnectionField(IngredientNode, filterset_class=IngredientFilter)

Я должен думать, что есть простое решение, которого я просто не вижу. Любая помощь будет оценена по достоинству. Спасибо!

Джанго 1.8.17, джанго-фильтр 0.15.3, графен-джанго 1.2.0


person CaffeineFueled    schedule 27.12.2016    source источник


Ответы (1)


Для потомков способ, которым мы работали над этой проблемой, состоял в том, чтобы переопределить DjangoFilterConnectionField таким образом, чтобы аргумент filterset_class был обязательным, и мы удалили код, который ссылался на мета-атрибуты узла. Недостатком является то, что мы больше не можем использовать ярлык filter_fields. Для нас это не проблема, так как мы использовали наборы фильтров с самого начала.

Все окончательное решение/обходной путь:

from django.db import models
from django_filters import FilterSet, OrderingFilter
from functools import partial
from graphene import ObjectType, Schema, relay
from graphene_django import DjangoObjectType, DjangoConnectionField
from graphene_django.filter.utils import get_filtering_args_from_filterset, get_filterset_class

class DjangoFilterConnectionField(DjangoConnectionField):

    def __init__(self, type, filterset_class, *args, **kwargs):

        self.filterset_class = get_filterset_class(filterset_class)
        self.filtering_args = get_filtering_args_from_filterset(self.filterset_class, type)
        kwargs.setdefault('args', {})
        kwargs['args'].update(self.filtering_args)
        super(DjangoFilterConnectionField, self).__init__(type, *args, **kwargs)

    @staticmethod
    def connection_resolver(resolver, connection, default_manager, filterset_class, filtering_args,
                            root, args, context, info):
        filter_kwargs = {k: v for k, v in args.items() if k in filtering_args}
        qs = default_manager.get_queryset()
        qs = filterset_class(data=filter_kwargs, queryset=qs).qs
        return DjangoConnectionField.connection_resolver(resolver, connection, qs, root, args, context, info)

    def get_resolver(self, parent_resolver):
        return partial(self.connection_resolver, parent_resolver, self.type, self.get_manager(),
                       self.filterset_class, self.filtering_args)

class Recipe(models.Model):

    name = models.CharField(max_length=50)
    ingredients = models.ManyToManyField('Ingredient', related_name='recipes')

class Ingredient(models.Model):

    name = models.CharField(max_length=50)

class RecipeFilter(FilterSet):

    order_by = OrderingFilter(fields=[('name', 'name')])

    class Meta:
        fields = {'name': ['icontains']}
        model = Recipe

class IngredientFilter(FilterSet):

    order_by = OrderingFilter(fields=[('name', 'name')])

    class Meta:
        fields = {'name': ['icontains']}
        model = Ingredient

class RecipeNode(DjangoObjectType):

    ingredients = DjangoFilterConnectionField(lambda: IngredientNode, filterset_class=IngredientFilter)

    class Meta:
        interfaces = [relay.Node]
        model = Recipe
        only_fields = ['name']

class IngredientNode(DjangoObjectType):

    recipes = DjangoFilterConnectionField(RecipeNode, filterset_class=RecipeFilter)

    class Meta:
        interfaces = [relay.Node]
        model = Ingredient
        only_fields = ['name']

class Queries(ObjectType):

    all_recipes = DjangoFilterConnectionField(RecipeNode, filterset_class=RecipeFilter)
    all_ingredients = DjangoFilterConnectionField(IngredientNode, filterset_class=IngredientFilter)

schema = Schema(query=Queries)

Переопределение DjangoFilterConnectionField таким образом позволяет нам использовать лямбду для ссылки на узлы, которые еще не определены.

person CaffeineFueled    schedule 02.01.2017