Aggregating 3 MongoDB collections - (Python, Flask, Jinja)

醉酒当歌 提交于 2020-05-16 02:21:09

问题


Thanks to great help found here I managed to put in place some aggregation for 2 collections in MongoDB.

@app.route("/perfume/<id>", methods=["POST", "GET"])
def perfume(id):
    perfume = mongo.db.perfumes.find_one({"_id": ObjectId(id)})
    form = AddReviewForm()
    cur = mongo.db.perfumes.aggregate(
        [
            {
                "$lookup": {
                    "from": "users",
                    "localField": "author",
                    "foreignField": "username",
                    "as": "creator",
                }
            },
            {"$unwind": "$creator"},
            {
                "$project": {
                    "_id": "$_id",
                    "perfumeName": "$name",
                    "perfumeBrand": "$brand",
                    "perfumeDescription": "$description",
                    "date_updated": "$date_updated",
                    "perfumePicture": "$picture",
                    "isPublic": "$public",
                    "perfumeType": "$perfume_type",
                    "username": "$creator.username",
                    "firstName": "$creator.first_name",
                    "lastName": "$creator.last_name",
                    "profilePicture": "$creator.avatar",
                }
            },
            {"$match": {"_id": ObjectId(id)}},
        ]
    )
    return render_template(
        "perfume.html", title="Perfumes", cursor=cur, perfume=perfume, form=form
    )

I now would like to add a third collection to that aggregate.

My goal is the following: I have now 3 collections, perfumes, users and reviews and I want to display in a template a perfume (a document from the perfumes collection), data from the creator (which is a document in the users collection) and data from the review (a document from the reviews collection).

I'm currently managing to do the first two, but I'm adding the 'review perfume' functionality now.

Since reviewers are also users, I would like to aggregate this new collection to the current lookup.

This collection (reviews) has only 3 fields: _id, review_author and review. The review_author will necessarily be present in the users collection, since they have to be logged in to review.

I also managed to insert the _id of the review document (from the reviews collection) as an array element in the perfumes collection. How's the new aggregate lookup I need in order to have in the template data from all three collections? I'd be so happy to read help!

Currently my schemas are as follow:

{
    "perfumes": {
        "_id": "_id",
        "author": "<string>",  <== used to unwind with following collection
        "brand": "<string>",
        "name": "<string>",
        "perfume_type": "<string>",
        "description": "<text field>",
        "date_updated": "<date>",
        "public": "<boolean>",
        "picture": "<string>",
        "review": "<array>"
    },
    "users": {
        "_id": "_id",
        "username": "<string>",  <== used to unwind to previous coll, needs to link to next
        "first_name": "<string>",
        "last_name": "<string>",
        "email": "<string>",
        "is_admin": "<boolean>",
        "avatar": "<string>"
    },
    "reviews": {
        "_id": "_id",
        "review_author": "<string>", <== to be used as a link
        "review": "<text field>"
    },
}

Thank you!

UPDATE:

This is a sample document from perfumes

[
    {
        "_id": {
            "$oid": "5ebd751e0a52cd0dade39491"
        },
        "author": "Guillermo",
        "brand": "A new Brand",
        "name": "A new Perfume",
        "perfume_type": "Oriental",
        "description": "<p>This is the description</p>",
        "date_updated": {
            "$date": "2020-05-14T16:43:10.801Z"
        },
        "public": false,
        "picture": "generic.png",
        "review": [
            {
                "$oid": "5ebd752c0a52cd0dade39492"
            }
        ]
    }
]

This is a sample document from users:

[
    {
        "_id": {
            "$oid": "5eb09b0a7b62cdd2e8800d96"
        },
        "username": "Guillermo",
        "first_name": "Guillermo",
        "last_name": "Brachetta",
        "email": "brachetta@me.com",
        "password": "pbkdf2:sha256:150000$4vanQ3Qe$83031f081327e26c1125b861054f1d184bdb4373af323e6a10602587f6d40607",
        "is_admin": true,
        "avatar": "a92de23ae01cdfde.jpg"
    }
]

And this is a sample document from reviews:

[
    {
        "_id": {
            "$oid": "5ebd752c0a52cd0dade39492"
        },
        "review_author": "Guillermo",
        "review": "<p>This is one review</p>"
    }
]

So one user can create many perfumes. A perfume can have many reviews. Different users can create reviews for the same or different perfumes.

UPDATE 2:

This is looking very promising and I'm super excited about it! I'll make a short schema of what I'm aiming to do in the template:

{% for item in cursor &}
  <perfume name>
  <perfume brand>
  <perfume description>
  <perfume picture>
  <author username (of the perfume)>
  <author first and last names>
  <date created>
  <eventually other information>
{% if current_user == author %}
  <button edit>
  <button delete>
{% endif %}
  <button review <=+ available to all logged in users>
{% endfor %}

{% for item in cursor['review'] <= this is not working %}
  <perfume review>
  <perfume review author>
  <perfume review author avatar>
  <eventually other information>
{% endfor %}

With the double aggregation I was achieving all of it except obviously the looping over the reviews. Looking forward, I'm sure this is getting somewhere!! :) P.S.: This is just very schematic to try to be clear with the idea!


回答1:


Try the below code:

cur = db.perfumes.aggregate([
    {
        '$lookup': {
            'from': 'users',
            'localField': 'author',
            'foreignField': 'username',
            'as': 'creator'
        }
    },
    {
        '$unwind': '$creator'
    },
    {
        '$unwind': {
            'path': '$review',
            'preserveNullAndEmptyArrays': True
        }
    },
    {
        '$lookup': {
            'from': 'review',
            'let': {'rid': {'$toString': '$review._id'}},
            'pipeline': [{
                '$match': {
                    '$expr': {
                        '$eq': ['$$rid', {'$toString': '$_id'}]
                    }
                }
            }],
            'as': 'userReviews'
        }
    },
    {
        '$unwind': '$userReviews'
    },
    {
        '$lookup': {
            'from': 'users',
            'localField': 'userReviews.review_author',
            'foreignField': 'username',
            'as': 'reviewUserInfo'
        }
    },
    {
        '$unwind': '$reviewUserInfo'
    },
    {
        '$group': {
            '_id':'$_id',
            'perfumeName': {'$first': '$name'},
            'perfumeBrand': {'$first': '$brand'},
            'perfumeDescription': {'$first': '$description'},
            'date_updated': {'$first': '$date_updated'},
            'perfumePicture': {'$first': '$picture'},
            'isPublic': {'$first': '$public'},
            'perfumeType': {'$first': '$perfume_type'},
            'username': {'$first': '$creator.username'},
            'firstName': {'$first': '$creator.first_name'},
            'lastName': {'$first': '$creator.last_name'},
            'profilePicture': {'$first': '$creator.avatar'},
            'userReviews': {
                '$push': {
                    'reviewId': '$userReviews._id',
                    'review_author': '$userReviews.review_author',
                    'review': '$userReviews.review',
                    'review_author_first_name': '$reviewUserInfo.first_name',
                    'review_author_last_name': '$reviewUserInfo.last_name',
                    'review_author_email': '$reviewUserInfo.email',
                    'review_author_avatar': '$reviewUserInfo.avatar'
                }
            }
        }
    },
])

Output:

{
    "_id" : ObjectId("5ebd7eec93256dc00c8f6f6c"),
    "perfumeName" : "A new Perfume",
    "perfumeBrand" : "A new Brand",
    "perfumeDescription" : "<p>This is the description</p>",
    "date_updated" : {
            "$date" : "2020-05-14T16:43:10.801Z"
    },
    "perfumePicture" : "generic.png",
    "isPublic" : false,
    "perfumeType" : "Oriental",
    "username" : "Guillermo",
    "firstName" : "Guillermo",
    "lastName" : "Brachetta",
    "profilePicture" : null,
    "userReviews" : [
        {
            "reviewId" : ObjectId("5ebd752c0a52cd0dade39492"),
            "review_author" : "Guillermo",
            "review" : "<p>This is one review</p>",
            "review_author_first_name" : "Guillermo",
            "review_author_last_name" : "Brachetta",
            "review_author_email" : "brachetta@me.com",
            "review_author_avatar" : "a92de23ae01cdfde.jpg"
        }
    ]
}

P.S. - You can use $match operator to match a particular user, as you have done in OP and you can use $project operator to project more fields and get the required output.



来源:https://stackoverflow.com/questions/61801980/aggregating-3-mongodb-collections-python-flask-jinja

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!