From 8a44232bfc5d26b69d9ebfde7e57510d84fb58e5 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Thu, 1 Dec 2011 07:57:24 -0800 Subject: [PATCH] Added Reverse Delete Rule support to ListFields DictFields and MapFields aren't supported and raise an InvalidDocument Error Closes #254 --- mongoengine/base.py | 13 ++++++++++--- tests/document.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/mongoengine/base.py b/mongoengine/base.py index ad2ebca7..2fd40ec4 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -503,15 +503,22 @@ class DocumentMetaclass(type): attrs['_db_field_map'] = dict([(k, v.db_field) for k, v in doc_fields.items() if k!=v.db_field]) attrs['_reverse_db_field_map'] = dict([(v, k) for k, v in attrs['_db_field_map'].items()]) - from mongoengine import Document, EmbeddedDocument + from mongoengine import Document, EmbeddedDocument, DictField new_class = super_new(cls, name, bases, attrs) for field in new_class._fields.values(): field.owner_document = new_class + delete_rule = getattr(field, 'reverse_delete_rule', DO_NOTHING) + f = field + if isinstance(f, ComplexBaseField) and hasattr(f, 'field'): + delete_rule = getattr(f.field, 'reverse_delete_rule', DO_NOTHING) + if isinstance(f, DictField) and delete_rule != DO_NOTHING: + raise InvalidDocumentError("Reverse delete rules are not supported for %s (field: %s)" % (field.__class__.__name__, field.name)) + f = field.field + if delete_rule != DO_NOTHING: - field.document_type.register_delete_rule(new_class, field.name, - delete_rule) + f.document_type.register_delete_rule(new_class, field.name, delete_rule) if field.name and hasattr(Document, field.name) and EmbeddedDocument not in new_class.mro(): raise InvalidDocumentError("%s is a document method and not a valid field name" % field.name) diff --git a/tests/document.py b/tests/document.py index f12affd4..391c3e25 100644 --- a/tests/document.py +++ b/tests/document.py @@ -2214,6 +2214,46 @@ class DocumentTest(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. + """ + + class BlogPost(Document): + content = StringField() + authors = ListField(ReferenceField(self.Person, reverse_delete_rule=CASCADE)) + reviewers = ListField(ReferenceField(self.Person, reverse_delete_rule=NULLIFY)) + + self.Person.drop_collection() + BlogPost.drop_collection() + + author = self.Person(name='Test User') + author.save() + + reviewer = self.Person(name='Re Viewer') + reviewer.save() + + post = BlogPost(content= 'Watched some TV') + post.authors = [author] + post.reviewers = [reviewer] + post.save() + + reviewer.delete() + self.assertEqual(len(BlogPost.objects), 1) # No effect on the BlogPost + self.assertEqual(BlogPost.objects.get().reviewers, []) + + # Delete the Person, which should lead to deletion of the BlogPost, too + author.delete() + self.assertEqual(len(BlogPost.objects), 0) + + def throw_invalid_document_error(): + class Blog(Document): + content = StringField() + authors = MapField(ReferenceField(self.Person, reverse_delete_rule=CASCADE)) + reviewers = DictField(field=ReferenceField(self.Person, reverse_delete_rule=NULLIFY)) + + self.assertRaises(InvalidDocumentError, throw_invalid_document_error) + + def test_reverse_delete_rule_cascade_recurs(self): """Ensure that a chain of documents is also deleted upon cascaded deletion.