diff --git a/docs/changelog.rst b/docs/changelog.rst index e3cd7232..cfae79e0 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,7 @@ Changelog Changes in dev ============== +- Updated default collection naming convention - Added Document Mixin support - Fixed queryet __repr__ mid iteration - Added hint() support, so cantell Mongo the proper index to use for the query diff --git a/docs/index.rst b/docs/index.rst index ccb7fbe2..3b036564 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,7 +2,7 @@ MongoEngine User Documentation ============================== -MongoEngine is an Object-Document Mapper, written in Python for working with +MongoEngine is an Object-Document Mapper, written in Python for working with MongoDB. To install it, simply run .. code-block:: console @@ -15,7 +15,7 @@ To get help with using MongoEngine, use the `MongoEngine Users mailing list `_ or come chat on the `#mongoengine IRC channel `_. -If you are interested in contributing, join the developers' `mailing list +If you are interested in contributing, join the developers' `mailing list `_. .. toctree:: @@ -26,6 +26,7 @@ If you are interested in contributing, join the developers' `mailing list apireference django changelog + upgrading Indices and tables ================== diff --git a/docs/upgrade.rst b/docs/upgrade.rst new file mode 100644 index 00000000..f005e2e2 --- /dev/null +++ b/docs/upgrade.rst @@ -0,0 +1,73 @@ +========= +Upgrading +========= + +0.4 to 0.5 +=========== + +There have been the following backwards incompatibilities from 0.4 to 0.5: + +#. Default collection naming. + +Previously it was just lowercase, its now much more pythonic and readable as its +lowercase and underscores, previously :: + + class MyAceDocument(Document): + pass + + MyAceDocument._meta['collection'] == myacedocument + +In 0.5 this will change to :: + + class MyAceDocument(Document): + pass + + MyAceDocument._get_collection_name() == my_ace_document + +To upgrade use a Mixin class to set meta like so :: + + class BaseMixin(object): + meta = { + 'collection': lambda c: c.__name__.lower() + } + + class MyAceDocument(Document, BaseMixin): + pass + + MyAceDocument._get_collection_name() == myacedocument + +Alternatively, you can rename your collections eg :: + + from mongoengine.connection import _get_db + from mongoengine.base import _document_registry + + def rename_collections(): + db = _get_db() + + failure = False + + collection_names = [d._get_collection_name() for d in _document_registry.values()] + + for new_style_name in collection_names: + if not new_style_name: # embedded documents don't have collections + continue + old_style_name = new_style_name.replace('_', '') + + if old_style_name == new_style_name: + continue # Nothing to do + + existing = db.collection_names() + if old_style_name in existing: + if new_style_name in existing: + failure = True + print "FAILED to rename: %s to %s (already exists)" % ( + old_style_name, new_style_name) + else: + db[old_style_name].rename(new_style_name) + print "Renamed: %s to %s" % (old_style_name, new_style_name) + + if failure: + print "Upgrading collection names failed" + else: + print "Upgraded collection names" + diff --git a/mongoengine/base.py b/mongoengine/base.py index e59119eb..94f00cbf 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -492,7 +492,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): raise ValueError("Abstract document cannot have non-abstract base") return super_new(cls, name, bases, attrs) - collection = name.lower() + collection = ''.join('_%s' % c if c.isupper() else c for c in name).strip('_').lower() id_field = None base_indexes = [] diff --git a/tests/document.py b/tests/document.py index c10c903f..28d61332 100644 --- a/tests/document.py +++ b/tests/document.py @@ -62,22 +62,72 @@ class DocumentTest(unittest.TestCase): # Ensure Document isn't treated like an actual document self.assertFalse(hasattr(Document, '_fields')) - def test_dynamic_collection_naming(self): + def test_collection_name(self): + """Ensure that a collection with a specified name may be used. + """ - def create_collection_name(cls): - return "PERSON" + class DefaultNamingTest(Document): + pass + self.assertEquals('default_naming_test', DefaultNamingTest._get_collection_name()) - class DynamicPerson(Document): - name = StringField() - age = IntField() + class CustomNamingTest(Document): + meta = {'collection': 'pimp_my_collection'} - meta = {'collection': create_collection_name} + self.assertEquals('pimp_my_collection', CustomNamingTest._get_collection_name()) - collection = DynamicPerson._get_collection_name() - self.assertEquals(collection, 'PERSON') + class DynamicNamingTest(Document): + meta = {'collection': lambda c: "DYNAMO"} + self.assertEquals('DYNAMO', DynamicNamingTest._get_collection_name()) - DynamicPerson(name='Test User', age=30).save() - self.assertTrue(collection in self.db.collection_names()) + # Use Abstract class to handle backwards compatibility + class BaseDocument(Document): + meta = { + 'abstract': True, + 'collection': lambda c: c.__name__.lower() + } + + class OldNamingConvention(BaseDocument): + pass + self.assertEquals('oldnamingconvention', OldNamingConvention._get_collection_name()) + + class InheritedAbstractNamingTest(BaseDocument): + meta = {'collection': 'wibble'} + self.assertEquals('wibble', InheritedAbstractNamingTest._get_collection_name()) + + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + + class NonAbstractBase(Document): + pass + + class InheritedDocumentFailTest(NonAbstractBase): + meta = {'collection': 'fail'} + + self.assertTrue(issubclass(w[0].category, SyntaxWarning)) + self.assertEquals('non_abstract_base', InheritedDocumentFailTest._get_collection_name()) + + # Mixin tests + class BaseMixin(object): + meta = { + 'collection': lambda c: c.__name__.lower() + } + + class OldMixinNamingConvention(Document, BaseMixin): + pass + self.assertEquals('oldmixinnamingconvention', OldMixinNamingConvention._get_collection_name()) + + class BaseMixin(object): + meta = { + 'collection': lambda c: c.__name__.lower() + } + + class BaseDocument(Document, BaseMixin): + pass + + class MyDocument(BaseDocument): + pass + self.assertEquals('mydocument', OldMixinNamingConvention._get_collection_name()) def test_get_superclasses(self): """Ensure that the correct list of superclasses is assembled.