Skip to content

Commit

Permalink
Graphene django v3 (projectcaluma#1645)
Browse files Browse the repository at this point in the history
* fix(datasource): accept new additional parameters from graphene

* fix(filters): overload resolve_queryset() to support multi order_by

Graphene does not really support order_by with multiple values like we need it.

There's a bug in graphene that we need to work around:
 graphql-python/graphene-django#1280

While we're at it, also clean up some more data.
  • Loading branch information
David Vogt authored and fugal-dy committed Feb 16, 2022
1 parent 2759f1b commit 5b474f8
Show file tree
Hide file tree
Showing 9 changed files with 722 additions and 617 deletions.
88 changes: 86 additions & 2 deletions caluma/caluma_core/filters.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from functools import reduce
import enum
from functools import reduce, singledispatch

import graphene
from django import forms
Expand All @@ -25,13 +26,14 @@
from graphene import Enum, InputObjectType, List
from graphene.types import generic
from graphene.types.utils import get_type
from graphene.utils.str_converters import to_camel_case
from graphene.utils.str_converters import to_camel_case, to_snake_case
from graphene_django import filter
from graphene_django.converter import convert_choice_name
from graphene_django.filter.filterset import GrapheneFilterSetMixin
from graphene_django.forms.converter import convert_form_field
from graphene_django.registry import get_global_registry
from localized_fields.fields import LocalizedField
from rest_framework.exceptions import ValidationError

from .forms import (
GlobalIDFormField,
Expand Down Expand Up @@ -525,6 +527,88 @@ class DjangoFilterConnectionField(
def filterset_class(self):
return self._provided_filterset_class

@classmethod
def connection_resolver(
cls,
resolver,
connection,
default_manager,
queryset_resolver,
max_limit,
enforce_first_or_last,
root,
info,
**args,
):
return super().connection_resolver(
resolver=resolver,
connection=connection,
default_manager=default_manager,
queryset_resolver=queryset_resolver,
max_limit=max_limit,
enforce_first_or_last=enforce_first_or_last,
root=root,
info=info,
**cls._clean_args_for_queryset_resolver(args),
)

@classmethod
def _clean_args_for_queryset_resolver(cls, args):
# Graphend parses incoming data into Enums too early, thus our filters
# will receive enum objects that cannot be parsed
#
# TODO: check if this is still required after the below
# resolve_queryset() is completely implemented (we assumed it's
# the Enums, but it was actually the list in order_by. We'll keep
# it here until we KNOW we can remove it again..)
@singledispatch
def clean(data):
return data

@clean.register(enum.Enum)
def _(data):
return data.value

@clean.register(list)
def _(data):
return [clean(e) for e in data]

@clean.register(dict)
def _(data):
return {k: clean(v) for k, v in data.items()}

return clean(args)

@classmethod
def resolve_queryset(
cls, connection, iterable, info, args, filtering_args, filterset_class
):
# Overload the parent class' resolve_queryset() because (for now)
# it is unable to deal with multiple order_by values. It's more or less
# a literal copy of DjangoFilterConnectionField.resolve_queryset() except
# for a "better" filter_kwargs() that doesn't fail on lists in the order_by
# parameter.
def filter_kwargs():
kwargs = {}
for k, v in args.items():
if k in filtering_args:
if k == "order_by" and v is not None:
if isinstance(v, list):
v = [to_snake_case(e) for e in v]
else:
v = to_snake_case(v)
kwargs[k] = v
return kwargs

qs = connection._meta.node.get_queryset(iterable, info)

filterset = filterset_class(
data=filter_kwargs(), queryset=qs, request=info.context
)
if filterset.form.is_valid():
return filterset.qs
raise ValidationError(filterset.form.errors.as_json())


class DjangoFilterSetConnectionField(DjangoFilterConnectionField):
@property
Expand Down
3 changes: 2 additions & 1 deletion caluma/caluma_core/ordering.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,14 @@ def __init__(self, field_name="meta"):
def get_ordering_value(
self, qs: QuerySet, value: Any
) -> Tuple[QuerySet, OrderingFieldType]:
value = (hasattr(value, "value") and value.value) or value # noqa: B009

return qs, CombinedExpression(F(self.field_name), "->", Value(value))


class AttributeOrderingMixin(CalumaOrdering):
def get_ordering_value(self, qs, value):
value = (getattr(value, "value") and value.value) or value # noqa: B009
value = (hasattr(value, "value") and value.value) or value # noqa: B009
if value not in self._fields: # pragma: no cover
# this is normally covered by graphene enforcing its schema,
# but we still need to handle it
Expand Down
6 changes: 6 additions & 0 deletions caluma/caluma_core/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ class CalumaChoiceField(ChoiceField):
def to_internal_value(self, data):
# TODO: This shouldn't be required IMHO - find out why
# graphene parses the enum value before we get to it
#
# This is a workaround for the following bug:
# https://github.com/graphql-python/graphene-django/issues/1280
#
# If/when this bug is fixed, this whole intermediate class may
# become obsolete
if isinstance(data, enum.Enum):
data = data.value
return super().to_internal_value(data)
Expand Down
2 changes: 1 addition & 1 deletion caluma/caluma_data_source/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class Query(ObjectType):
all_data_sources = ConnectionField(DataSourceConnection)
data_source = ConnectionField(DataSourceDataConnection, name=String(required=True))

def resolve_all_data_sources(self, info):
def resolve_all_data_sources(self, info, **kwargs):
return get_data_sources()

def resolve_data_source(self, info, name, **kwargs):
Expand Down
57 changes: 57 additions & 0 deletions caluma/caluma_form/tests/__snapshots__/test_form.ambr
Original file line number Diff line number Diff line change
@@ -1,3 +1,60 @@
# name: test_query_all_forms[First result-1st-float]
<class 'dict'> {
'allForms': <class 'dict'> {
'edges': <class 'list'> [
<class 'dict'> {
'node': <class 'dict'> {
'description': 'First result',
'id': 'Rm9ybTplbnZpcm9ubWVudGFsLXRlbg==',
'meta': <class 'dict'> {
},
'name': '1st',
'questions': <class 'dict'> {
'edges': <class 'list'> [
<class 'dict'> {
'node': <class 'dict'> {
'id': 'RmxvYXRRdWVzdGlvbjpyZWR1Y2UtYWdhaW5zdA==',
'label': 'Derek Mendoza',
'slug': 'reduce-against',
},
},
],
},
'slug': 'environmental-ten',
},
},
<class 'dict'> {
'node': <class 'dict'> {
'description': 'Second result',
'id': 'Rm9ybTpsb3NzLWxlYXJu',
'meta': <class 'dict'> {
},
'name': '2nd',
'questions': <class 'dict'> {
'edges': <class 'list'> [
],
},
'slug': 'loss-learn',
},
},
<class 'dict'> {
'node': <class 'dict'> {
'description': 'Second result',
'id': 'Rm9ybTpzb25nLWxpZ2h0',
'meta': <class 'dict'> {
},
'name': '3rd',
'questions': <class 'dict'> {
'edges': <class 'list'> [
],
},
'slug': 'song-light',
},
},
],
},
}
---
# name: test_remove_form_question
<class 'dict'> {
'removeFormQuestion': <class 'dict'> {
Expand Down
138 changes: 0 additions & 138 deletions caluma/caluma_form/tests/__snapshots__/test_history.ambr

This file was deleted.

Loading

0 comments on commit 5b474f8

Please sign in to comment.