Cl0udSt0ne
Cl0udSt0ne

Reputation: 538

Flask_Mongoengine: Not a mongoengine object after using aggregate

When use aggregate with mongoengine, it return a CommandCursor instead of mongoengine object list, which mean that the mongonengine is not really be used,

For example: if some document doesn't has a title field, a error will be raised. How can I convert my results to mongoengine object?

class Post(Document):
    title = StringField(max_length=120, required=True)
    author = ReferenceField(User)

Host.objects()
# [<Post: Post object>, <Post: Post object>, ...]

pipeline = [
    {
        "$match": {
            'types': type,
        }
    },
    {
        "$project": {
            "name": 1,
            'brating': {
                "$divide": [
                    {"$add": ["$total_score", 60]},
                    {"$add": ["$total_votes", 20]}
                ]
            }
        }
    },
    {"$sort": {"brating": -1}},
    {"$limit": 100}

]

Host.objects.aggregate(*pipeline)
# <class 'pymongo.command_cursor.CommandCursor'>

list(Host.objects.aggregate(*pipeline))
# <class 'list'>

Upvotes: 1

Views: 913

Answers (1)

Steve Rossiter
Steve Rossiter

Reputation: 2925

The aggregate function is just a shortcut to the underlying pymongo function.

The documents that come back from aggregate may involve some $group or other stage that means they bear no relation to your object model so mongoengine couldn't convert them to mongoengine objects.

In the case of your pipeline you are using a $project stage to return a new type of document which only has name and brating fields.

Mongoengine isn't going to be able to do what you want here so you have a couple options:

  • Store the brating field on the Post documents. Initialise the rating at 0 when the post is created and when $total_score or $total_votes are updated, also update the rating.

  • Accept that you are getting back non-mongoengine objects and handle them accordingly. The cursor will yield normal python dictionaries which you can then access the fields post['name'] or post['brating'] in your client code.

  • Use a normal .objects query and sort on the client side.

The final step will obliviously be a problem if you have lots of documents but for a small number try something like:

posts = Post.objects(types=type).only("name", "total_score", "total_votes")
top_posts = sorted(list(posts),key=lambda p: (p.total_score+60)/(p.total_votes+20))[:100]

Upvotes: 1

Related Questions