Add GenericLazyReferenceField
This commit is contained in:
parent
e6c0280b40
commit
35d0458228
@ -4,7 +4,7 @@ Changelog
|
|||||||
|
|
||||||
Changes in 0.15.0
|
Changes in 0.15.0
|
||||||
=================
|
=================
|
||||||
- Add LazyReferenceField to address #1230
|
- Add LazyReferenceField and GenericLazyReferenceField to address #1230
|
||||||
|
|
||||||
Changes in 0.14.1
|
Changes in 0.14.1
|
||||||
=================
|
=================
|
||||||
|
@ -47,8 +47,7 @@ __all__ = (
|
|||||||
'GenericEmbeddedDocumentField', 'DynamicField', 'ListField',
|
'GenericEmbeddedDocumentField', 'DynamicField', 'ListField',
|
||||||
'SortedListField', 'EmbeddedDocumentListField', 'DictField',
|
'SortedListField', 'EmbeddedDocumentListField', 'DictField',
|
||||||
'MapField', 'ReferenceField', 'CachedReferenceField',
|
'MapField', 'ReferenceField', 'CachedReferenceField',
|
||||||
'LazyReferenceField',
|
'LazyReferenceField', 'GenericLazyReferenceField',
|
||||||
# 'GenericLazyReferenceField',
|
|
||||||
'GenericReferenceField', 'BinaryField', 'GridFSError', 'GridFSProxy',
|
'GenericReferenceField', 'BinaryField', 'GridFSError', 'GridFSProxy',
|
||||||
'FileField', 'ImageGridFsProxy', 'ImproperlyConfigured', 'ImageField',
|
'FileField', 'ImageGridFsProxy', 'ImproperlyConfigured', 'ImageField',
|
||||||
'GeoPointField', 'PointField', 'LineStringField', 'PolygonField',
|
'GeoPointField', 'PointField', 'LineStringField', 'PolygonField',
|
||||||
@ -1275,6 +1274,12 @@ class GenericReferenceField(BaseField):
|
|||||||
"""A reference to *any* :class:`~mongoengine.document.Document` subclass
|
"""A reference to *any* :class:`~mongoengine.document.Document` subclass
|
||||||
that will be automatically dereferenced on access (lazily).
|
that will be automatically dereferenced on access (lazily).
|
||||||
|
|
||||||
|
Note this field works the same way as :class:`~mongoengine.document.ReferenceField`,
|
||||||
|
doing database I/O access the first time it is accessed (even if it's to access
|
||||||
|
it ``pk`` or ``id`` field).
|
||||||
|
To solve this you should consider using the
|
||||||
|
:class:`~mongoengine.fields.GenericLazyReferenceField`.
|
||||||
|
|
||||||
.. note ::
|
.. note ::
|
||||||
* Any documents used as a generic reference must be registered in the
|
* Any documents used as a generic reference must be registered in the
|
||||||
document registry. Importing the model will automatically register
|
document registry. Importing the model will automatically register
|
||||||
@ -2159,6 +2164,8 @@ class LazyReferenceField(BaseField):
|
|||||||
"""A really lazy reference to a document.
|
"""A really lazy reference to a document.
|
||||||
Unlike the :class:`~mongoengine.fields.ReferenceField` it must be manually
|
Unlike the :class:`~mongoengine.fields.ReferenceField` it must be manually
|
||||||
dereferenced using it ``fetch()`` method.
|
dereferenced using it ``fetch()`` method.
|
||||||
|
|
||||||
|
.. versionadded:: 0.15
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, document_type, passthrough=False, dbref=False,
|
def __init__(self, document_type, passthrough=False, dbref=False,
|
||||||
@ -2274,3 +2281,65 @@ class LazyReferenceField(BaseField):
|
|||||||
|
|
||||||
def lookup_member(self, member_name):
|
def lookup_member(self, member_name):
|
||||||
return self.document_type._fields.get(member_name)
|
return self.document_type._fields.get(member_name)
|
||||||
|
|
||||||
|
|
||||||
|
class GenericLazyReferenceField(GenericReferenceField):
|
||||||
|
"""A reference to *any* :class:`~mongoengine.document.Document` subclass
|
||||||
|
that will be automatically dereferenced on access (lazily).
|
||||||
|
Unlike the :class:`~mongoengine.fields.GenericReferenceField` it must be
|
||||||
|
manually dereferenced using it ``fetch()`` method.
|
||||||
|
|
||||||
|
.. note ::
|
||||||
|
* Any documents used as a generic reference must be registered in the
|
||||||
|
document registry. Importing the model will automatically register
|
||||||
|
it.
|
||||||
|
|
||||||
|
* You can use the choices param to limit the acceptable Document types
|
||||||
|
|
||||||
|
.. versionadded:: 0.15
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.passthrough = kwargs.pop('passthrough', False)
|
||||||
|
super(GenericLazyReferenceField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def _validate_choices(self, value):
|
||||||
|
if isinstance(value, LazyReference):
|
||||||
|
value = value.document_type
|
||||||
|
super(GenericLazyReferenceField, self)._validate_choices(value)
|
||||||
|
|
||||||
|
def __get__(self, instance, owner):
|
||||||
|
if instance is None:
|
||||||
|
return self
|
||||||
|
|
||||||
|
value = instance._data.get(self.name)
|
||||||
|
if isinstance(value, LazyReference):
|
||||||
|
if value.passthrough != self.passthrough:
|
||||||
|
instance._data[self.name] = LazyReference(
|
||||||
|
value.document_type, value.pk, passthrough=self.passthrough)
|
||||||
|
elif value is not None:
|
||||||
|
if isinstance(value, (dict, SON)):
|
||||||
|
value = LazyReference(get_document(value['_cls']), value['_ref'].id, passthrough=self.passthrough)
|
||||||
|
elif isinstance(value, Document):
|
||||||
|
value = LazyReference(type(value), value.pk, passthrough=self.passthrough)
|
||||||
|
instance._data[self.name] = value
|
||||||
|
|
||||||
|
return super(GenericLazyReferenceField, self).__get__(instance, owner)
|
||||||
|
|
||||||
|
def validate(self, value):
|
||||||
|
if isinstance(value, LazyReference) and value.pk is None:
|
||||||
|
self.error('You can only reference documents once they have been'
|
||||||
|
' saved to the database')
|
||||||
|
return super(GenericLazyReferenceField, self).validate(value)
|
||||||
|
|
||||||
|
def to_mongo(self, document):
|
||||||
|
if document is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if isinstance(document, LazyReference):
|
||||||
|
return SON((
|
||||||
|
('_cls', document.document_type._class_name),
|
||||||
|
('_ref', document)
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
return super(GenericLazyReferenceField, self).to_mongo(document)
|
||||||
|
@ -933,7 +933,7 @@ class FieldTest(MongoDBTestCase):
|
|||||||
authors = ListField(ReferenceField(User))
|
authors = ListField(ReferenceField(User))
|
||||||
authors_as_lazy = ListField(LazyReferenceField(User))
|
authors_as_lazy = ListField(LazyReferenceField(User))
|
||||||
generic = ListField(GenericReferenceField())
|
generic = ListField(GenericReferenceField())
|
||||||
# generic_as_lazy = ListField(LazyGenericReferenceField())
|
generic_as_lazy = ListField(GenericLazyReferenceField())
|
||||||
|
|
||||||
User.drop_collection()
|
User.drop_collection()
|
||||||
BlogPost.drop_collection()
|
BlogPost.drop_collection()
|
||||||
@ -992,17 +992,17 @@ class FieldTest(MongoDBTestCase):
|
|||||||
post.generic = [user]
|
post.generic = [user]
|
||||||
post.validate()
|
post.validate()
|
||||||
|
|
||||||
# post.generic_as_lazy = [1, 2]
|
post.generic_as_lazy = [1, 2]
|
||||||
# self.assertRaises(ValidationError, post.validate)
|
self.assertRaises(ValidationError, post.validate)
|
||||||
|
|
||||||
# post.generic_as_lazy = [User(), Comment()]
|
post.generic_as_lazy = [User(), Comment()]
|
||||||
# self.assertRaises(ValidationError, post.validate)
|
self.assertRaises(ValidationError, post.validate)
|
||||||
|
|
||||||
# post.generic_as_lazy = [Comment()]
|
post.generic_as_lazy = [Comment()]
|
||||||
# self.assertRaises(ValidationError, post.validate)
|
self.assertRaises(ValidationError, post.validate)
|
||||||
|
|
||||||
# post.generic_as_lazy = [user]
|
post.generic_as_lazy = [user]
|
||||||
# post.validate()
|
post.validate()
|
||||||
|
|
||||||
def test_sorted_list_sorting(self):
|
def test_sorted_list_sorting(self):
|
||||||
"""Ensure that a sorted list field properly sorts values.
|
"""Ensure that a sorted list field properly sorts values.
|
||||||
@ -4872,5 +4872,185 @@ class LazyReferenceFieldTest(MongoDBTestCase):
|
|||||||
self.assertNotEqual(other_animalref, animal)
|
self.assertNotEqual(other_animalref, animal)
|
||||||
|
|
||||||
|
|
||||||
|
class GenericLazyReferenceFieldTest(MongoDBTestCase):
|
||||||
|
def test_generic_lazy_reference_simple(self):
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
class Ocurrence(Document):
|
||||||
|
person = StringField()
|
||||||
|
animal = GenericLazyReferenceField()
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocurrence.drop_collection()
|
||||||
|
|
||||||
|
animal = Animal(name="Leopard", tag="heavy").save()
|
||||||
|
Ocurrence(person="test", animal=animal).save()
|
||||||
|
p = Ocurrence.objects.get()
|
||||||
|
self.assertIsInstance(p.animal, LazyReference)
|
||||||
|
fetched_animal = p.animal.fetch()
|
||||||
|
self.assertEqual(fetched_animal, animal)
|
||||||
|
# `fetch` keep cache on referenced document by default...
|
||||||
|
animal.tag = "not so heavy"
|
||||||
|
animal.save()
|
||||||
|
double_fetch = p.animal.fetch()
|
||||||
|
self.assertIs(fetched_animal, double_fetch)
|
||||||
|
self.assertEqual(double_fetch.tag, "heavy")
|
||||||
|
# ...unless specified otherwise
|
||||||
|
fetch_force = p.animal.fetch(force=True)
|
||||||
|
self.assertIsNot(fetch_force, fetched_animal)
|
||||||
|
self.assertEqual(fetch_force.tag, "not so heavy")
|
||||||
|
|
||||||
|
def test_generic_lazy_reference_choices(self):
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
class Vegetal(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
class Mineral(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
class Ocurrence(Document):
|
||||||
|
living_thing = GenericLazyReferenceField(choices=[Animal, Vegetal])
|
||||||
|
thing = GenericLazyReferenceField()
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Vegetal.drop_collection()
|
||||||
|
Mineral.drop_collection()
|
||||||
|
Ocurrence.drop_collection()
|
||||||
|
|
||||||
|
animal = Animal(name="Leopard").save()
|
||||||
|
vegetal = Vegetal(name="Oak").save()
|
||||||
|
mineral = Mineral(name="Granite").save()
|
||||||
|
|
||||||
|
occ_animal = Ocurrence(living_thing=animal, thing=animal).save()
|
||||||
|
occ_vegetal = Ocurrence(living_thing=vegetal, thing=vegetal).save()
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
Ocurrence(living_thing=mineral).save()
|
||||||
|
|
||||||
|
occ = Ocurrence.objects.get(living_thing=animal)
|
||||||
|
self.assertEqual(occ, occ_animal)
|
||||||
|
self.assertIsInstance(occ.thing, LazyReference)
|
||||||
|
self.assertIsInstance(occ.living_thing, LazyReference)
|
||||||
|
|
||||||
|
occ.thing = vegetal
|
||||||
|
occ.living_thing = vegetal
|
||||||
|
occ.save()
|
||||||
|
|
||||||
|
occ.thing = mineral
|
||||||
|
occ.living_thing = mineral
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
occ.save()
|
||||||
|
|
||||||
|
def test_generic_lazy_reference_set(self):
|
||||||
|
class Animal(Document):
|
||||||
|
meta = {'allow_inheritance': True}
|
||||||
|
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
class Ocurrence(Document):
|
||||||
|
person = StringField()
|
||||||
|
animal = GenericLazyReferenceField()
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocurrence.drop_collection()
|
||||||
|
|
||||||
|
class SubAnimal(Animal):
|
||||||
|
nick = StringField()
|
||||||
|
|
||||||
|
animal = Animal(name="Leopard", tag="heavy").save()
|
||||||
|
sub_animal = SubAnimal(nick='doggo', name='dog').save()
|
||||||
|
for ref in (
|
||||||
|
animal,
|
||||||
|
LazyReference(Animal, animal.pk),
|
||||||
|
{'_cls': 'Animal', '_ref': DBRef(animal._get_collection_name(), animal.pk)},
|
||||||
|
|
||||||
|
sub_animal,
|
||||||
|
LazyReference(SubAnimal, sub_animal.pk),
|
||||||
|
{'_cls': 'SubAnimal', '_ref': DBRef(sub_animal._get_collection_name(), sub_animal.pk)},
|
||||||
|
):
|
||||||
|
p = Ocurrence(person="test", animal=ref).save()
|
||||||
|
p.reload()
|
||||||
|
self.assertIsInstance(p.animal, (LazyReference, Document))
|
||||||
|
p.animal.fetch()
|
||||||
|
|
||||||
|
def test_generic_lazy_reference_bad_set(self):
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
class Ocurrence(Document):
|
||||||
|
person = StringField()
|
||||||
|
animal = GenericLazyReferenceField(choices=['Animal'])
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocurrence.drop_collection()
|
||||||
|
|
||||||
|
class BadDoc(Document):
|
||||||
|
pass
|
||||||
|
|
||||||
|
animal = Animal(name="Leopard", tag="heavy").save()
|
||||||
|
baddoc = BadDoc().save()
|
||||||
|
for bad in (
|
||||||
|
42,
|
||||||
|
'foo',
|
||||||
|
baddoc,
|
||||||
|
LazyReference(BadDoc, animal.pk)
|
||||||
|
):
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
p = Ocurrence(person="test", animal=bad).save()
|
||||||
|
|
||||||
|
def test_generic_lazy_reference_query_conversion(self):
|
||||||
|
class Member(Document):
|
||||||
|
user_num = IntField(primary_key=True)
|
||||||
|
|
||||||
|
class BlogPost(Document):
|
||||||
|
title = StringField()
|
||||||
|
author = GenericLazyReferenceField()
|
||||||
|
|
||||||
|
Member.drop_collection()
|
||||||
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
|
m1 = Member(user_num=1)
|
||||||
|
m1.save()
|
||||||
|
m2 = Member(user_num=2)
|
||||||
|
m2.save()
|
||||||
|
|
||||||
|
post1 = BlogPost(title='post 1', author=m1)
|
||||||
|
post1.save()
|
||||||
|
|
||||||
|
post2 = BlogPost(title='post 2', author=m2)
|
||||||
|
post2.save()
|
||||||
|
|
||||||
|
post = BlogPost.objects(author=m1).first()
|
||||||
|
self.assertEqual(post.id, post1.id)
|
||||||
|
|
||||||
|
post = BlogPost.objects(author=m2).first()
|
||||||
|
self.assertEqual(post.id, post2.id)
|
||||||
|
|
||||||
|
# Same thing by passing a LazyReference instance
|
||||||
|
post = BlogPost.objects(author=LazyReference(Member, m2.pk)).first()
|
||||||
|
self.assertEqual(post.id, post2.id)
|
||||||
|
|
||||||
|
def test_generic_lazy_reference_not_set(self):
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
class Ocurrence(Document):
|
||||||
|
person = StringField()
|
||||||
|
animal = GenericLazyReferenceField()
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocurrence.drop_collection()
|
||||||
|
|
||||||
|
Ocurrence(person='foo').save()
|
||||||
|
p = Ocurrence.objects.get()
|
||||||
|
self.assertIs(p.animal, None)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user