ReferenceFields can now reference abstract Document types

A class that inherits from an abstract Document type is stored in the database
as a reference with a 'cls' field that is the class name of the document being
stored.

Fixes #837
This commit is contained in:
Ashley Whetter 2015-11-10 14:29:25 +00:00
parent 19cbb442ee
commit 013227323d
2 changed files with 69 additions and 6 deletions

View File

@ -928,9 +928,14 @@ class ReferenceField(BaseField):
self._auto_dereference = instance._fields[self.name]._auto_dereference self._auto_dereference = instance._fields[self.name]._auto_dereference
# Dereference DBRefs # Dereference DBRefs
if self._auto_dereference and isinstance(value, DBRef): if self._auto_dereference and isinstance(value, DBRef):
value = self.document_type._get_db().dereference(value) if hasattr(value, 'cls'):
# Dereference using the class type specified in the reference
cls = get_document(value.cls)
else:
cls = self.document_type
value = cls._get_db().dereference(value)
if value is not None: if value is not None:
instance._data[self.name] = self.document_type._from_son(value) instance._data[self.name] = cls._from_son(value)
return super(ReferenceField, self).__get__(instance, owner) return super(ReferenceField, self).__get__(instance, owner)
@ -940,22 +945,30 @@ class ReferenceField(BaseField):
return document.id return document.id
return document return document
id_field_name = self.document_type._meta['id_field']
id_field = self.document_type._fields[id_field_name]
if isinstance(document, Document): if isinstance(document, Document):
# We need the id from the saved object to create the DBRef # We need the id from the saved object to create the DBRef
id_ = document.pk id_ = document.pk
if id_ is None: if id_ is None:
self.error('You can only reference documents once they have' self.error('You can only reference documents once they have'
' been saved to the database') ' been saved to the database')
# Use the attributes from the document instance, so that they
# override the attributes of this field's document type
cls = document
else: else:
id_ = document id_ = document
cls = self.document_type
id_field_name = cls._meta['id_field']
id_field = cls._fields[id_field_name]
id_ = id_field.to_mongo(id_) id_ = id_field.to_mongo(id_)
if self.dbref: if self.dbref:
collection = self.document_type._get_collection_name() collection = cls._get_collection_name()
return DBRef(collection, id_) return DBRef(collection, id_)
elif self.document_type._meta.get('abstract'):
collection = cls._get_collection_name()
return DBRef(collection, id_, cls=cls._class_name)
return id_ return id_

View File

@ -2281,6 +2281,56 @@ class FieldTest(unittest.TestCase):
Member.drop_collection() Member.drop_collection()
BlogPost.drop_collection() BlogPost.drop_collection()
def test_reference_class_with_abstract_parent(self):
"""Ensure that a class with an abstract parent can be referenced.
"""
class Sibling(Document):
name = StringField()
meta = {"abstract": True}
class Sister(Sibling):
pass
class Brother(Sibling):
sibling = ReferenceField(Sibling)
Sister.drop_collection()
Brother.drop_collection()
sister = Sister(name="Alice")
sister.save()
brother = Brother(name="Bob", sibling=sister)
brother.save()
self.assertEquals(Brother.objects[0].sibling.name, sister.name)
Sister.drop_collection()
Brother.drop_collection()
def test_reference_abstract_class(self):
"""Ensure that an abstract class instance cannot be used in the
reference of that abstract class.
"""
class Sibling(Document):
name = StringField()
meta = {"abstract": True}
class Sister(Sibling):
pass
class Brother(Sibling):
sibling = ReferenceField(Sibling)
Sister.drop_collection()
Brother.drop_collection()
sister = Sibling(name="Alice")
brother = Brother(name="Bob", sibling=sister)
self.assertRaises(ValidationError, brother.save)
Sister.drop_collection()
Brother.drop_collection()
def test_generic_reference(self): def test_generic_reference(self):
"""Ensure that a GenericReferenceField properly dereferences items. """Ensure that a GenericReferenceField properly dereferences items.
""" """