Skip to content

Commit b92c484

Browse files
authored
Merge pull request #1949 from bagerard/fix_delta_bug
Fix bug in _delta method - setting ListField to empty in DynamicDocument is faulty
2 parents 78a9420 + c306d42 commit b92c484

File tree

3 files changed

+41
-19
lines changed

3 files changed

+41
-19
lines changed

docs/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Changelog
55
Development
66
===========
77
- (Fill this out as you fix issues and develop your features).
8+
- Fix bug in _delta method - Update of a ListField depends on an unrelated dynamic field update #1733
89
- Remove deprecated `save()` method and used `insert_one()` #1899
910

1011
=================

mongoengine/base/document.py

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,6 @@ def _delta(self):
579579

580580
set_fields = self._get_changed_fields()
581581
unset_data = {}
582-
parts = []
583582
if hasattr(self, '_changed_fields'):
584583
set_data = {}
585584
# Fetch each set item from its path
@@ -589,15 +588,13 @@ def _delta(self):
589588
new_path = []
590589
for p in parts:
591590
if isinstance(d, (ObjectId, DBRef)):
591+
# Don't dig in the references
592592
break
593-
elif isinstance(d, list) and p.lstrip('-').isdigit():
594-
if p[0] == '-':
595-
p = str(len(d) + int(p))
596-
try:
597-
d = d[int(p)]
598-
except IndexError:
599-
d = None
593+
elif isinstance(d, list) and p.isdigit():
594+
# An item of a list (identified by its index) is updated
595+
d = d[int(p)]
600596
elif hasattr(d, 'get'):
597+
# dict-like (dict, embedded document)
601598
d = d.get(p)
602599
new_path.append(p)
603600
path = '.'.join(new_path)
@@ -609,26 +606,26 @@ def _delta(self):
609606

610607
# Determine if any changed items were actually unset.
611608
for path, value in set_data.items():
612-
if value or isinstance(value, (numbers.Number, bool)):
609+
if value or isinstance(value, (numbers.Number, bool)): # Account for 0 and True that are truthy
613610
continue
614611

615-
# If we've set a value that ain't the default value don't unset it.
616-
default = None
612+
parts = path.split('.')
613+
617614
if (self._dynamic and len(parts) and parts[0] in
618615
self._dynamic_fields):
619616
del set_data[path]
620617
unset_data[path] = 1
621618
continue
622-
elif path in self._fields:
619+
620+
# If we've set a value that ain't the default value don't unset it.
621+
default = None
622+
if path in self._fields:
623623
default = self._fields[path].default
624624
else: # Perform a full lookup for lists / embedded lookups
625625
d = self
626-
parts = path.split('.')
627626
db_field_name = parts.pop()
628627
for p in parts:
629-
if isinstance(d, list) and p.lstrip('-').isdigit():
630-
if p[0] == '-':
631-
p = str(len(d) + int(p))
628+
if isinstance(d, list) and p.isdigit():
632629
d = d[int(p)]
633630
elif (hasattr(d, '__getattribute__') and
634631
not isinstance(d, dict)):
@@ -646,10 +643,9 @@ def _delta(self):
646643
default = None
647644

648645
if default is not None:
649-
if callable(default):
650-
default = default()
646+
default = default() if callable(default) else default
651647

652-
if default != value:
648+
if value != default:
653649
continue
654650

655651
del set_data[path]

tests/fields/fields.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,31 @@ class Person(Document):
186186
data_to_be_saved = sorted(person.to_mongo().keys())
187187
self.assertEqual(data_to_be_saved, ['age', 'created', 'userid'])
188188

189+
def test_default_value_is_not_used_when_changing_value_to_empty_list_for_strict_doc(self):
190+
"""List field with default can be set to the empty list (strict)"""
191+
# Issue #1733
192+
class Doc(Document):
193+
x = ListField(IntField(), default=lambda: [42])
194+
195+
doc = Doc(x=[1]).save()
196+
doc.x = []
197+
doc.save()
198+
reloaded = Doc.objects.get(id=doc.id)
199+
self.assertEqual(reloaded.x, [])
200+
201+
def test_default_value_is_not_used_when_changing_value_to_empty_list_for_dyn_doc(self):
202+
"""List field with default can be set to the empty list (dynamic)"""
203+
# Issue #1733
204+
class Doc(DynamicDocument):
205+
x = ListField(IntField(), default=lambda: [42])
206+
207+
doc = Doc(x=[1]).save()
208+
doc.x = []
209+
doc.y = 2 # Was triggering the bug
210+
doc.save()
211+
reloaded = Doc.objects.get(id=doc.id)
212+
self.assertEqual(reloaded.x, [])
213+
189214
def test_default_values_when_deleting_value(self):
190215
"""Ensure that default field values are used after non-default
191216
values are explicitly deleted.

0 commit comments

Comments
 (0)