Save no longer tramples over documents now sets or unsets explicit fields.
Fixes #146, refs #18 Thanks @zhangcheng for the initial code
This commit is contained in:
		| @@ -5,9 +5,10 @@ Changelog | |||||||
| Changes in dev | Changes in dev | ||||||
| ============== | ============== | ||||||
|  |  | ||||||
|  | - Fixed saving so sets updated values rather than overwrites | ||||||
| - Added ComplexDateTimeField - Handles datetimes correctly with microseconds | - Added ComplexDateTimeField - Handles datetimes correctly with microseconds | ||||||
| - Added ComplexBaseField - for improved flexibility and performance. | - Added ComplexBaseField - for improved flexibility and performance | ||||||
| - Added get_FIELD_display() method for easy choice field displaying. | - Added get_FIELD_display() method for easy choice field displaying | ||||||
| - Added queryset.slave_okay(enabled) method | - Added queryset.slave_okay(enabled) method | ||||||
| - Updated queryset.timeout(enabled) and queryset.snapshot(enabled) to be chainable | - Updated queryset.timeout(enabled) and queryset.snapshot(enabled) to be chainable | ||||||
| - Added insert method for bulk inserts | - Added insert method for bulk inserts | ||||||
|   | |||||||
| @@ -92,6 +92,9 @@ class BaseField(object): | |||||||
|         """Descriptor for assigning a value to a field in a document. |         """Descriptor for assigning a value to a field in a document. | ||||||
|         """ |         """ | ||||||
|         instance._data[self.name] = value |         instance._data[self.name] = value | ||||||
|  |         # If the field set is in the _present_fields list add it so we can track | ||||||
|  |         if hasattr(instance, '_present_fields') and self.name not in instance._present_fields: | ||||||
|  |             instance._present_fields.append(self.name) | ||||||
|  |  | ||||||
|     def to_python(self, value): |     def to_python(self, value): | ||||||
|         """Convert a MongoDB-compatible type to a Python type. |         """Convert a MongoDB-compatible type to a Python type. | ||||||
| @@ -592,13 +595,14 @@ class BaseDocument(object): | |||||||
|             if field.choices:  # dynamically adds a way to get the display value for a field with choices |             if field.choices:  # dynamically adds a way to get the display value for a field with choices | ||||||
|                 setattr(self, 'get_%s_display' % attr_name, partial(self._get_FIELD_display, field=field)) |                 setattr(self, 'get_%s_display' % attr_name, partial(self._get_FIELD_display, field=field)) | ||||||
|  |  | ||||||
|             # Use default value if present |  | ||||||
|             value = getattr(self, attr_name, None) |             value = getattr(self, attr_name, None) | ||||||
|             setattr(self, attr_name, value) |             setattr(self, attr_name, value) | ||||||
|  |  | ||||||
|         # Assign initial values to instance |         # Assign initial values to instance | ||||||
|         for attr_name in values.keys(): |         for attr_name in values.keys(): | ||||||
|             try: |             try: | ||||||
|                 setattr(self, attr_name, values.pop(attr_name)) |                 value = values.pop(attr_name) | ||||||
|  |                 setattr(self, attr_name, value) | ||||||
|             except AttributeError: |             except AttributeError: | ||||||
|                 pass |                 pass | ||||||
|  |  | ||||||
| @@ -739,7 +743,6 @@ class BaseDocument(object): | |||||||
|             cls = subclasses[class_name] |             cls = subclasses[class_name] | ||||||
|  |  | ||||||
|         present_fields = data.keys() |         present_fields = data.keys() | ||||||
|  |  | ||||||
|         for field_name, field in cls._fields.items(): |         for field_name, field in cls._fields.items(): | ||||||
|             if field.db_field in data: |             if field.db_field in data: | ||||||
|                 value = data[field.db_field] |                 value = data[field.db_field] | ||||||
|   | |||||||
| @@ -95,6 +95,16 @@ class Document(BaseDocument): | |||||||
|             collection = self.__class__.objects._collection |             collection = self.__class__.objects._collection | ||||||
|             if force_insert: |             if force_insert: | ||||||
|                 object_id = collection.insert(doc, safe=safe, **write_options) |                 object_id = collection.insert(doc, safe=safe, **write_options) | ||||||
|  |             elif '_id' in doc: | ||||||
|  |                 # Perform a set rather than a save - this will only save set fields | ||||||
|  |                 object_id = doc.pop('_id') | ||||||
|  |                 collection.update({'_id': object_id}, {"$set": doc}, upsert=True, safe=safe, **write_options) | ||||||
|  |  | ||||||
|  |                 # Find and unset any fields explicitly set to None | ||||||
|  |                 if hasattr(self, '_present_fields'): | ||||||
|  |                     removals = dict([(k, 1) for k in self._present_fields if k not in doc and k != '_id']) | ||||||
|  |                     if removals: | ||||||
|  |                         collection.update({'_id': object_id}, {"$unset": removals}, upsert=True, safe=safe, **write_options) | ||||||
|             else: |             else: | ||||||
|                 object_id = collection.save(doc, safe=safe, **write_options) |                 object_id = collection.save(doc, safe=safe, **write_options) | ||||||
|         except pymongo.errors.OperationFailure, err: |         except pymongo.errors.OperationFailure, err: | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							| @@ -45,6 +45,6 @@ setup(name='mongoengine', | |||||||
|       long_description=LONG_DESCRIPTION, |       long_description=LONG_DESCRIPTION, | ||||||
|       platforms=['any'], |       platforms=['any'], | ||||||
|       classifiers=CLASSIFIERS, |       classifiers=CLASSIFIERS, | ||||||
|       install_requires=['pymongo', 'blinker', 'django>=1.3'], |       install_requires=['pymongo', 'blinker', 'django==1.3'], | ||||||
|       test_suite='tests', |       test_suite='tests', | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -789,6 +789,90 @@ class DocumentTest(unittest.TestCase): | |||||||
|         except ValidationError: |         except ValidationError: | ||||||
|             self.fail() |             self.fail() | ||||||
|  |  | ||||||
|  |     def test_update(self): | ||||||
|  |         """Ensure that an existing document is updated instead of be overwritten. | ||||||
|  |         """ | ||||||
|  |         # Create person object and save it to the database | ||||||
|  |         person = self.Person(name='Test User', age=30) | ||||||
|  |         person.save() | ||||||
|  |  | ||||||
|  |         # Create same person object, with same id, without age | ||||||
|  |         same_person = self.Person(name='Test') | ||||||
|  |         same_person.id = person.id | ||||||
|  |         same_person.save() | ||||||
|  |  | ||||||
|  |         # Confirm only one object | ||||||
|  |         self.assertEquals(self.Person.objects.count(), 1) | ||||||
|  |  | ||||||
|  |         # reload | ||||||
|  |         person.reload() | ||||||
|  |         same_person.reload() | ||||||
|  |  | ||||||
|  |         # Confirm the same | ||||||
|  |         self.assertEqual(person, same_person) | ||||||
|  |         self.assertEqual(person.name, same_person.name) | ||||||
|  |         self.assertEqual(person.age, same_person.age) | ||||||
|  |  | ||||||
|  |         # Confirm the saved values | ||||||
|  |         self.assertEqual(person.name, 'Test') | ||||||
|  |         self.assertEqual(person.age, 30) | ||||||
|  |  | ||||||
|  |         # Test only / exclude only updates included fields | ||||||
|  |         person = self.Person.objects.only('name').get() | ||||||
|  |         person.name = 'User' | ||||||
|  |         person.save() | ||||||
|  |  | ||||||
|  |         person.reload() | ||||||
|  |         self.assertEqual(person.name, 'User') | ||||||
|  |         self.assertEqual(person.age, 30) | ||||||
|  |  | ||||||
|  |         # test exclude only updates set fields | ||||||
|  |         person = self.Person.objects.exclude('name').get() | ||||||
|  |         person.age = 21 | ||||||
|  |         person.save() | ||||||
|  |  | ||||||
|  |         person.reload() | ||||||
|  |         self.assertEqual(person.name, 'User') | ||||||
|  |         self.assertEqual(person.age, 21) | ||||||
|  |  | ||||||
|  |         # Test only / exclude can set non excluded / included fields | ||||||
|  |         person = self.Person.objects.only('name').get() | ||||||
|  |         person.name = 'Test' | ||||||
|  |         person.age = 30 | ||||||
|  |         person.save() | ||||||
|  |  | ||||||
|  |         person.reload() | ||||||
|  |         self.assertEqual(person.name, 'Test') | ||||||
|  |         self.assertEqual(person.age, 30) | ||||||
|  |  | ||||||
|  |         # test exclude only updates set fields | ||||||
|  |         person = self.Person.objects.exclude('name').get() | ||||||
|  |         person.name = 'User' | ||||||
|  |         person.age = 21 | ||||||
|  |         person.save() | ||||||
|  |  | ||||||
|  |         person.reload() | ||||||
|  |         self.assertEqual(person.name, 'User') | ||||||
|  |         self.assertEqual(person.age, 21) | ||||||
|  |  | ||||||
|  |         # Confirm does remove unrequired fields | ||||||
|  |         person = self.Person.objects.exclude('name').get() | ||||||
|  |         person.age = None | ||||||
|  |         person.save() | ||||||
|  |  | ||||||
|  |         person.reload() | ||||||
|  |         self.assertEqual(person.name, 'User') | ||||||
|  |         self.assertEqual(person.age, None) | ||||||
|  |  | ||||||
|  |         person = self.Person.objects.get() | ||||||
|  |         person.name = None | ||||||
|  |         person.age = None | ||||||
|  |         person.save() | ||||||
|  |  | ||||||
|  |         person.reload() | ||||||
|  |         self.assertEqual(person.name, None) | ||||||
|  |         self.assertEqual(person.age, None) | ||||||
|  |  | ||||||
|     def test_delete(self): |     def test_delete(self): | ||||||
|         """Ensure that document may be deleted using the delete method. |         """Ensure that document may be deleted using the delete method. | ||||||
|         """ |         """ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user