【问题标题】:Using graphene-django, is it possible to define a circular relationship between two nodes?使用graphene-django,是否可以定义两个节点之间的循环关系?
【发布时间】:2017-05-12 00:00:40
【问题描述】:

使用以下人为的示例:

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)

如何定义 RecipeNodeIngredientNode 之间的循环关系,以便我可以运行以下 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
            }
          }
        }
      }
    }
  }
}

就目前而言,我无法从RecipeNode 引用IngredientNode,因为它尚未定义。如果我尝试使用其他地方推荐的 lambda,我会收到 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)

我不得不认为有一个我没有看到的简单解决方案。任何帮助,将不胜感激。谢谢!

Django 1.8.17、django-filter 0.15.3、graphene-django 1.2.0

【问题讨论】:

    标签: django graphql django-filter graphene-python


    【解决方案1】:

    对于后代,我们解决此问题的方法是重新定义 DjangoFilterConnectionField 以便需要 filterset_class 参数,并且我们删除了引用节点的 Meta 属性的代码。不利的一面是我们不能再利用 filter_fields 快捷方式。对我们来说,这不是问题,因为我们从一开始就使用 FilterSets。

    整个最终解决方案/解决方法:

    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 允许我们使用 lambda 来引用尚未定义的节点。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-04-09
      • 2021-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多