使用graphene-django时如何在django-filter的filterset_class中使用Relay的分页功能

问题描述

我有一个Django项目,正在使用django-graphene创建GraphQL API。

尝试将DjangoFilterConnectionFieldrelay.Connectionwhich is the core of pagination's feature)一起使用时出现问题

我的模型太大,有很多关系,但是让我们保持简单...

class Pattern(models.Model):
    code = models.CharField(
        max_length=15
    )
    name = models.CharField(
        max_length=50
    )
    slug = AutoSlugField(
        populate_from='name',max_length=150
    )
    ...

我的节点如下:

class PatternNode(DjangoObjectType):
    # Many fields here...
    ...

    class Meta:
        model = Pattern
        interfaces = (relay.Node,)
        filterset_class = PatternFilterSet

如您所见,我已经在节点的filterset_class中设置了Meta属性

因此,这是该过滤器集:

class PatternFilterSet(FilterSet):
    order_by = OrderingFilter(
        fields=(
            ('date','date'),('name','name'),)
    )
    productcategorization__design__contains = CharFilter(method="product_categorization_design_filter")
    products__predominant_colors__contains = CharFilter(method="products_predominant_colors_filter")

    class Meta:
        model = Pattern
        fields = {
            'name': ['exact','icontains','istartswith'],'alt_name': ['exact','slug': ['exact'],'pattern_class': ['exact'],'sectors': ['exact','in'],'products__instances': ['exact'],'productcategorization__business': ['exact'],'productcategorization__market_segment': ['exact',}

    @staticmethod
    def product_categorization_design_filter(queryset,name,value):
        """
        Does a productcategorization__design__contains filter "manually" because adding it in the Meta.fields does not
        work for ArrayField.

        Args:
             queryset (patterns.managers.PatternQuerySet)
             name (str)
             value (Array) comma delimited list of designs

        Returns:
            filtered_queryset (QuerySet)
        """
        return queryset.filter(productcategorization__design__contains=value.split(","))

    @staticmethod
    def products_predominant_colors_filter(queryset,value):
        """
        Does a products__predominant_colors__contains filter "manually" because adding it in the Meta.fields does not
        work for ArrayField.

        Args:
             queryset (patterns.managers.PatternQuerySet)
             name (str)
             value (Array) comma delimited list of designs

        Returns:
            filtered_queryset (QuerySet)
        """
        return queryset.filter(products__predominant_colors__contains=value.split(",")).distinct()

如您所见,我的API中需要针对该特定模型的许多特殊过滤选项。

在我的模式中,我有以下内容

class PatternConnection(relay.Connection):
    class Meta:
        node = PatternNode


class Query(graphene.ObjectType):
    pattern = relay.Node.Field(
        PatternNode,id=ID(),slug=String()
    )
    patterns = relay.ConnectionField(PatternConnection)

目前一切正常,但过滤器不起作用。

我正在执行以下查询

query Patterns {
    patterns(first: 2) {
        pageInfo {
            startCursor
            endCursor
            hasNextPage
        }
        edges {
            cursor
            node {
                id
                name
            }
        }
    }
}

,并收到以下回复

{
    "data": {
        "patterns": {
            "pageInfo": {
                "startCursor": "YXJyYXljb25uZWN0aW9uOjA=","endCursor": "YXJyYXljb25uZWN0aW9uOjE=","hasNextPage": true
            },"edges": [
                {
                    "cursor": "YXJyYXljb25uZWN0aW9uOjA=","node": {
                        "id": "UGF0dGVybk5vZGU6Mjcw","name": "42 Oz - Jk"
                    }
                },{
                    "cursor": "YXJyYXljb25uZWN0aW9uOjE=","node": {
                        "id": "UGF0dGVybk5vZGU6Mjcx","name": "42 Oz - Pebble Top - Jk"
                    }
                }
            ]
        }
    }
}

所以分页效果很好!

现在,当我使用自己的过滤器之一进行尝试时,就像这样:

query Patterns ($predominantColors: String) {
    patterns(first: 2,products_PredominantColors_Contains: $predominantColors) {
        pageInfo {
            startCursor
            endCursor
            hasNextPage
        }
        edges {
            cursor
            node {
                id
                name
            }
        }
    }
}

我收到以下答复:

{
    "errors": [
        {
            "message": "UnkNown argument \"products_PredominantColors_Contains\" on field \"patterns\" of type \"Query\".","locations": [
                {
                    "line": 2,"column": 24
                }
            ]
        }
    ]
}

我认为是因为我没有使用DjangoFilterConnectionField as suggested here,但是当我尝试这样做时:

class PatternConnection(relay.Connection):
    class Meta:
        node = PatternNode


class Query(graphene.ObjectType):
    pattern = relay.Node.Field(
        PatternNode,slug=String()
    )
    patterns = DjangoFilterConnectionField(PatternConnection)

我遇到以下错误

September 23,2020 - 17:06:12
Django version 2.2.12,using settings 'proquinal_api.settings'
Starting development server at http://api.spradling.local:8000/
Quit the server with CONTROL-C.
/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/graphene_django/types.py:131: UserWarning: Django model "cities_light.City" does not have a field or attribute named "location". Consider removing the field from the "exclude" list of DjangoObjectType "CityNode" because it has no effect
  type_=type_,Internal Server Error: /graphql/
Traceback (most recent call last):
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/graphene_django/settings.py",line 79,in import_from_string
    module = importlib.import_module(module_path)
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/python3.framework/Versions/3.7/lib/python3.7/importlib/__init__.py",line 127,in import_module
    return _bootstrap._gcd_import(name[level:],package,level)
  File "<frozen importlib._bootstrap>",line 1006,in _gcd_import
  File "<frozen importlib._bootstrap>",line 983,in _find_and_load
  File "<frozen importlib._bootstrap>",line 967,in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>",line 677,in _load_unlocked
  File "<frozen importlib._bootstrap_external>",line 728,in exec_module
  File "<frozen importlib._bootstrap>",line 219,in _call_with_frames_removed
  File "/Users/cristianrojas/www/spradling-api/proquinal_api/schema.py",line 49,in <module>
    mutation=Mutation
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/graphene/types/schema.py",line 78,in __init__
    self.build_typemap()
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/graphene/types/schema.py",line 168,in build_typemap
    initial_types,auto_camelcase=self.auto_camelcase,schema=self
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/graphene/types/typemap.py",line 80,in __init__
    super(TypeMap,self).__init__(types)
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/graphql/type/typemap.py",line 31,in __init__
    self.update(reduce(self.reducer,types,OrderedDict()))  # type: ignore
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/graphene/types/typemap.py",line 88,in reducer
    return self.graphene_reducer(map,type)
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/graphene/types/typemap.py",line 117,in graphene_reducer
    return GraphQLTypeMap.reducer(map,internal_type)
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/graphql/type/typemap.py",line 109,in reducer
    field_map = type_.fields
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/graphql/pyutils/cached_property.py",line 22,in __get__
    value = obj.__dict__[self.func.__name__] = self.func(obj)
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/graphql/type/deFinition.py",line 198,in fields
    return define_field_map(self,self._fields)
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/graphql/type/deFinition.py",line 212,in define_field_map
    field_map = field_map()
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/graphene/types/typemap.py",line 275,in construct_fields_for_type
    map = self.reducer(map,field.type)
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/graphene_django/fields.py",line 98,in type
    assert _type._Meta.connection,"The type {} doesn't have a connection".format(
AttributeError: 'Connectionoptions' object has no attribute 'connection'

During handling of the above exception,another exception occurred:

Traceback (most recent call last):
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/django/core/handlers/exception.py",line 34,in inner
    response = get_response(request)
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/django/core/handlers/base.py",line 115,in _get_response
    response = self.process_exception_by_middleware(e,request)
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/django/core/handlers/base.py",line 113,in _get_response
    response = wrapped_callback(request,*callback_args,**callback_kwargs)
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/django/views/decorators/csrf.py",line 54,in wrapped_view
    return view_func(*args,**kwargs)
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/django/views/generic/base.py",line 62,in view
    self = cls(**initkwargs)
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/graphene_django/views.py",line 100,in __init__
    schema = graphene_settings.SCHEMA
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/graphene_django/settings.py",line 126,in __getattr__
    val = perform_import(val,attr)
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/graphene_django/settings.py",line 65,in perform_import
    return import_from_string(val,setting_name)
  File "/Users/cristianrojas/.virtualenvs/spradling-api-YJ5S1R6Y/lib/python3.7/site-packages/graphene_django/settings.py",in import_from_string
    raise ImportError(msg)
ImportError: Could not import 'proquinal_api.schema.schema' for Graphene setting 'SCHEMA'. AttributeError: 'Connectionoptions' object has no attribute 'connection'.
[23/Sep/2020 17:08:02] "POST /graphql/ HTTP/1.1" 500 212017

因此,我想知道将DjangoFilterConnectionFieldPatternConnection中继的连接结合使用以使过滤器和分页协同工作的正确方法是什么。

解决方法

的方式将 PatternNode 传递到 DjangoFilterConnectionField

import graphene


class PatternNode(DjangoObjectType):
    # Many fields here...
    ...

    class Meta:
        model = Pattern
        interfaces = (relay.Node,)
        filterset_class = PatternFilterSet
        

class Query(graphene.ObjectType):
    patterns = DjangoFilterConnectionField(PatternNode)