Обновить документ MongoEngine с помощью python dict?

Возможно ли обновить документ MongoEngine с помощью python dict?

Например:

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"
        }
    ]
})

Ответ 1

Хорошо, я просто сделал для этого функцию.

Вы называете его как 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)

Ответ 2

Попробуйте что-нибудь еще

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

Ответ 3

Я пробовал большинство ответов выше, никто из них, похоже, действительно не работает с встроенными документами. Несмотря на то, что они обновили поля, они также удалили содержимое незаполненных полей в документе Embedded.

Для этого я решил пойти по пути, предложенному @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 protected]'}}}

будет преобразован в:

{'set__communication__mobile_phone': u'2323232323', 'set__communication__email__primary': '[email protected]'}

Что можно использовать как это

document.update(**conv_dict_to_update(data))

Также доступно в этом документе: https://gist.github.com/Visgean/e536e466207bf439983a

Я не знаю, насколько это эффективно, но это работает.

Ответ 4

Вот функция обновления документов с помощью 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'
        }
      ]
    }
  ]
})

FYI Это на самом деле мое имя кошки.

EDIT: опечатка в именах переменных.

Ответ 5

Довольно поздно к игре здесь, но 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 и обновит его.

Ответ 6

Для хранения python dict в качестве mongoengine.fields.DictField документа можно использовать mongoengine.fields.DictField.

Оплата пособия.

Поле словаря, которое содержит стандартный словарь Python. Это похоже на внедренный документ, но структура не определена.