Support for partial updates.
This commit is contained in:
parent
2689bcf953
commit
478062cb0f
3 changed files with 60 additions and 33 deletions
|
|
@ -18,7 +18,7 @@ Differences between Mongomallard and Mongoengine
|
||||||
* The primary key is only stored as `_id` in the database and is referenced in Python as `pk` or as the name of the primary key field.
|
* The primary key is only stored as `_id` in the database and is referenced in Python as `pk` or as the name of the primary key field.
|
||||||
* Saves are not cascaded by default.
|
* Saves are not cascaded by default.
|
||||||
* `Document.save()` supports `full=True` keyword argument to force saving all model fields.
|
* `Document.save()` supports `full=True` keyword argument to force saving all model fields.
|
||||||
* `_get_changed_fields()` / `_changed_fields` returns a set
|
* `_get_changed_fields()` / `_changed_fields` returns a set of field names (not db field names)
|
||||||
* Simplified `EmailField` email regex to be more compatible
|
* Simplified `EmailField` email regex to be more compatible
|
||||||
* Assigning invalid types (e.g. an invalid string to `IntField`) raises immediately a `ValueError`
|
* Assigning invalid types (e.g. an invalid string to `IntField`) raises immediately a `ValueError`
|
||||||
* `order_by()` without an argument resets the ordering (no ordering will be applied)
|
* `order_by()` without an argument resets the ordering (no ordering will be applied)
|
||||||
|
|
@ -26,7 +26,6 @@ Differences between Mongomallard and Mongoengine
|
||||||
Untested / not implemented yet:
|
Untested / not implemented yet:
|
||||||
-----
|
-----
|
||||||
|
|
||||||
* Delta updates for lists / embedded documents (fields that have changed on the document are fully updated)
|
|
||||||
* Dynamic documents / `DynamicField`, dynamic addition/deletion of fields
|
* Dynamic documents / `DynamicField`, dynamic addition/deletion of fields
|
||||||
* Field display name methods
|
* Field display name methods
|
||||||
* `SequenceField`
|
* `SequenceField`
|
||||||
|
|
|
||||||
|
|
@ -243,30 +243,36 @@ class BaseDocument(object):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def _mark_as_changed(self, key):
|
def _mark_as_changed(self, key):
|
||||||
"""Marks a key as explicitly changed by the user
|
"""Marks a key as explicitly changed by the user.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if key:
|
if key:
|
||||||
self._changed_fields.add(key)
|
self._changed_fields.add(key)
|
||||||
|
|
||||||
def _get_changed_fields(self):
|
def _get_changed_fields(self):
|
||||||
|
"""Returns a list of all fields that have explicitly been changed.
|
||||||
|
"""
|
||||||
changed_fields = set(self._changed_fields)
|
changed_fields = set(self._changed_fields)
|
||||||
EmbeddedDocumentField = _import_class("EmbeddedDocumentField")
|
EmbeddedDocumentField = _import_class("EmbeddedDocumentField")
|
||||||
for field_name, field in self._fields.iteritems():
|
for field_name, field in self._fields.iteritems():
|
||||||
if (isinstance(field, ComplexBaseField) and
|
if field_name not in changed_fields:
|
||||||
isinstance(field.field, EmbeddedDocumentField)):
|
if (isinstance(field, ComplexBaseField) and
|
||||||
field_value = getattr(self, field_name, None)
|
isinstance(field.field, EmbeddedDocumentField)):
|
||||||
if field_value:
|
field_value = getattr(self, field_name, None)
|
||||||
for idx in (field_value if isinstance(field_value, dict)
|
if field_value:
|
||||||
else xrange(len(field_value))):
|
for idx in (field_value if isinstance(field_value, dict)
|
||||||
if field_value[idx]._get_changed_fields():
|
else xrange(len(field_value))):
|
||||||
changed_fields.add(field_name)
|
changed_subfields = field_value[idx]._get_changed_fields()
|
||||||
continue
|
if changed_subfields:
|
||||||
elif isinstance(field, EmbeddedDocumentField):
|
changed_fields |= set(['.'.join([field_name, str(idx), subfield_name])
|
||||||
field_value = getattr(self, field_name, None)
|
for subfield_name in changed_subfields])
|
||||||
if field_value:
|
elif isinstance(field, EmbeddedDocumentField):
|
||||||
if field_value._get_changed_fields():
|
field_value = getattr(self, field_name, None)
|
||||||
changed_fields.add(field_name)
|
if field_value:
|
||||||
|
changed_subfields = field_value._get_changed_fields()
|
||||||
|
if changed_subfields:
|
||||||
|
changed_fields |= set(['.'.join([field_name, subfield_name])
|
||||||
|
for subfield_name in changed_subfields])
|
||||||
return changed_fields
|
return changed_fields
|
||||||
|
|
||||||
def _clear_changed_fields(self):
|
def _clear_changed_fields(self):
|
||||||
|
|
@ -289,23 +295,47 @@ class BaseDocument(object):
|
||||||
sets = {}
|
sets = {}
|
||||||
unsets = {}
|
unsets = {}
|
||||||
|
|
||||||
if full or not self._created:
|
|
||||||
fields = self._fields.iteritems()
|
|
||||||
else:
|
|
||||||
fields = ((field_name, self._fields[field_name]) for field_name in self._get_changed_fields())
|
|
||||||
|
|
||||||
def get(field_name, field):
|
def get_db_value(field, value):
|
||||||
value = getattr(self, field_name)
|
|
||||||
if value is None:
|
if value is None:
|
||||||
value = field.default() if callable(field.default) else field.default
|
value = field.default() if callable(field.default) else field.default
|
||||||
return value
|
return field.to_mongo(value)
|
||||||
|
|
||||||
data = ((
|
|
||||||
self._db_field_map.get(field_name, field_name),
|
|
||||||
field.to_mongo(get(field_name, field)))
|
|
||||||
for field_name, field in fields)
|
|
||||||
|
|
||||||
for db_field_name, db_value in data:
|
if full or not self._created:
|
||||||
|
fields = self._fields.iteritems()
|
||||||
|
db_data = ((self._db_field_map.get(field_name, field_name),
|
||||||
|
get_db_value(field, getattr(self, field_name)))
|
||||||
|
for field_name, field in fields)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# List of (db_field_name, db_value) tuples.
|
||||||
|
db_data = []
|
||||||
|
|
||||||
|
for field_name in self._get_changed_fields():
|
||||||
|
parts = field_name.split('.')
|
||||||
|
|
||||||
|
db_field_parts = []
|
||||||
|
|
||||||
|
value = self
|
||||||
|
for part in parts:
|
||||||
|
if isinstance(value, list) and part.isdigit():
|
||||||
|
db_field_parts.append(part)
|
||||||
|
field = field.field
|
||||||
|
value = value[int(part)]
|
||||||
|
elif isinstance(value, dict):
|
||||||
|
db_field_parts.append(part)
|
||||||
|
field = field.field
|
||||||
|
value = value[part]
|
||||||
|
else: # It's a document
|
||||||
|
obj = value
|
||||||
|
field = obj._fields[part]
|
||||||
|
db_field_parts.append(obj._db_field_map.get(part, part))
|
||||||
|
value = getattr(obj, part)
|
||||||
|
|
||||||
|
db_data.append(('.'.join(db_field_parts), get_db_value(field, value)))
|
||||||
|
|
||||||
|
for db_field_name, db_value in db_data:
|
||||||
if db_value == None:
|
if db_value == None:
|
||||||
unsets[db_field_name] = 1
|
unsets[db_field_name] = 1
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -397,10 +397,8 @@ class InstanceTest(unittest.TestCase):
|
||||||
doc.embedded_field.dict_field['woot'] = "woot"
|
doc.embedded_field.dict_field['woot'] = "woot"
|
||||||
|
|
||||||
self.assertEqual(doc._get_changed_fields(), set([
|
self.assertEqual(doc._get_changed_fields(), set([
|
||||||
'list_field', 'dict_field', 'embedded_field']))
|
'list_field', 'dict_field', 'embedded_field.list_field',
|
||||||
#self.assertEqual(doc._get_changed_fields(), [
|
'embedded_field.dict_field']))
|
||||||
# 'list_field', 'dict_field', 'embedded_field.list_field',
|
|
||||||
# 'embedded_field.dict_field'])
|
|
||||||
doc.save()
|
doc.save()
|
||||||
|
|
||||||
doc = doc.reload()
|
doc = doc.reload()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue