graphql-python/graphene-mongo

Filtering by id for MongoengineConnectionField returns edge with null

Gentatsu opened this issue · 11 comments

When I try to filter by id using MongoengineConnectionField, using this query:

{tasks(id:"5ecd4880f80bde8199d3d005") {
  edges {  
    node { 
      id 
      state
    }
  } 
}}```

returns this:

```{
  "data": {
    "tasks": {
      "edges": [
        {
          "node": null
        }
      ]
    }
  }
}```

can you show your type class and resolver?

class Task(Document):

    meta = {"collection": "tasks", "strict": False}
    dateStarted = DateTimeField(default=datetime.now)
    state = StringField()
class CustomNode(Node):
    class Meta:
        name = 'Node'

    @staticmethod
    def to_global_id(type, id):
        return id
class Task_T(MongoengineObjectType):
    class Meta:
        model = Task
        interfaces = (CustomNode,)

 tasks = MongoengineConnectionField(Task_T)

you should either

  1. Define get_node_from_global_id as mentioned here https://docs.graphene-python.org/en/latest/relay/nodes/#custom-nodes
    or
  2. Implement a resolver for your field on the parent object that takes into account the id filter

Not sure if this belongs in the issues sections of this repo

A custom resolver defeats the purpose of the MongoengineConnectionField. I like being able to expose the model and filter on any item without explicitly defining them in resolver.

The get_node_from_global_id was the kicker. It's not very well defined in that example as it forces me to define the type. With a bit of cheeky debugging, I found a way to resolve generic types:

    @staticmethod
    def get_node_from_global_id(info, global_id, only_type=None):
        model = getattr(info.parent_type.graphene_type, info.field_name).model
        return model.objects.get(id=global_id)

Hope this helps someone in the future. I think it should be an issue as there are many instances where people want to resolve non-global ids and filter on them.

yeah having a generic get_node_from_global_id is simple as long as you are doing a plain get, once you get into embedded documents and nesting thats where it becomes harder

I've got both in mine actually! You're very correct! I tried filtering ids on an embedded document w/ a different field name.

I've modified it to this to work generically:

 @staticmethod
    def get_node_from_global_id(info, global_id, only_type=None):
        model = info.return_type.graphene_type.Edge.node.type._meta.model
        return model.objects.get(id=global_id)

Tested on embedded field + list of reference fields and seems to work.

how does this work on EmbeddedDocuments when they dont have an objects property?

You're right. This does not work on filtering EmbeddedDocuents. I needed to look at the last several records of an EmbeddedDocument, and it kept complaining about not having a pk property. I tried adding in a primary key w/ initialisating an ObjectId, setting primary_key, unique, required to True but to no avail.

I've tried it with both EmbeddedListField and ListField(EmbeddedDocument).

Not sure if I should create a separate issue for this?

This is because graphene_mongo expects the pk property to be present on your models (for some reason), if your embedded document has a natural/surrogate key, then you can add an alias called pk on your EmbeddedDocument subclass for this key as a workaround:

@property
def pk(self):
    return self.id

replacing id with the name of the attr you want to use

Hello, is there any progress or suggestions in how to filter EmbeddedDocuments?

I have been trying for a while now and I've stumbled upon this open issue for the same thing.

I have changed a few lines in the fields.py file like so:

# This is line 221 in the file
if callable(getattr(self.model, "objects", None)):
    iterables = self.get_queryset(self.model, info, **args)
    list_length = iterables.count()
else:
    iterables = getattr(_root, info.field_name, [])
    _args = args.copy()
    # I don't know what the "pk__in" argument does so I delete this argument in the copy made so that it doesn't cause conflicts.
    del _args["pk__in"]
    # I don't know if there is a more efficient way of doing this search
    for arg_name, arg in _args.items():
        iterables = [_object for _object in iterables if getattr(_object, arg_name, None) == arg]
    list_length = len(iterables)

Right now, it works and have not ran into any issues while doing a few tests.