问题
I am working with mongoengine and trying to do a simple migration. I have a field which I would like to migrate from being a StringField to a ReferenceField to another Object. I planned on doing the migration by hand, first constructing the new object based on the string that comes from the old StringField and then setting it explicitly.
The problem is I cannot even access one of the top level documents anymore once I changed the field type. Is it required to create a "dummy" field in the my document's class code as a placeholder for the new field? This seems kludgy to me so I assume there is a better way?
Here is the error, which is because the field coming out of the DB (StringField) is not consistent with a reference field.
/usr/lib/python2.7/site-packages/mongoengine/queryset/base.pyc in __getitem__(self, key)
149 return queryset._get_as_pymongo(queryset._cursor.next())
150 return queryset._document._from_son(queryset._cursor[key],
--> 151 _auto_dereference=self._auto_dereference)
152 raise AttributeError
153
/usr/lib/python2.7/site-packages/mongoengine/base/document.pyc in _from_son(cls, son, _auto_dereference)
568 try:
569 data[field_name] = (value if value is None
--> 570 else field.to_python(value))
571 if field_name != field.db_field:
572 del data[field.db_field]
/usr/lib/python2.7/site-packages/mongoengine/fields.pyc in to_python(self, value)
935 not isinstance(value, (DBRef, Document, EmbeddedDocument))):
936 collection = self.document_type._get_collection_name()
--> 937 value = DBRef(collection, self.document_type.id.to_python(value))
938 return value
939
/usr/lib/python2.7/site-packages/mongoengine/base/fields.pyc in to_python(self, value)
390 def to_python(self, value):
391 if not isinstance(value, ObjectId):
--> 392 value = ObjectId(value)
393 return value
394
/usr/lib/python2.7/site-packages/bson/objectid.pyc in __init__(self, oid)
88 self.__generate()
89 else:
---> 90 self.__validate(oid)
91
92 @classmethod
/usr/lib/python2.7/site-packages/bson/objectid.pyc in __validate(self, oid)
192 raise InvalidId("%s is not a valid ObjectId" % oid)
193 else:
--> 194 raise InvalidId("%s is not a valid ObjectId" % oid)
195 else:
196 raise TypeError("id must be an instance of (%s, %s, ObjectId), "
InvalidId: Was Dirty: a2111fe89383bb562738b81c2b63fe78e877ed32 is not a valid ObjectId
回答1:
I've always migrated things by hand using an intermediate collection or an intermediate field in the same collection, but this example makes it look like you don't have to. Stack overflow hates external links so I'm including the example verbatim below. BTW be careful with that "drop_collection" part!
import unittest
from mongoengine import *
class Test(unittest.TestCase):
def setUp(self):
conn = connect(db='mongoenginetest')
def create_old_data(self):
class Person(Document):
name = StringField()
age = FloatField() # Save as string
meta = {'allow_inheritance': True}
Person.drop_collection()
Person(name="Rozza", age=35.00).save()
rozza = Person._get_collection().find_one()
self.assertEqual(35.00, rozza['age'])
def test_migration(self):
self.create_old_data()
class Person(Document):
name = StringField()
age = StringField()
meta = {'allow_inheritance': True}
for p in Person.objects:
p.age = "%s" % p.age
p.save()
rozza = Person.objects.first()
self.assertEqual("35.0", rozza.age)
if __name__ == '__main__':
unittest.main()
回答2:
The 2 options I would suggest for your migration script:
- using DynamicField on the field you must migrate should allow you to read ObjectIds and store back DBRefs
- doing the migration in pymongo directly (pymongo's collection is accessible through Person._get_collection()) and looping over the items, updating & saving
来源:https://stackoverflow.com/questions/20618863/migrations-in-mongoengine-invalidid