I've a Django project where I'm using django-graphene to create a GraphQL API.
There's an issue when trying to use DjangoFilterConnectionField
together with the relay.Connection
(which is the core of pagination's feature)
My model is too large and has many relationships, but let's keep things simple...
class Pattern(models.Model):
code = models.CharField(
max_length=15
)
name = models.CharField(
max_length=50
)
slug = AutoSlugField(
populate_from='name',
max_length=150
)
...
My node looks like:
class PatternNode(DjangoObjectType):
# Many fields here...
...
class Meta:
model = Pattern
interfaces = (relay.Node,)
filterset_class = PatternFilterSet
As you can see, I've setup the filterset_class
attribute in the Meta
of my Node.
So, here's that filter set:
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()
As you can see there are many special filtering options I need in my API for that particular Model.
In my schema I've the following:
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)
Everything works pretty fine at this point, but filters aren't working.
I'm executing the following query:
query Patterns {
patterns(first: 2) {
pageInfo {
startCursor
endCursor
hasNextPage
}
edges {
cursor
node {
id
name
}
}
}
}
and receiving the following response:
{
"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"
}
}
]
}
}
}
so pagination is working well!
Now, when I try it using one of my filters, like this:
query Patterns ($predominantColors: String) {
patterns(first: 2, products_PredominantColors_Contains: $predominantColors) {
pageInfo {
startCursor
endCursor
hasNextPage
}
edges {
cursor
node {
id
name
}
}
}
}
I'm receiving the following response:
{
"errors": [
{
"message": "Unknown argument \"products_PredominantColors_Contains\" on field \"patterns\" of type \"Query\".",
"locations": [
{
"line": 2,
"column": 24
}
]
}
]
}
I assume, that's because I'm not using the DjangoFilterConnectionField
as suggested here, but when I try to do this:
class PatternConnection(relay.Connection):
class Meta:
node = PatternNode
class Query(graphene.ObjectType):
pattern = relay.Node.Field(
PatternNode,
id=ID(),
slug=String()
)
patterns = DjangoFilterConnectionField(PatternConnection)
I'm getting the following error:
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
So I'm wondering what's the right way to use DjangoFilterConnectionField
in combination with my PatternConnection
Relay's Connection to make filters and pagination work together.
pass the PatternNode
to the DjangoFilterConnectionField
as
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)