MongoEngine - 如何在转换为 json 时有效地引用列表字段
MongoEngine - How to deference a List Field efficiently when converting to json
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
是对象 ID(引用)数组,而不是取消引用的 child
对象:
$scope.items = [{'$oid': '123', 'name': 'foo', 'children': [{'$oid': '456'}]}];
我希望这个 angular 对象包含所有取消引用的子项。有没有有效的方法来做到这一点?
到目前为止,这是唯一适合我的方法,时间复杂度为 O(n^3)。为了清楚起见,我已经最小化了列表理解。多个 obj['_id'] = {'$oid': str(obj['_id']}
是将 ObjectId
转换为可以序列化为 json.
的必要条件
@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))
因为您只将子项存储为引用,所以在使用上面的 QuerySet.all
方法时,您总是必须返回服务器以取消引用它们。 mongodb 的人知道,当使用像 pymongo 这样的驱动程序时,这是一个很大的性能问题,所以他们有一个 aggregation framework 允许您在服务器上进行取消引用。
与 mongoengine 一起使用的文档是 pretty poor but a look at the unit tests in the mongoengine source 帮助填补空白。
在 和 的帮助下,如果您使用的是 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 }};
此处管道中的基本步骤是:
- 展开子项:为
children
数组中的每个子元素创建一个单独的文档
- 查找子项:转到
child
集合并根据_id
进行查找,并将结果存储在每个文档的children
字段中。本质上是用匹配的文档替换 ObjectID。
- 对结果进行分组:按
_id
并根据分组中的第一项包含 name
,并将所有子字段推送到名为 children
[=44= 的字段中]
$lookup 仅适用于 mongodb 3.2,如果您需要 运行 mongodb 的早期版本,那么您别无选择,只能制作多个查询。此外,$lookup
不适用于分片集合。
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
是对象 ID(引用)数组,而不是取消引用的 child
对象:
$scope.items = [{'$oid': '123', 'name': 'foo', 'children': [{'$oid': '456'}]}];
我希望这个 angular 对象包含所有取消引用的子项。有没有有效的方法来做到这一点?
到目前为止,这是唯一适合我的方法,时间复杂度为 O(n^3)。为了清楚起见,我已经最小化了列表理解。多个 obj['_id'] = {'$oid': str(obj['_id']}
是将 ObjectId
转换为可以序列化为 json.
@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))
因为您只将子项存储为引用,所以在使用上面的 QuerySet.all
方法时,您总是必须返回服务器以取消引用它们。 mongodb 的人知道,当使用像 pymongo 这样的驱动程序时,这是一个很大的性能问题,所以他们有一个 aggregation framework 允许您在服务器上进行取消引用。
与 mongoengine 一起使用的文档是 pretty poor but a look at the unit tests in the mongoengine source 帮助填补空白。
在
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 }};
此处管道中的基本步骤是:
- 展开子项:为
children
数组中的每个子元素创建一个单独的文档 - 查找子项:转到
child
集合并根据_id
进行查找,并将结果存储在每个文档的children
字段中。本质上是用匹配的文档替换 ObjectID。 - 对结果进行分组:按
_id
并根据分组中的第一项包含name
,并将所有子字段推送到名为children
[=44= 的字段中]
$lookup 仅适用于 mongodb 3.2,如果您需要 运行 mongodb 的早期版本,那么您别无选择,只能制作多个查询。此外,$lookup
不适用于分片集合。