Search code examples
djangographqlgraphene-pythondjango-filtersvue-apollo

How to stop Graphql + Django-Filters to return All Objects when Filter String is Empty?


Using:

  • Django 3.x [ Django-Filters 2.2.0, graphene-django 2.8.0, graphql-relay 2.0.1 ]
  • Vue 2.x [ Vue-Apollo ]

I have a simple Birds Django-Model with Fields like name, habitat and applied different Filters on these Fields like icontains or iexact. My Goal was to apply a simple search field in my Frontend (Vue). So far it works, but whenever this Filter Value is empty or has blanks (see Example 3), Graphql returns all Objects.

My first approach was on the FrontEnd and to use some kind of logic on my input value, like when String is Empty/blank send isnull=true . But then i thought that Django should handle this in the first place. I guess this Issue relates to my filters (see Django < relay_schema.py) or in other words do i have to apply some kind of logic on these Filters?

In the moment i try to customize some filterset_class but it feels like this would maybe too much, maybe i missed something? So i ask here if maybe someone has some hints, therefore my question is:

How to stop Graphql + Django-Filters to return All Objects when Filter String is Empty?

GraphiQL IDE

Example 1


query {birdsNodeFilter (name_Iexact: "finch") {
  edges {
    node {
      id
      name
    }
     }
    }
}

Returns

{
  "data": {
    "birdsNodeFilter": {
      "edges": [
        {
          "node": {
            "id": "QmlyZHNOb2RlOjE=",
            "name": "Finch",
            "habitat": "Europe"
          }
        }
      ]
    }
  }
}

Fine for me!

Example 2


query {birdsNodeFilter (name_Iexact: "Unicorns") {
  edges {
    node {
      id
      name
      habitat
    }
     }
    }
}

Returns

{
  "data": {
    "birdsNodeFilter": {
      "edges": []
    }
  }
}

No Unicorns there - good

Example 3


query {birdsNodeFilter (name_Iexact: "") {
  edges {
    node {
      id
      name
    }
     }
    }
}

Return

{
  "data": {
    "birdsNodeFilter": {
      "edges": [
        {
          "node": {
            "id": "QmlyZHNOb2RlOjE=",
            "name": "Finch",
            "habitat": "Europe"
          }
        },
        {
          "node": {
            "id": "QmlyZHNOb2RlOjI=",
            "name": "Bald Eagle",
            "habitat": "USA"
          }
        },

<...And so on...>

Not fine for me!

Django

relay_schema.py


class BirdsNode(DjangoObjectType):
    class Meta:
        model = Birds
        filter_fields = {
            'id': ['iexact'],
            'name': ['iexact', 'icontains', 'istartswith', 'isnull'],
            'habitat': ['iexact', 'icontains', 'istartswith'],
        }
        interfaces = (relay.Node, )


class BirdQuery(graphene.ObjectType):
    birdConNode = relay.Node.Field(BirdsNode)
    birdsNodeFilter = DjangoFilterConnectionField(BirdsNode) 

Solution

  • This is my Solution which worked in GraphiQL and in my Frontend VUE. I added a logic to the Birds2Query with def resolve_all_birds2 for each Filter (for testing purpose not on all Filter ) . Besides that i also added a ExtendedConnection for counting.

    Note: i changed the class names from my former Question.

    Update: This Solution works on the python side. But the apollo client provides also the Apollo manager - also known as Dollar Apollo - there you can also use the this.$apollo.queries.tags.skip property as an static or dynamic solution to start and stop queries.

    relay_schema.py

    class ExtendedConnection(Connection):
        class Meta:
            abstract = True
    
        total_count = Int()
        edge_count = Int()
        name_check = ""
    
        def resolve_total_count(root, info, **kwargs):
            return root.length
        def resolve_edge_count(root, info, **kwargs):
            return len(root.edges)
    
    class Birds2Node(DjangoObjectType):
        class Meta:
    
            model = Birds
            filter_fields =  {
                'id':  ['exact', 'icontains'],
                'name': ['exact', 'icontains', 'istartswith', 'iendswith'],
            }
    
            interfaces = (relay.Node, )
            connection_class = ExtendedConnection
    
    class Birds2Query(ObjectType):
        birds2 = relay.Node.Field(Birds2Node)
        all_birds2 = DjangoFilterConnectionField(Birds2Node)
    
    
        def resolve_all_birds2(self, info, **kwargs):
            # Filtering for Empty/ Blank Values in Filter.Key.Value before returning queryset
             if 'name__icontains' in kwargs:
                nameIcon = kwargs['name__icontains']
                nameIconBool = bool(nameIcon.strip()) # if blanks turns False           
                if nameIconBool == False: # has blanks         
                    return Birds.objects.filter(name=None)
                pass
    
        
            if 'name__istartswith' in kwargs:           
                nameIsta = kwargs['name__istartswith']
                nameIstaBool = bool(nameIsta.strip()) # if blanks turns False          
                if nameIstaBool == False: # has blanks         
                    return Birds.objects.filter(name=None)
                pass
             return
    

    GraphiQL Blockquote

    Example 1

    query {allBirds2 (name_Icontains:""){
      totalCount
        edgeCount
        edges {
          node {
            id
            name
            habitat
          }  
        }
      }
    }
    

    Stopped to return all Objects while Filter is blank.

    {
      "data": {
        "allBirds2": {
          "totalCount": 0,
          "edgeCount": 0,
          "edges": []
        }
      }
    }
    

    Example 2 with blanks and one letter

    query {allBirds2 (name_Icontains:" f  "){
      totalCount
        edgeCount
        edges {
          node {
            id
            name
            habitat
          }  
        }
      }
    }
    

    return - exactly what i wanted

    {
      "data": {
        "allBirds2": {
          "totalCount": 1,
          "edgeCount": 1,
          "edges": [
            {
              "node": {
                "id": "QmlyZHMyTm9kZTox",
                "name": "Finch",
                "habitat": "Europe"
              }
            }
          ]
        }
      }
    }