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:
Ross Lawley 2011-06-09 16:09:06 +01:00
parent fb09fde209
commit fd7f882011
5 changed files with 104 additions and 6 deletions

View File

@ -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

View File

@ -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]

View File

@ -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:

View File

@ -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',
) )

View File

@ -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.
""" """