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:
parent
fb09fde209
commit
fd7f882011
@ -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.
|
||||||
"""
|
"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user