Repository: graphql-python/graphene-django

Issue:
Can't use GlobalIDFilter as FilterSet field
I noticed that when I was trying to use a `GlobalIDFilter` as a FilterSet field, it doesn't call the filter method (both my custom method `filter_table_id` and `GlobalIDFilter.filter` is not called) and returns all of the records in the table. If I use a CharFilter instead of a GlobalIDFilter, it works and filters as expected.

This works. But type is `String` in queries.
```python
class ModelFilter(FilterSet):
    def filter_table_id(self, queryset, name, value):
        _, _id = from_global_id(value)
        return queryset.filter(table1__table2__table3__id=_id)

    table_id = CharFilter(method='filter_table_id')

    class Meta:
        model = DBModel
        fields = {
            'name': ['exact', 'icontains'],
            'table_id': ['exact'],
        }
```

But if I replace the `CharFilter` with `GlobalIDFilter`, the type is `ID` in the query, but all the records are returned, none are filtered out.
```python
class ModelFilter(FilterSet):
    def filter_table_id(self, queryset, name, value):
        _, _id = from_global_id(value)
        return queryset.filter(table1__table2__table3__id=_id)

    table_id = GlobalIDFilter(method='filter_table_id')

    class Meta:
        model = DBModel
        fields = {
            'name': ['exact', 'icontains'],
            'table_id': ['exact'],
        }
```

I tried to see why it was not working, but I could not find out why the filter function nor my `filter_table_id` was being called.
Am I using the wrong thing for filtering?
Is there a better way to do what I want?

I am using venv with:
 * Python 3.7.0 (default, Sep 12 2018, 18:30:08), the default Python3.7 Ubuntu 18.04 package.
 * graphene-django==2.2.0
 * django-filter==2.0.0
 * graphene==2.1.3


Discussion:
I have a similar problem with `MultipleChoiceFilter`.
In my case it returns an empty queryset as soon as I use the `MultipleChoiceFilter` field in the query. If I don't use it or use a `CharFilter` or `NumberFilter` it works again.
Also if I change `MultipleChoiceFilter` to `CharFilter` it also works.
@SN9NV I found the source of the problem
https://github.com/carltongibson/django-filter/issues/1078
I believe this is an issue with django-filter and not graphene-django. Can we close this?
This is an issue in graphene-django, although django-filter's docs should be updated to discuss error handling. The short version is that the view should determine how to handle errors, checking whether the `filterset.is_valid()` and then what to do with the `filterset.errors`. Note that this is basically the same API as Django forms, which are used internally.
@rpkilby can you point me to the area in the code that is causing this bug so we can triage this issue?
I'm not that familiar with graphene-django, but the below is where the filterset is created and called:
https://github.com/graphql-python/graphene-django/blob/dc561c68c49d1a91637503f765857d819c36769a/graphene_django/filter/fields.py#L92-L96

Does it make sense for `DjangoFilterConnectionField.connection_resolver` to raise validation errors? If so, then the code should look like:

```python
filterset = filterset_class(
    data=filter_kwargs,
    queryset=default_manager.get_queryset(),
    request=info.context,
)
if filterset.is_valid():
    return filterset.qs

# I don't know how validation is handled in graphene, but you'd need to convert these 
# validation errors to the equivalent in graphene, and return/raise appropriately.
do_something_with(filterset.errors)


```
@rpkilby I think it makes sense. 

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.


Resolving patch:
diff --git a/graphene_django/filter/fields.py b/graphene_django/filter/fields.py
--- a/graphene_django/filter/fields.py
+++ b/graphene_django/filter/fields.py
@@ -1,6 +1,7 @@
 from collections import OrderedDict
 from functools import partial
 
+from django.core.exceptions import ValidationError
 from graphene.types.argument import to_arguments
 from ..fields import DjangoConnectionField
 from .utils import get_filtering_args_from_filterset, get_filterset_class
@@ -59,7 +60,12 @@ def resolve_queryset(
             connection, iterable, info, args
         )
         filter_kwargs = {k: v for k, v in args.items() if k in filtering_args}
-        return filterset_class(data=filter_kwargs, queryset=qs, request=info.context).qs
+        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())
 
     def get_queryset_resolver(self):
         return partial(


Test patch:
diff --git a/graphene_django/filter/tests/test_fields.py b/graphene_django/filter/tests/test_fields.py
--- a/graphene_django/filter/tests/test_fields.py
+++ b/graphene_django/filter/tests/test_fields.py
@@ -400,6 +400,114 @@ def test_global_id_field_relation():
     assert id_filter.field_class == GlobalIDFormField
 
 
+def test_global_id_field_relation_with_filter():
+    class ReporterFilterNode(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            filter_fields = ["first_name", "articles"]
+
+    class ArticleFilterNode(DjangoObjectType):
+        class Meta:
+            model = Article
+            interfaces = (Node,)
+            filter_fields = ["headline", "reporter"]
+
+    class Query(ObjectType):
+        all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
+        all_articles = DjangoFilterConnectionField(ArticleFilterNode)
+        reporter = Field(ReporterFilterNode)
+        article = Field(ArticleFilterNode)
+
+    r1 = Reporter.objects.create(first_name="r1", last_name="r1", email="r1@test.com")
+    r2 = Reporter.objects.create(first_name="r2", last_name="r2", email="r2@test.com")
+    Article.objects.create(
+        headline="a1",
+        pub_date=datetime.now(),
+        pub_date_time=datetime.now(),
+        reporter=r1,
+        editor=r1,
+    )
+    Article.objects.create(
+        headline="a2",
+        pub_date=datetime.now(),
+        pub_date_time=datetime.now(),
+        reporter=r2,
+        editor=r2,
+    )
+
+    # Query articles created by the reporter `r1`
+    query = """
+    query {
+        allArticles (reporter: "UmVwb3J0ZXJGaWx0ZXJOb2RlOjE=") {
+            edges {
+                node {
+                    id
+                }
+            }
+        }
+    }
+    """
+    schema = Schema(query=Query)
+    result = schema.execute(query)
+    assert not result.errors
+    # We should only get back a single article
+    assert len(result.data["allArticles"]["edges"]) == 1
+
+
+def test_global_id_field_relation_with_filter_not_valid_id():
+    class ReporterFilterNode(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            filter_fields = ["first_name", "articles"]
+
+    class ArticleFilterNode(DjangoObjectType):
+        class Meta:
+            model = Article
+            interfaces = (Node,)
+            filter_fields = ["headline", "reporter"]
+
+    class Query(ObjectType):
+        all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
+        all_articles = DjangoFilterConnectionField(ArticleFilterNode)
+        reporter = Field(ReporterFilterNode)
+        article = Field(ArticleFilterNode)
+
+    r1 = Reporter.objects.create(first_name="r1", last_name="r1", email="r1@test.com")
+    r2 = Reporter.objects.create(first_name="r2", last_name="r2", email="r2@test.com")
+    Article.objects.create(
+        headline="a1",
+        pub_date=datetime.now(),
+        pub_date_time=datetime.now(),
+        reporter=r1,
+        editor=r1,
+    )
+    Article.objects.create(
+        headline="a2",
+        pub_date=datetime.now(),
+        pub_date_time=datetime.now(),
+        reporter=r2,
+        editor=r2,
+    )
+
+    # Filter by the global ID that does not exist
+    query = """
+    query {
+        allArticles (reporter: "fake_global_id") {
+            edges {
+                node {
+                    id
+                }
+            }
+        }
+    }
+    """
+    schema = Schema(query=Query)
+    result = schema.execute(query)
+    assert "Invalid ID specified." in result.errors[0].message
+
+
 def test_global_id_multiple_field_implicit():
     field = DjangoFilterConnectionField(ReporterNode, fields=["pets"])
     filterset_class = field.filterset_class