MongoEngine - 如何在转换为json时有效地使用List Field

时间:2016-02-07 20:51:11

标签: mongodb mongoengine flask-mongoengine

class Parent(document):
    name = StringField()
    children = ListField(ReferenceField('Child'))

class Child(document):
    name = StringField()
    parents = ListField(ReferenceField(Parent))

@app.route('/home/')
def home():
    parents = Parent.objects.all()
    return render_template('home.html', items=parents)

我有两个类似于上面的集合,它们保持着多对多的关系。

在使用Angular的模板中,我将javascript变量设置为父类列表,如下所示:

$scope.items = {{ parents|tojson }};

这会产生一组父母,他们chilren是一个Object Ids(引用)数组,而不是解除引用的child个对象:

$scope.items = [{'$oid': '123', 'name': 'foo', 'children': [{'$oid': '456'}]}];

我希望这个角对象包含所有被解除引用的子对象。有没有一种有效的方法呢?

到目前为止,这是唯一适合我的方法,在O(n ^ 3)。为清晰起见,我已将列表理解最小化。将obj['_id'] = {'$oid': str(obj['_id']}转换为可以序列化为json的东西需要多个ObjectId

@app.route('/home/')
def home():
    parents = Parent.objects.all()
    temps = []
    for parent in parents:
        p = parent.to_mongo()
        # At this point, the children of parent and p are references only
        p['_id'] = {'$oid': str(p['_id'])
        temp_children = []
        for child in parent.children:
            # Now the child is dereferenced
            c = child.to_mongo()
            c['_id'] = {$oid': str(c['_id'])}
            # Children have links back to Parent. Keep these as references.
            c['parents'] = [{'oid': str(parent_ref)} for parent_ref in c['parents']]
            temp_children.append(c)

        p['children'] = temp_children
        temps.append(parent.to_mongo())

    return render_template('home.html', items=temps)            

以下内容不起作用,但导致未解除引用的儿童:

json.loads(json.dumps(accounts))

1 个答案:

答案 0 :(得分:2)

因为您只是将子项存储为引用,所以在使用QuerySet.all方法时,您总是必须返回到服务器以取消引用它们。 mongodb的人都知道在使用像pymongo这样的驱动程序时这是一个很大的性能问题,所以他们有一个aggregation framework允许你在服务器上进行解除引用。

使用mongoengine的文档是pretty poor,但请查看unit tests in the mongoengine source帮助填写空白。

this answer的帮助下,如果您使用的是mongodb 3.2或更高版本,那么您可以按照以下方式实现您的目标:

import mongoengine as db
from bson.json_util import dumps

class Parent(db.Document):
    name = db.StringField()
    children = db.ListField(db.ReferenceField('Child'))


class Child(db.Document):
    name = db.StringField()
    parents = db.ListField(db.ReferenceField(Parent))


pipeline = [{"$unwind": "$children"},
            {"$lookup":
                 {"from": "child",
                  "localField": "children",
                  "foreignField": "_id",
                  "as": "children"
                  }
             },
            {"$group": {
                "_id": "$_id",
                "name": {"$first": "$name"},
                "children": {"$push": "$children"}
            }
            }
            ]


@app.route('/home/')
def home():
    parents = []
    for p in Parent.objects.aggregate(*pipeline):
        parents.append(p)
    items= dumps(parents)
    return render_template('home.html', items=items)

然后在home.html中,您只需要:

$scope.items = {{ items }};

这里的管道基本步骤是:

  1. 展开孩子:为children数组
  2. 中的每个子元素创建单独的文档
  3. 查找孩子:根据child转到_id集合并查找,并将结果存储在每个文档的children字段中。基本上用匹配的文档替换ObjectID。
  4. 将结果分组:按_id并根据分组中的第一项添加name,并将所有子字段推送到名为children
  5. 的字段中

    $lookup仅适用于mongodb 3.2,如果您需要运行早期版本的mongodb,那么您将别无选择,只能进行多次查询。此外,$lookup不适用于分片集合。