2013-09-25 2 views
8

Возможно ли обновить документ 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" 
     } 
    ] 
}) 
+0

Не могли бы вы пояснить свою проблему, пожалуйста. От чтения вы ответили ниже, стало ясно, что вы ищете решение, которое позволяет обновлять EmbeddedDocuments, хотя это должно быть указано в вопросе. – Dawson

+0

@Dawson Нет, я хочу обновить полный документ, включая внедренные документы/списки и т. Д., Используя питоновский язык. – rednaw

ответ

6

Ok Я только что сделал функцию для него.

Вы называете это 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) 
+0

Это очень хорошо, но как насчет удаленных полей ?. – kovan

+0

@kovan Что вы имеете в виду? Когда вы пытаетесь обновить поля, которые были удалены? Если вам нужна резервная копия для этого, вы всегда можете написать ее самостоятельно, не должно быть слишком сложно. – rednaw

3

Попробуйте что-то больше нравится эта

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

Спасибо за ваш ответ. Это работает для нормальных полей, но не для EmbeddedDocumentFields. Я добавил требование к моему вопросу. Извините за то, что я не сразу понял. – rednaw

+1

Вы можете выполнить с индексом элементов списка в своем запросе на обновление 'p.update (** {" set__name ":" Hank "," set__address ":" Far away ", 'set__pets__0__name': 'Scooter'})' – hckjck

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

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

EDIT: typo в именах переменных.

+0

Эй, Доусон, что конкретно не сработало для вас о моем методе? – rednaw

+0

@rednaw Ваше решение не учитывало встроенные документы, содержащие внедренные документы. Модель, которую я обновляю, имеет ListField встроенных документов. У самих EmbeddedDocuments есть ListField встроенных документов. Ваше решение разрешило обновлять EmbeddedDocuments, ListFields и ListFields, содержащие EmbeddedDocuments, но не EmbeddedDocuments, содержащие EmbeddedDocuments или ListFields. – Dawson

+0

Доусон, ваш пример использования отлично работает с моим методом. Я не понимаю, что не работает для вас. Я тестировал этот скрипт: http://pastebin.com/uMB0t1Xw – rednaw

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

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

Смежные вопросы