diff --git a/AUTHORS b/AUTHORS index ed4ed472..5598455b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -105,4 +105,5 @@ that much better: * Adam Reeve * Anthony Nemitz * deignacio - * shaunduncan \ No newline at end of file + * shaunduncan + * Meir Kriheli \ No newline at end of file diff --git a/docs/changelog.rst b/docs/changelog.rst index 75fc39a1..11a490d7 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,7 @@ Changelog Changes in 0.6.X ================ +- Added PULL reverse_delete_rule - Fixed CASCADE delete bug - Fixed db_field data load error - Fixed recursive save with FileField diff --git a/docs/guide/defining-documents.rst b/docs/guide/defining-documents.rst index ba7a5801..a005ddd3 100644 --- a/docs/guide/defining-documents.rst +++ b/docs/guide/defining-documents.rst @@ -289,6 +289,10 @@ Its value can take any of the following constants: :const:`mongoengine.CASCADE` Any object containing fields that are refererring to the object being deleted are deleted first. +:const:`mongoengine.PULL` + Removes the reference to the object (using MongoDB's "pull" operation) + from any object's fields of + :class:`~mongoengine.ListField` (:class:`~mongoengine.ReferenceField`). .. warning:: diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 8bb33a92..ec66789c 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -656,6 +656,7 @@ class ReferenceField(BaseField): * NULLIFY - Updates the reference to null. * CASCADE - Deletes the documents associated with the reference. * DENY - Prevent the deletion of the reference object. + * PULL - Pull the reference from a :class:`~mongoengine.ListField` of references Alternative syntax for registering delete rules (useful when implementing bi-directional delete rules) diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index 8370d333..ab35afbc 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -10,7 +10,7 @@ from bson.code import Code from mongoengine import signals __all__ = ['queryset_manager', 'Q', 'InvalidQueryError', - 'DO_NOTHING', 'NULLIFY', 'CASCADE', 'DENY'] + 'DO_NOTHING', 'NULLIFY', 'CASCADE', 'DENY', 'PULL'] # The maximum number of items to display in a QuerySet.__repr__ @@ -21,6 +21,7 @@ DO_NOTHING = 0 NULLIFY = 1 CASCADE = 2 DENY = 3 +PULL = 4 class DoesNotExist(Exception): @@ -1321,6 +1322,10 @@ class QuerySet(object): document_cls.objects(**{field_name + '__in': self}).update( safe_update=safe, **{'unset__%s' % field_name: 1}) + elif rule == PULL: + document_cls.objects(**{field_name + '__in': self}).update( + safe_update=safe, + **{'pull_all__%s' % field_name: self}) self._collection.remove(self._query, safe=safe) diff --git a/tests/test_queryset.py b/tests/test_queryset.py index c768ade9..5078ca28 100644 --- a/tests/test_queryset.py +++ b/tests/test_queryset.py @@ -1419,6 +1419,36 @@ class QuerySetTest(unittest.TestCase): self.assertRaises(OperationError, self.Person.objects.delete) + def test_reverse_delete_rule_pull(self): + """Ensure pulling of references to deleted documents. + """ + class BlogPost(Document): + content = StringField() + authors = ListField(ReferenceField(self.Person, + reverse_delete_rule=PULL)) + + BlogPost.drop_collection() + self.Person.drop_collection() + + me = self.Person(name='Test User') + me.save() + + someoneelse = self.Person(name='Some-one Else') + someoneelse.save() + + post = BlogPost(content='Watching TV', authors=[me, someoneelse]) + post.save() + + another = BlogPost(content='Chilling Out', authors=[someoneelse]) + another.save() + + someoneelse.delete() + post.reload() + another.reload() + + self.assertEqual(post.authors, [me]) + self.assertEqual(another.authors, []) + def test_update(self): """Ensure that atomic updates work properly. """