From 602d7dad0020937364f7076a1930d46209d6009d Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Wed, 8 Jun 2011 17:10:26 +0100 Subject: [PATCH] Improvements to Abstract Base Classes Added test example highlighting what to do to migrate a class from complex (allows inheritance) to simple. --- mongoengine/base.py | 13 +++++-- tests/document.py | 90 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 6 deletions(-) diff --git a/mongoengine/base.py b/mongoengine/base.py index 3875fea5..8a0a1f23 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -263,7 +263,7 @@ class DocumentMetaclass(type): superclasses[base._class_name] = base superclasses.update(base._superclasses) - if hasattr(base, '_meta'): + if hasattr(base, '_meta') and not base._meta.get('abstract'): # Ensure that the Document class may be subclassed - # inheritance may be disabled to remove dependency on # additional fields _cls and _types @@ -280,7 +280,7 @@ class DocumentMetaclass(type): # Only simple classes - direct subclasses of Document - may set # allow_inheritance to False - if not simple_class and not meta['allow_inheritance']: + if not simple_class and not meta['allow_inheritance'] and not meta['abstract']: raise ValueError('Only direct subclasses of Document may set ' '"allow_inheritance" to False') attrs['_meta'] = meta @@ -360,8 +360,9 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): # Subclassed documents inherit collection from superclass for base in bases: - if hasattr(base, '_meta') and 'collection' in base._meta: - collection = base._meta['collection'] + if hasattr(base, '_meta'): + if 'collection' in base._meta: + collection = base._meta['collection'] # Propagate index options. for key in ('index_background', 'index_drop_dups', 'index_opts'): @@ -370,6 +371,9 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): id_field = id_field or base._meta.get('id_field') base_indexes += base._meta.get('indexes', []) + # Propagate 'allow_inheritance' + if 'allow_inheritance' in base._meta: + base_meta['allow_inheritance'] = base._meta['allow_inheritance'] meta = { 'abstract': False, @@ -384,6 +388,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): 'index_opts': {}, 'queryset_class': QuerySet, 'delete_rules': {}, + 'allow_inheritance': True } meta.update(base_meta) diff --git a/tests/document.py b/tests/document.py index a8120469..14541469 100644 --- a/tests/document.py +++ b/tests/document.py @@ -151,12 +151,12 @@ class DocumentTest(unittest.TestCase): """Ensure that inheritance may be disabled on simple classes and that _cls and _types will not be used. """ + class Animal(Document): - meta = {'allow_inheritance': False} name = StringField() + meta = {'allow_inheritance': False} Animal.drop_collection() - def create_dog_class(): class Dog(Animal): pass @@ -191,6 +191,92 @@ class DocumentTest(unittest.TestCase): self.assertFalse('_cls' in comment.to_mongo()) self.assertFalse('_types' in comment.to_mongo()) + def test_allow_inheritance_abstract_document(self): + """Ensure that abstract documents can set inheritance rules and that + _cls and _types will not be used. + """ + class FinalDocument(Document): + meta = {'abstract': True, + 'allow_inheritance': False} + + class Animal(FinalDocument): + name = StringField() + + Animal.drop_collection() + def create_dog_class(): + class Dog(Animal): + pass + self.assertRaises(ValueError, create_dog_class) + + # Check that _cls etc aren't present on simple documents + dog = Animal(name='dog') + dog.save() + collection = self.db[Animal._meta['collection']] + obj = collection.find_one() + self.assertFalse('_cls' in obj) + self.assertFalse('_types' in obj) + + Animal.drop_collection() + + def test_how_to_turn_off_inheritance(self): + """Demonstrates migrating from allow_inheritance = True to False. + """ + class Animal(Document): + name = StringField() + meta = { + 'indexes': ['name'] + } + + Animal.drop_collection() + + dog = Animal(name='dog') + dog.save() + + collection = self.db[Animal._meta['collection']] + obj = collection.find_one() + self.assertTrue('_cls' in obj) + self.assertTrue('_types' in obj) + + info = collection.index_information() + info = [value['key'] for key, value in info.iteritems()] + self.assertEquals([[(u'_id', 1)], [(u'_types', 1)], [(u'_types', 1), (u'name', 1)]], info) + + # Turn off inheritance + class Animal(Document): + name = StringField() + meta = { + 'allow_inheritance': False, + 'indexes': ['name'] + } + collection.update({}, {"$unset": {"_types": 1, "_cls": 1}}, False, True) + + # Confirm extra data is removed + obj = collection.find_one() + self.assertFalse('_cls' in obj) + self.assertFalse('_types' in obj) + + info = collection.index_information() + info = [value['key'] for key, value in info.iteritems()] + self.assertEquals([[(u'_id', 1)], [(u'_types', 1)], [(u'_types', 1), (u'name', 1)]], info) + + info = collection.index_information() + indexes_to_drop = [key for key, value in info.iteritems() if '_types' in dict(value['key'])] + for index in indexes_to_drop: + collection.drop_index(index) + + info = collection.index_information() + info = [value['key'] for key, value in info.iteritems()] + self.assertEquals([[(u'_id', 1)]], info) + + # Recreate indexes + dog = Animal.objects.first() + dog.save() + info = collection.index_information() + info = [value['key'] for key, value in info.iteritems()] + self.assertEquals([[(u'_id', 1)], [(u'name', 1),]], info) + + Animal.drop_collection() + def test_abstract_documents(self): """Ensure that a document superclass can be marked as abstract thereby not using it as the name for the collection."""