使用 graphene-django 时如何使用来自 django-filter 的 filterset_class 的 Relay 分页功能
How to use Relay's pagination feature with filterset_class from django-filter when using graphene-django
我有一个 Django 项目,我在其中使用 django-graphene 创建 GraphQL API。
尝试将 DjangoFilterConnectionField
与 relay.Connection
(which 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
如您所见,我在节点的 Meta
中设置了 filterset_class
属性。
所以,这是过滤器集:
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', 'icontains', 'istartswith'],
'slug': ['exact'],
'pattern_class': ['exact'],
'sectors': ['exact', 'in'],
'products__instances': ['exact'],
'productcategorization__business': ['exact'],
'productcategorization__market_segment': ['exact', 'in'],
}
@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, name, 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,
id=ID(),
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", line 88, 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
所以我想知道将 DjangoFilterConnectionField
与我的 PatternConnection
中继连接结合使用以使过滤器和分页协同工作的正确方法是什么。
将 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(<b>PatternNode</b>)
我有一个 Django 项目,我在其中使用 django-graphene 创建 GraphQL API。
尝试将 DjangoFilterConnectionField
与 relay.Connection
(which 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
如您所见,我在节点的 Meta
中设置了 filterset_class
属性。
所以,这是过滤器集:
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', 'icontains', 'istartswith'],
'slug': ['exact'],
'pattern_class': ['exact'],
'sectors': ['exact', 'in'],
'products__instances': ['exact'],
'productcategorization__business': ['exact'],
'productcategorization__market_segment': ['exact', 'in'],
}
@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, name, 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,
id=ID(),
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", line 88, 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
所以我想知道将 DjangoFilterConnectionField
与我的 PatternConnection
中继连接结合使用以使过滤器和分页协同工作的正确方法是什么。
将 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(<b>PatternNode</b>)