ReferenceField now store ObjectId's by default rather than DBRef (#290)

This commit is contained in:
Ross Lawley 2013-04-24 12:14:34 +00:00
parent c59ea26845
commit c60ea40828
8 changed files with 92 additions and 32 deletions

View File

@ -4,6 +4,7 @@ Changelog
Changes in 0.8.X Changes in 0.8.X
================ ================
- ReferenceField now store ObjectId's by default rather than DBRef (#290)
- Added ImageField support for inline replacements (#86) - Added ImageField support for inline replacements (#86)
- Added SequenceField.set_next_value(value) helper (#159) - Added SequenceField.set_next_value(value) helper (#159)
- Updated .only() behaviour - now like exclude it is chainable (#202) - Updated .only() behaviour - now like exclude it is chainable (#202)

View File

@ -76,6 +76,35 @@ the case and the data is set only in the ``document._data`` dictionary: ::
File "<stdin>", line 1, in <module> File "<stdin>", line 1, in <module>
AttributeError: 'Animal' object has no attribute 'size' AttributeError: 'Animal' object has no attribute 'size'
ReferenceField
--------------
ReferenceFields now store ObjectId's by default - this is more efficient than
DBRefs as we already know what Document types they reference.
# Old code
class Animal(Document):
name = ReferenceField('self')
# New code to keep dbrefs
class Animal(Document):
name = ReferenceField('self', dbref=True)
To migrate all the references you need to touch each object and mark it as dirty
eg::
# Doc definition
class Person(Document):
name = StringField()
parent = ReferenceField('self')
friends = ListField(ReferenceField('self'))
# Mark all ReferenceFields as dirty and save
for p in Person.objects:
p._mark_as_dirty('parent')
p._mark_as_dirty('friends')
p.save()
Querysets Querysets
========= =========

View File

@ -772,7 +772,7 @@ class ReferenceField(BaseField):
.. versionchanged:: 0.5 added `reverse_delete_rule` .. versionchanged:: 0.5 added `reverse_delete_rule`
""" """
def __init__(self, document_type, dbref=None, def __init__(self, document_type, dbref=False,
reverse_delete_rule=DO_NOTHING, **kwargs): reverse_delete_rule=DO_NOTHING, **kwargs):
"""Initialises the Reference Field. """Initialises the Reference Field.
@ -786,12 +786,7 @@ class ReferenceField(BaseField):
self.error('Argument to ReferenceField constructor must be a ' self.error('Argument to ReferenceField constructor must be a '
'document class or a string') 'document class or a string')
if dbref is None: self.dbref = dbref
msg = ("ReferenceFields will default to using ObjectId "
"in 0.8, set DBRef=True if this isn't desired")
warnings.warn(msg, FutureWarning)
self.dbref = dbref if dbref is not None else True # To change in 0.8
self.document_type_obj = document_type self.document_type_obj = document_type
self.reverse_delete_rule = reverse_delete_rule self.reverse_delete_rule = reverse_delete_rule
super(ReferenceField, self).__init__(**kwargs) super(ReferenceField, self).__init__(**kwargs)

View File

@ -30,28 +30,6 @@ class AllWarnings(unittest.TestCase):
# restore default handling of warnings # restore default handling of warnings
warnings.showwarning = self.showwarning_default warnings.showwarning = self.showwarning_default
def test_dbref_reference_field_future_warning(self):
class Person(Document):
name = StringField()
parent = ReferenceField('self')
Person.drop_collection()
p1 = Person()
p1.parent = None
p1.save()
p2 = Person(name="Wilson Jr")
p2.parent = p1
p2.save(cascade=False)
self.assertTrue(len(self.warning_list) > 0)
warning = self.warning_list[0]
self.assertEqual(FutureWarning, warning["category"])
self.assertTrue("ReferenceFields will default to using ObjectId"
in str(warning["message"]))
def test_document_save_cascade_future_warning(self): def test_document_save_cascade_future_warning(self):
class Person(Document): class Person(Document):

View File

@ -1,3 +1,5 @@
from convert_to_new_inheritance_model import *
from refrencefield_dbref_to_object_id import *
from turn_off_inheritance import * from turn_off_inheritance import *
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -38,7 +38,7 @@ class ConvertToNewInheritanceModel(unittest.TestCase):
# 3. Confirm extra data is removed # 3. Confirm extra data is removed
count = collection.find({'_types': {"$exists": True}}).count() count = collection.find({'_types': {"$exists": True}}).count()
assert count == 0 self.assertEqual(0, count)
# 4. Remove indexes # 4. Remove indexes
info = collection.index_information() info = collection.index_information()

View File

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
import unittest
from mongoengine import Document, connect
from mongoengine.connection import get_db
from mongoengine.fields import StringField, ReferenceField, ListField
__all__ = ('ConvertToObjectIdsModel', )
class ConvertToObjectIdsModel(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
def test_how_to_convert_to_object_id_reference_fields(self):
"""Demonstrates migrating from 0.7 to 0.8
"""
# 1. Old definition - using dbrefs
class Person(Document):
name = StringField()
parent = ReferenceField('self', dbref=True)
friends = ListField(ReferenceField('self', dbref=True))
Person.drop_collection()
p1 = Person(name="Wilson", parent=None).save()
f1 = Person(name="John", parent=None).save()
f2 = Person(name="Paul", parent=None).save()
f3 = Person(name="George", parent=None).save()
f4 = Person(name="Ringo", parent=None).save()
Person(name="Wilson Jr", parent=p1, friends=[f1, f2, f3, f4]).save()
# 2. Start the migration by changing the schema
# Change ReferenceField as now dbref defaults to False
class Person(Document):
name = StringField()
parent = ReferenceField('self')
friends = ListField(ReferenceField('self'))
# 3. Loop all the objects and mark parent as changed
for p in Person.objects:
p._mark_as_changed('parent')
p._mark_as_changed('friends')
p.save()
# 4. Confirmation of the fix!
wilson = Person.objects(name="Wilson Jr").as_pymongo()[0]
self.assertEqual(p1.id, wilson['parent'])
self.assertEqual([f1.id, f2.id, f3.id, f4.id], wilson['friends'])

View File

@ -72,6 +72,7 @@ class SignalTests(unittest.TestCase):
else: else:
signal_output.append('Not loaded') signal_output.append('Not loaded')
self.Author = Author self.Author = Author
Author.drop_collection()
class Another(Document): class Another(Document):
name = StringField() name = StringField()
@ -110,6 +111,7 @@ class SignalTests(unittest.TestCase):
signal_output.append('post_delete Another signal, %s' % document) signal_output.append('post_delete Another signal, %s' % document)
self.Another = Another self.Another = Another
Another.drop_collection()
class ExplicitId(Document): class ExplicitId(Document):
id = IntField(primary_key=True) id = IntField(primary_key=True)
@ -123,7 +125,8 @@ class SignalTests(unittest.TestCase):
signal_output.append('Is updated') signal_output.append('Is updated')
self.ExplicitId = ExplicitId self.ExplicitId = ExplicitId
self.ExplicitId.objects.delete() ExplicitId.drop_collection()
# Save up the number of connected signals so that we can check at the # Save up the number of connected signals so that we can check at the
# end that all the signals we register get properly unregistered # end that all the signals we register get properly unregistered
self.pre_signals = ( self.pre_signals = (