-
Notifications
You must be signed in to change notification settings - Fork 769
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add TypedFilter which allow to explicitly give a filter input GraphQL type * Fix doc typo Co-authored-by: Thomas Leonard <thomas@loftorbital.com>
- Loading branch information
Showing
11 changed files
with
413 additions
and
154 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import warnings | ||
from ...utils import DJANGO_FILTER_INSTALLED | ||
|
||
if not DJANGO_FILTER_INSTALLED: | ||
warnings.warn( | ||
"Use of django filtering requires the django-filter package " | ||
"be installed. You can do so using `pip install django-filter`", | ||
ImportWarning, | ||
) | ||
else: | ||
from .array_filter import ArrayFilter | ||
from .global_id_filter import GlobalIDFilter, GlobalIDMultipleChoiceFilter | ||
from .list_filter import ListFilter | ||
from .range_filter import RangeFilter | ||
from .typed_filter import TypedFilter | ||
|
||
__all__ = [ | ||
"DjangoFilterConnectionField", | ||
"GlobalIDFilter", | ||
"GlobalIDMultipleChoiceFilter", | ||
"ArrayFilter", | ||
"ListFilter", | ||
"RangeFilter", | ||
"TypedFilter", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from django_filters.constants import EMPTY_VALUES | ||
|
||
from .typed_filter import TypedFilter | ||
|
||
|
||
class ArrayFilter(TypedFilter): | ||
""" | ||
Filter made for PostgreSQL ArrayField. | ||
""" | ||
|
||
def filter(self, qs, value): | ||
""" | ||
Override the default filter class to check first whether the list is | ||
empty or not. | ||
This needs to be done as in this case we expect to get the filter applied with | ||
an empty list since it's a valid value but django_filter consider an empty list | ||
to be an empty input value (see `EMPTY_VALUES`) meaning that | ||
the filter does not need to be applied (hence returning the original | ||
queryset). | ||
""" | ||
if value in EMPTY_VALUES and value != []: | ||
return qs | ||
if self.distinct: | ||
qs = qs.distinct() | ||
lookup = "%s__%s" % (self.field_name, self.lookup_expr) | ||
qs = self.get_method(qs)(**{lookup: value}) | ||
return qs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
from django_filters import Filter, MultipleChoiceFilter | ||
|
||
from graphql_relay.node.node import from_global_id | ||
|
||
from ...forms import GlobalIDFormField, GlobalIDMultipleChoiceField | ||
|
||
|
||
class GlobalIDFilter(Filter): | ||
""" | ||
Filter for Relay global ID. | ||
""" | ||
|
||
field_class = GlobalIDFormField | ||
|
||
def filter(self, qs, value): | ||
""" Convert the filter value to a primary key before filtering """ | ||
_id = None | ||
if value is not None: | ||
_, _id = from_global_id(value) | ||
return super(GlobalIDFilter, self).filter(qs, _id) | ||
|
||
|
||
class GlobalIDMultipleChoiceFilter(MultipleChoiceFilter): | ||
field_class = GlobalIDMultipleChoiceField | ||
|
||
def filter(self, qs, value): | ||
gids = [from_global_id(v)[1] for v in value] | ||
return super(GlobalIDMultipleChoiceFilter, self).filter(qs, gids) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from .typed_filter import TypedFilter | ||
|
||
|
||
class ListFilter(TypedFilter): | ||
""" | ||
Filter that takes a list of value as input. | ||
It is for example used for `__in` filters. | ||
""" | ||
|
||
def filter(self, qs, value): | ||
""" | ||
Override the default filter class to check first whether the list is | ||
empty or not. | ||
This needs to be done as in this case we expect to get an empty output | ||
(if not an exclude filter) but django_filter consider an empty list | ||
to be an empty input value (see `EMPTY_VALUES`) meaning that | ||
the filter does not need to be applied (hence returning the original | ||
queryset). | ||
""" | ||
if value is not None and len(value) == 0: | ||
if self.exclude: | ||
return qs | ||
else: | ||
return qs.none() | ||
else: | ||
return super(ListFilter, self).filter(qs, value) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from django.core.exceptions import ValidationError | ||
from django.forms import Field | ||
|
||
from .typed_filter import TypedFilter | ||
|
||
|
||
def validate_range(value): | ||
""" | ||
Validator for range filter input: the list of value must be of length 2. | ||
Note that validators are only run if the value is not empty. | ||
""" | ||
if len(value) != 2: | ||
raise ValidationError( | ||
"Invalid range specified: it needs to contain 2 values.", code="invalid" | ||
) | ||
|
||
|
||
class RangeField(Field): | ||
default_validators = [validate_range] | ||
empty_values = [None] | ||
|
||
|
||
class RangeFilter(TypedFilter): | ||
field_class = RangeField |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from django_filters import Filter | ||
|
||
from graphene.types.utils import get_type | ||
|
||
|
||
class TypedFilter(Filter): | ||
""" | ||
Filter class for which the input GraphQL type can explicitly be provided. | ||
If it is not provided, when building the schema, it will try to guess | ||
it from the field. | ||
""" | ||
|
||
def __init__(self, input_type=None, *args, **kwargs): | ||
self._input_type = input_type | ||
super(TypedFilter, self).__init__(*args, **kwargs) | ||
|
||
@property | ||
def input_type(self): | ||
input_type = get_type(self._input_type) | ||
if input_type is not None: | ||
if not callable(getattr(input_type, "get_type", None)): | ||
raise ValueError( | ||
"Wrong `input_type` for {}: it only accepts graphene types, got {}".format( | ||
self.__class__.__name__, input_type | ||
) | ||
) | ||
return input_type |
Oops, something went wrong.