Repository: graphql-python/graphene-django

Issue:
Using exact filter on Relay with ForeignKey model
I found a problem when I try to filter a model with ForeignKey. I expect to get the result below if the ID is incorrect.
```
{
    "data": {
        "forms": {
            "edges": []
        }
    }
}
```
However, I get every single row in my table returned if the value of ID is incorrect. Filtering works as expected if the ID exists.

```
# models
class Province(models.Model):
    name = models.CharField(max_length=50)

class City(models.Model):
    province = models.ForeignKey(Province,
                                 on_delete=models.CASCADE,
                                 related_name='city')
    name = models.CharField(max_length=50)
```

```
# schema
class ProvinceNode(DjangoObjectType):
    class Meta:
        model = Province
        filter_fields = {'name': ['exact']}
        interfaces = (relay.Node, )


class CityNode(DjangoObjectType):
    class Meta:
        model = City
        filter_fields = {'province': ['exact']}
        interfaces = (relay.Node, )

cities = DjangoFilterConnectionField(queries.CityNode)
```

```
# query
{
  cities(province: "UHJvdmluY2VOb2RlOjE3") {
    edges {
      node { id name province { id name } }
    }
  }
}
```

Discussion:

Patch:
diff --git a/graphene_django/converter.py b/graphene_django/converter.py
index c84b61a..92963d6 100644
--- a/graphene_django/converter.py
+++ b/graphene_django/converter.py
@@ -255,10 +255,14 @@ def convert_field_to_djangomodel(field, registry=None):
 
 @convert_django_field.register(ArrayField)
 def convert_postgres_array_to_list(field, registry=None):
-    base_type = convert_django_field(field.base_field)
-    if not isinstance(base_type, (List, NonNull)):
-        base_type = type(base_type)
-    return List(base_type, description=field.help_text, required=not field.null)
+    inner_type = convert_django_field(field.base_field)
+    if not isinstance(inner_type, (List, NonNull)):
+        inner_type = (
+            NonNull(type(inner_type))
+            if inner_type.kwargs["required"]
+            else type(inner_type)
+        )
+    return List(inner_type, description=field.help_text, required=not field.null)
 
 
 @convert_django_field.register(HStoreField)
@@ -271,5 +275,9 @@ def convert_postgres_field_to_string(field, registry=None):
 def convert_postgres_range_to_string(field, registry=None):
     inner_type = convert_django_field(field.base_field)
     if not isinstance(inner_type, (List, NonNull)):
-        inner_type = type(inner_type)
+        inner_type = (
+            NonNull(type(inner_type))
+            if inner_type.kwargs["required"]
+            else type(inner_type)
+        )
     return List(inner_type, description=field.help_text, required=not field.null)
diff --git a/graphene_django/filter/fields.py b/graphene_django/filter/fields.py
index a46a4b7..3a98e8d 100644
--- 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 @@ class DjangoFilterConnectionField(DjangoConnectionField):
             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(
diff --git a/setup.py b/setup.py
index 560549a..affaec0 100644
--- a/setup.py
+++ b/setup.py
@@ -48,10 +48,14 @@ setup(
         "Programming Language :: Python :: 2",
         "Programming Language :: Python :: 2.7",
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.4",
-        "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: 3.6",
+        "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: Implementation :: PyPy",
+        "Framework :: Django",
+        "Framework :: Django :: 1.11",
+        "Framework :: Django :: 2.2",
+        "Framework :: Django :: 3.0",
     ],
     keywords="api graphql protocol rest relay graphene",
     packages=find_packages(exclude=["tests"]),


Test patch:
diff --git a/graphene_django/filter/tests/test_fields.py b/graphene_django/filter/tests/test_fields.py
index 166d806..b8ae6fe 100644
--- 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
diff --git a/graphene_django/tests/test_converter.py b/graphene_django/tests/test_converter.py
index 8e4495a..f6e3606 100644
--- a/graphene_django/tests/test_converter.py
+++ b/graphene_django/tests/test_converter.py
@@ -310,6 +310,14 @@ def test_should_postgres_array_convert_list():
     )
     assert isinstance(field.type, graphene.NonNull)
     assert isinstance(field.type.of_type, graphene.List)
+    assert isinstance(field.type.of_type.of_type, graphene.NonNull)
+    assert field.type.of_type.of_type.of_type == graphene.String
+
+    field = assert_conversion(
+        ArrayField, graphene.List, models.CharField(max_length=100, null=True)
+    )
+    assert isinstance(field.type, graphene.NonNull)
+    assert isinstance(field.type.of_type, graphene.List)
     assert field.type.of_type.of_type == graphene.String
 
 
@@ -321,6 +329,17 @@ def test_should_postgres_array_multiple_convert_list():
     assert isinstance(field.type, graphene.NonNull)
     assert isinstance(field.type.of_type, graphene.List)
     assert isinstance(field.type.of_type.of_type, graphene.List)
+    assert isinstance(field.type.of_type.of_type.of_type, graphene.NonNull)
+    assert field.type.of_type.of_type.of_type.of_type == graphene.String
+
+    field = assert_conversion(
+        ArrayField,
+        graphene.List,
+        ArrayField(models.CharField(max_length=100, null=True)),
+    )
+    assert isinstance(field.type, graphene.NonNull)
+    assert isinstance(field.type.of_type, graphene.List)
+    assert isinstance(field.type.of_type.of_type, graphene.List)
     assert field.type.of_type.of_type.of_type == graphene.String
 
 
@@ -341,7 +360,8 @@ def test_should_postgres_range_convert_list():
     field = assert_conversion(IntegerRangeField, graphene.List)
     assert isinstance(field.type, graphene.NonNull)
     assert isinstance(field.type.of_type, graphene.List)
-    assert field.type.of_type.of_type == graphene.Int
+    assert isinstance(field.type.of_type.of_type, graphene.NonNull)
+    assert field.type.of_type.of_type.of_type == graphene.Int
 
 
 def test_generate_enum_name(graphene_settings):