From f182daa85eae94ccfffaa40e04f6370c007d10c4 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Mon, 28 Jan 2013 13:32:21 +0000 Subject: [PATCH] Fixed Documents deleted via a queryset don't call any signals (#105) --- docs/changelog.rst | 1 + mongoengine/queryset/queryset.py | 11 ++++++--- tests/document/instance.py | 40 ++++++++++++++++++++++++++++++-- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 601c2dbc..8b41d6ee 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -43,6 +43,7 @@ Changes in 0.8.X - Undefined data should not override instance methods (#49) - Added Django Group and Permission (#142) - Added Doc class and pk to Validation messages (#69) +- Fixed Documents deleted via a queryset don't call any signals (#105) Changes in 0.7.9 ================ diff --git a/mongoengine/queryset/queryset.py b/mongoengine/queryset/queryset.py index b5e33515..65703c32 100644 --- a/mongoengine/queryset/queryset.py +++ b/mongoengine/queryset/queryset.py @@ -364,10 +364,15 @@ class QuerySet(object): queryset = self.clone() doc = queryset._document - # Handle deletes where skips or limits have been applied - if queryset._skip or queryset._limit: + has_delete_signal = ( + signals.pre_delete.has_receivers_for(self._document) or + signals.post_delete.has_receivers_for(self._document)) + + # Handle deletes where skips or limits have been applied or has a + # delete signal + if queryset._skip or queryset._limit or has_delete_signal: for doc in queryset: - doc.delete() + doc.delete(safe=safe) return delete_rules = doc._meta.get('delete_rules') or {} diff --git a/tests/document/instance.py b/tests/document/instance.py index 3d4e8a97..172f0ccf 100644 --- a/tests/document/instance.py +++ b/tests/document/instance.py @@ -19,6 +19,7 @@ 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 import signals TEST_IMAGE_PATH = os.path.join(os.path.dirname(__file__), '../fields/mongoengine.png') @@ -1375,7 +1376,6 @@ class InstanceTest(unittest.TestCase): author.delete() self.assertEqual(len(BlogPost.objects), 0) - def test_reverse_delete_rule_cascade_and_nullify_complex_field(self): """Ensure that a referenced document is also deleted upon deletion for complex fields. @@ -1410,6 +1410,43 @@ class InstanceTest(unittest.TestCase): author.delete() self.assertEqual(len(BlogPost.objects), 0) + + def test_reverse_delete_rule_cascade_triggers_pre_delete_signal(self): + ''' ensure the pre_delete signal is triggered upon a cascading deletion + setup a blog post with content, an author and editor + delete the author which triggers deletion of blogpost via cascade + blog post's pre_delete signal alters an editor attribute + ''' + class Editor(self.Person): + review_queue = IntField(default=0) + + class BlogPost(Document): + content = StringField() + author = ReferenceField(self.Person, reverse_delete_rule=CASCADE) + editor = ReferenceField(Editor) + + @classmethod + def pre_delete(cls, sender, document, **kwargs): + # decrement the docs-to-review count + document.editor.update(dec__review_queue=1) + + signals.pre_delete.connect(BlogPost.pre_delete, sender=BlogPost) + + self.Person.drop_collection() + BlogPost.drop_collection() + Editor.drop_collection() + + author = self.Person(name='Will S.').save() + editor = Editor(name='Max P.', review_queue=1).save() + BlogPost(content='wrote some books', author=author, + editor=editor).save() + + # delete the author, the post is also deleted due to the CASCADE rule + author.delete() + # the pre-delete signal should have decremented the editor's queue + editor = Editor.objects(name='Max P.').get() + self.assertEqual(editor.review_queue, 0) + def test_two_way_reverse_delete_rule(self): """Ensure that Bi-Directional relationships work with reverse_delete_rule @@ -1426,7 +1463,6 @@ class InstanceTest(unittest.TestCase): Bar.register_delete_rule(Foo, 'bar', NULLIFY) Foo.register_delete_rule(Bar, 'foo', NULLIFY) - Bar.drop_collection() Foo.drop_collection()