【问题标题】:Update a MongoEngine document using a python dict?使用 python 字典更新 MongoEngine 文档?
【发布时间】:2013-09-25 10:30:05
【问题描述】:

是否可以使用 python dict 更新 MongoEngine 文档?

例如:

class Pets(EmbeddedDocument):
    name = StringField()

class Person(Document):
    name = StringField()
    address = StringField()
    pets = ListField(EmbeddedDocumentField(Pets))

p = Person()
p.update_with_dict({
    "name": "Hank",
    "address": "Far away",
    "pets": [
        {
            "name": "Scooter"
        }
    ]
})

【问题讨论】:

  • 请您更清楚您的问题。通过阅读您在下面的回答,很明显您正在寻找一种允许您更新 EmbeddedDocuments 的解决方案,尽管这应该在问题中说明。
  • @Dawson 不,我想使用 Python 字典更新完整的文档,包括嵌入式文档/列表字段等。
  • 不是更新,而是createp = Person() 使用默认值实例化对象,并且不会从数据库中获取现有对象然后更新属性。

标签: python mongodb mongoengine


【解决方案1】:

这里的游戏已经很晚了,但是 FWIW,MongoEngine 有一个内置的解决方案。

无论您想create 还是update,您都可以执行以下操作:

class Pets(EmbeddedDocument):
    name = StringField()

class Person(Document):
    name = StringField()
    address = StringField()
    pets = ListField(EmbeddedDocumentField(Pets))

p = Person(**{
    "name": "Hank",
    "address": "Far away",
    "pets": [{"name": "Scooter"}]
})
p.save()

update 的唯一区别是您需要坚持使用 id。这样 mongoengine 就不会使用现有的 id 复制文档,而是更新它。

【讨论】:

  • 这是新的解决方案。
  • 当您的文档模型中有主键或唯一约束时,此解决方案将失败。
【解决方案2】:

好的,我刚刚为它做了一个函数。

你称它为update_document(document, data_dict)。它将遍历data_dict 的项目并使用data_dict 的键获取字段实例。然后它将调用field_value(field, value),其中field 是字段实例。 field_value() 将使用 field.__class__ 检查字段的类型,并基于此返回 MongoEngine 期望的值。例如,普通StringField 的值可以按原样返回,但对于EmbeddedDocumentField,需要创建该嵌入文档类型的实例。它还对列表字段中的项目执行此技巧。

from mongoengine import fields


def update_document(document, data_dict):

    def field_value(field, value):

        if field.__class__ in (fields.ListField, fields.SortedListField):
            return [
                field_value(field.field, item)
                for item in value
            ]
        if field.__class__ in (
            fields.EmbeddedDocumentField,
            fields.GenericEmbeddedDocumentField,
            fields.ReferenceField,
            fields.GenericReferenceField
        ):
            return field.document_type(**value)
        else:
            return value

    [setattr(
        document, key,
        field_value(document._fields[key], value)
    ) for key, value in data_dict.items()]

    return document

用法:

class Pets(EmbeddedDocument):
    name = StringField()

class Person(Document):
    name = StringField()
    address = StringField()
    pets = ListField(EmbeddedDocumentField(Pets))

person = Person()

data = {
    "name": "Hank",
    "address": "Far away",
    "pets": [
        {
            "name": "Scooter"
        }
    ]
}

update_document(person, data)

【讨论】:

  • @kovan 你到底是什么意思?当您尝试更新已删除的字段时?如果你需要一个后备,你可以自己写,应该不会太难。
【解决方案3】:

试试类似的东西

p.update(**{
    "set__name": "Hank",
    "set__address": "Far away"
})

【讨论】:

  • 感谢您的回答。这适用于普通字段,但不适用于 EmbeddedDocumentFields。我在我的问题中添加了要求。很抱歉没有立即说清楚。
  • 您可以在更新查询p.update(**{"set__name": "Hank", "set__address": "Far away", 'set__pets__0__name': 'Scooter'})中使用列表项索引来完成@
【解决方案4】:

我已经尝试了上面的大部分答案,但它们似乎都不适用于嵌入式文档。即使他们更新了字段,他们也删除了嵌入式文档中未填充字段的内容。

为此,我决定采用@hckjck 建议的路径,我编写了一个简单的函数,将 dict 转换为格式,以便document.update 可以处理:

def convert_dict_to_update(dictionary, roots=None, return_dict=None):
    """    
    :param dictionary: dictionary with update parameters
    :param roots: roots of nested documents - used for recursion
    :param return_dict: used for recursion
    :return: new dict
    """
    if return_dict is None:
        return_dict = {}
    if roots is None:
        roots = []

    for key, value in dictionary.iteritems():
        if isinstance(value, dict):
            roots.append(key)
            convert_dict_to_update(value, roots=roots, return_dict=return_dict)
            roots.remove(key)  # go one level down in the recursion
        else:
            if roots:
                set_key_name = 'set__{roots}__{key}'.format(
                    roots='__'.join(roots), key=key)
            else:
                set_key_name = 'set__{key}'.format(key=key)
            return_dict[set_key_name] = value

    return return_dict

现在这个数据:

{u'communication': {u'mobile_phone': u'2323232323', 'email':{'primary' : 'email@example.com'}}}

将转换为:

{'set__communication__mobile_phone': u'2323232323', 'set__communication__email__primary': 'email@example.com'}

可以这样使用

document.update(**conv_dict_to_update(data))

也可在此 gist 中找到:https://gist.github.com/Visgean/e536e466207bf439983a

我不知道这有多有效,但它确实有效。

【讨论】:

  • 你对我帮助很大,遗憾的是 mongoengine 没有用户友好的功能
【解决方案5】:

这是一个使用 EmbeddedDocuments 更新文档的功能。它基于@rednaw 的解决方案,但考虑了具有 EmbeddedDocuments 的 EmbeddedDocuments。

from mongoengine.fields import *

def field_value(field, value):
  ''' 
  Converts a supplied value to the type required by the field.
  If the field requires a EmbeddedDocument the EmbeddedDocument
  is created and updated using the supplied data.
  '''
  if field.__class__ in (ListField, SortedListField):
    # return a list of the field values 
    return [
      field_value(field.field, item) 
      for item in value]

  elif field.__class__ in (
    EmbeddedDocumentField,
    GenericEmbeddedDocumentField,
    ReferenceField,
    GenericReferenceField):

    embedded_doc = field.document_type()
    update_document(embedded_doc, value)
    return embedded_doc
  else:
    return value


def update_document(doc, data):
  ''' Update an document to match the supplied dictionary.
  '''
  for key, value in data.iteritems():

    if hasattr(doc, key):
        value = field_value(doc._fields[key], value)
        setattr(doc, key, value)
    else:
        # handle invalid key
        pass

  return doc

这里的关键是field_value 方法更新嵌入文档而不是用数据实例化它。

用法示例:

class Pets(EmbeddedDocument):
    name = StringField()

class Person(EmbeddedDocument):
    name = StringField()
    address = StringField()
    pets = ListField(EmbeddedDocumentField(Pets))

class Group(Document):
    name = StringField()
    members = ListField(EmbeddedDocumentField(Person))

g = Group()

update_document(g, {
  'name': 'Coding Buddies',
  'members': [
    {
      'name': 'Dawson',
      'address': 'Somewhere in Nova Scotia',
      'pets': [
        {
          'name': 'Sparkles'
        }
      ]
    },
    {
      'name': 'rednaw',
      'address': 'Not too sure?',
      'pets': [
        {
          'name': 'Fluffy'
        }
      ]
    }
  ]
})

仅供参考,这实际上是我猫的名字。

编辑:变量名中的错字。

【讨论】:

  • 嘿道森,我的方法到底有什么不适合你的?
  • @rednaw 您的解决方案没有考虑包含嵌入文档的嵌入文档。我正在更新的模型有一个 EmbeddedDocuments 的 ListField。这些 EmbeddedDocuments 本身有一个 EmbeddedDocuments 的 ListField。您的解决方案允许更新包含 EmbeddedDocuments 的 EmbeddedDocuments、ListFields 和 ListFields,但不允许更新包含 EmbeddedDocuments 或 ListFields 的 EmbeddedDocuments。
  • 道森,您的使用示例似乎适用于我的方法。我不明白什么对你不起作用。我用这个脚本测试过:pastebin.com/uMB0t1Xw
  • 不确定这个函数,有一些未定义的变量...例如最后一行的update_entity和entity...
  • 那是一种类型。我在编辑中修复了这个问题。 entity 应该是一个文档。
【解决方案6】:

要将python dict存储为子文档,可以使用mongoengine.fields.DictField

结帐manuals

包装标准 Python 字典的字典字段。这是 类似于嵌入文档,但没有定义结构。

【讨论】:

    【解决方案7】:

    你必须使用 Document 类的from_json 方法。请参阅this。它也适用于 EmbeddedDocumentField

    在你的情况下,你必须这样做

    import json
    
    p = Person.from_json(json.dumps({
        "name": "Hank",
        "address": "Far away",
        "pets": [
            {
                "name": "Scooter"
            }
        ]
    }))
    

    请记住,这仍然没有保存。您可以通过添加p.save()来保存它

    注意:from_json 方法有额外的参数来选择是要创建一个全新的文档还是更新现有的文档。请查看此答案中链接的文档。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-05-28
      • 1970-01-01
      • 2015-01-12
      • 1970-01-01
      • 1970-01-01
      • 2012-12-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多