使用python dict更新MongoEngine文档?

时间:2013-09-25 10:30:05

标签: python mongodb mongoengine

是否可以使用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"
        }
    ]
})

6 个答案:

答案 0 :(得分:7)

好的,我刚刚为它做了一个功能。

你称之为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)

答案 1 :(得分:4)

尝试更像这样的东西

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

答案 2 :(得分:3)

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

为此,我决定采用@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))

此优惠还有:https://gist.github.com/Visgean/e536e466207bf439983a

我不知道这有多有效但是有效。

答案 3 :(得分:2)

这里的游戏还算晚,但是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复制文档,而是对其进行更新。

答案 4 :(得分:1)

这是使用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'
        }
      ]
    }
  ]
})

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

编辑:变量名中的拼写错误。

答案 5 :(得分:0)

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

结帐manuals

  

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