From eeb5a83e98c598ad18b6183495a89cd0c7d1cf32 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Tue, 4 Jun 2013 16:35:25 +0000 Subject: [PATCH] Added lock when calling doc.Delete() for when signals have no sender (#350) --- docs/changelog.rst | 1 + mongoengine/document.py | 3 +- mongoengine/queryset/queryset.py | 17 +++++--- tests/test_signals.py | 70 ++------------------------------ 4 files changed, 16 insertions(+), 75 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 20e20461..df24a5f6 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,7 @@ Changelog Changes in 0.8.2 ================ +- Added lock when calling doc.Delete() for when signals have no sender (#350) - Reload forces read preference to be PRIMARY (#355) - Querysets are now lest restrictive when querying duplicate fields (#332, #333) - FileField now honouring db_alias (#341) diff --git a/mongoengine/document.py b/mongoengine/document.py index e04e2bcd..5edfc810 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -347,11 +347,10 @@ class Document(BaseDocument): signals.pre_delete.send(self.__class__, document=self) try: - self._qs.filter(**self._object_key).delete(write_concern=write_concern) + self._qs.filter(**self._object_key).delete(write_concern=write_concern, _from_doc_delete=True) except pymongo.errors.OperationFailure, err: message = u'Could not delete document (%s)' % err.message raise OperationError(message) - signals.post_delete.send(self.__class__, document=self) def switch_db(self, db_alias): diff --git a/mongoengine/queryset/queryset.py b/mongoengine/queryset/queryset.py index 00a0abcb..5077f895 100644 --- a/mongoengine/queryset/queryset.py +++ b/mongoengine/queryset/queryset.py @@ -407,7 +407,7 @@ class QuerySet(object): self._len = count return count - def delete(self, write_concern=None): + def delete(self, write_concern=None, _from_doc_delete=False): """Delete the documents matched by the query. :param write_concern: Extra keyword arguments are passed down which @@ -416,20 +416,25 @@ class QuerySet(object): ``save(..., write_concern={w: 2, fsync: True}, ...)`` will wait until at least two servers have recorded the write and will force an fsync on the primary server. + :param _from_doc_delete: True when called from document delete therefore + signals will have been triggered so don't loop. """ queryset = self.clone() doc = queryset._document + if not write_concern: + write_concern = {} + + # Handle deletes where skips or limits have been applied or + # there is an untriggered delete signal has_delete_signal = signals.signals_available and ( signals.pre_delete.has_receivers_for(self._document) or signals.post_delete.has_receivers_for(self._document)) - if not write_concern: - write_concern = {} + call_document_delete = (queryset._skip or queryset._limit or + has_delete_signal) and not _from_doc_delete - # Handle deletes where skips or limits have been applied or has a - # delete signal - if queryset._skip or queryset._limit or has_delete_signal: + if call_document_delete: for doc in queryset: doc.delete(write_concern=write_concern) return diff --git a/tests/test_signals.py b/tests/test_signals.py index 65289c2d..27614bd9 100644 --- a/tests/test_signals.py +++ b/tests/test_signals.py @@ -41,7 +41,7 @@ class SignalTests(unittest.TestCase): @classmethod def pre_save(cls, sender, document, **kwargs): - signal_output.append('pre_save signal,, %s' % document) + signal_output.append('pre_save signal, %s' % document) @classmethod def pre_save_post_validation(cls, sender, document, **kwargs): @@ -83,54 +83,6 @@ class SignalTests(unittest.TestCase): self.Author = Author Author.drop_collection() - class Another(Document): - name = StringField() - - def __unicode__(self): - return self.name - - @classmethod - def pre_init(cls, sender, document, **kwargs): - signal_output.append('pre_init Another signal, %s' % cls.__name__) - signal_output.append(str(kwargs['values'])) - - @classmethod - def post_init(cls, sender, document, **kwargs): - signal_output.append('post_init Another signal, %s' % document) - - @classmethod - def pre_save(cls, sender, document, **kwargs): - signal_output.append('pre_save Another signal, %s' % document) - - @classmethod - def pre_save_post_validation(cls, sender, document, **kwargs): - signal_output.append('pre_save_post_validation Another signal, %s' % document) - if 'created' in kwargs: - if kwargs['created']: - signal_output.append('Is created') - else: - signal_output.append('Is updated') - - @classmethod - def post_save(cls, sender, document, **kwargs): - signal_output.append('post_save Another signal, %s' % document) - if 'created' in kwargs: - if kwargs['created']: - signal_output.append('Is created') - else: - signal_output.append('Is updated') - - @classmethod - def pre_delete(cls, sender, document, **kwargs): - signal_output.append('pre_delete Another signal, %s' % document) - - @classmethod - def post_delete(cls, sender, document, **kwargs): - signal_output.append('post_delete Another signal, %s' % document) - - self.Another = Another - Another.drop_collection() - class ExplicitId(Document): id = IntField(primary_key=True) @@ -169,14 +121,6 @@ class SignalTests(unittest.TestCase): signals.pre_bulk_insert.connect(Author.pre_bulk_insert, sender=Author) signals.post_bulk_insert.connect(Author.post_bulk_insert, sender=Author) - signals.pre_init.connect(Another.pre_init, sender=Another) - signals.post_init.connect(Another.post_init, sender=Another) - signals.pre_save.connect(Another.pre_save, sender=Another) - signals.pre_save_post_validation.connect(Another.pre_save_post_validation, sender=Another) - signals.post_save.connect(Another.post_save, sender=Another) - signals.pre_delete.connect(Another.pre_delete, sender=Another) - signals.post_delete.connect(Another.post_delete, sender=Another) - signals.post_save.connect(ExplicitId.post_save, sender=ExplicitId) def tearDown(self): @@ -190,14 +134,6 @@ class SignalTests(unittest.TestCase): signals.pre_bulk_insert.disconnect(self.Author.pre_bulk_insert) signals.post_bulk_insert.disconnect(self.Author.post_bulk_insert) - signals.pre_init.disconnect(self.Another.pre_init) - signals.post_init.disconnect(self.Another.post_init) - signals.post_delete.disconnect(self.Another.post_delete) - signals.pre_delete.disconnect(self.Another.pre_delete) - signals.post_save.disconnect(self.Another.post_save) - signals.pre_save_post_validation.disconnect(self.Another.pre_save_post_validation) - signals.pre_save.disconnect(self.Another.pre_save) - signals.post_save.disconnect(self.ExplicitId.post_save) # Check that all our signals got disconnected properly. @@ -239,7 +175,7 @@ class SignalTests(unittest.TestCase): a1 = self.Author(name='Bill Shakespeare') self.assertEqual(self.get_signal_output(a1.save), [ - "pre_save signal,, Bill Shakespeare", + "pre_save signal, Bill Shakespeare", "pre_save_post_validation signal, Bill Shakespeare", "Is created", "post_save signal, Bill Shakespeare", @@ -249,7 +185,7 @@ class SignalTests(unittest.TestCase): a1.reload() a1.name = 'William Shakespeare' self.assertEqual(self.get_signal_output(a1.save), [ - "pre_save signal,, William Shakespeare", + "pre_save signal, William Shakespeare", "pre_save_post_validation signal, William Shakespeare", "Is updated", "post_save signal, William Shakespeare",