diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index 4f5a87e5..60b4d975 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -84,6 +84,17 @@ class BaseDocument(object): self._initialised = True signals.post_init.send(self.__class__, document=self) + def __delattr__(self, *args, **kwargs): + """Handle deletions of fields""" + field_name = args[0] + if field_name in self._fields: + default = self._fields[field_name].default + if callable(default): + default = default() + setattr(self, field_name, default) + else: + super(BaseDocument, self).__delattr__(*args, **kwargs) + def __setattr__(self, name, value): # Handle dynamic data only if an initialised dynamic document if self._dynamic and not self._dynamic_lock: diff --git a/mongoengine/document.py b/mongoengine/document.py index edc819cb..66aa2632 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -250,16 +250,16 @@ class Document(BaseDocument): return created upsert = self._created + update_query = {} + if updates: - last_error = collection.update(select_dict, - {"$set": updates}, upsert=upsert, safe=safe, - **write_options) - created = is_new_object(last_error) + update_query["$set"] = updates if removals: - last_error = collection.update(select_dict, - {"$unset": removals}, upsert=upsert, safe=safe, - **write_options) - created = created or is_new_object(last_error) + update_query["$unset"] = removals + if updates or removals: + last_error = collection.update(select_dict, update_query, + upsert=upsert, safe=safe, **write_options) + created = is_new_object(last_error) warn_cascade = not cascade and 'cascade' not in self._meta cascade = (self._meta.get('cascade', True) diff --git a/tests/document/instance.py b/tests/document/instance.py index 172f0ccf..9a4149ff 100644 --- a/tests/document/instance.py +++ b/tests/document/instance.py @@ -18,7 +18,7 @@ from mongoengine.errors import (NotRegistered, InvalidDocumentError, from mongoengine.queryset import NULLIFY, Q from mongoengine.connection import get_db from mongoengine.base import get_document -from mongoengine.context_managers import switch_db +from mongoengine.context_managers import switch_db, query_counter from mongoengine import signals TEST_IMAGE_PATH = os.path.join(os.path.dirname(__file__), @@ -971,6 +971,28 @@ class InstanceTest(unittest.TestCase): self.assertEqual(person.age, 21) self.assertEqual(person.active, False) + def test_set_unset_one_operation(self): + """Ensure that $set and $unset actions are performed in the same + operation. + """ + class FooBar(Document): + foo = StringField(default=None) + bar = StringField(default=None) + + FooBar.drop_collection() + + # write an entity with a single prop + foo = FooBar(foo='foo').save() + + self.assertEqual(foo.foo, 'foo') + del foo.foo + foo.bar = 'bar' + + with query_counter() as q: + self.assertEqual(0, q) + foo.save() + self.assertEqual(1, q) + def test_save_only_changed_fields_recursive(self): """Ensure save only sets / unsets changed fields """ diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index c6b7c0e0..3d5c659c 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -260,7 +260,8 @@ class QuerySetTest(unittest.TestCase): name='Test User', write_options=write_options) author.save(write_options=write_options) - self.Person.objects.update(set__name='Ross', write_options=write_options) + self.Person.objects.update(set__name='Ross', + write_options=write_options) author = self.Person.objects.first() self.assertEqual(author.name, 'Ross')