graphene-mongo icon indicating copy to clipboard operation
graphene-mongo copied to clipboard

Filtering by id for MongoengineConnectionField returns edge with null

Open Gentatsu opened this issue 5 years ago • 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
        }
      ]
    }
  }
}```

Gentatsu avatar May 27 '20 08:05 Gentatsu

can you show your type class and resolver?

DonQueso89 avatar May 27 '20 10:05 DonQueso89

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)

Gentatsu avatar May 27 '20 10:05 Gentatsu

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

DonQueso89 avatar May 27 '20 12:05 DonQueso89

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.

Gentatsu avatar May 27 '20 13:05 Gentatsu

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

DonQueso89 avatar May 27 '20 13:05 DonQueso89

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.

Gentatsu avatar May 27 '20 14:05 Gentatsu

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

DonQueso89 avatar May 27 '20 16:05 DonQueso89

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?

Gentatsu avatar Jun 06 '20 09:06 Gentatsu

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

DonQueso89 avatar Jun 06 '20 12:06 DonQueso89

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.

AyluinReymaer avatar Oct 23 '20 12:10 AyluinReymaer

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.

AyluinReymaer avatar Oct 23 '20 13:10 AyluinReymaer