import unittest import pytest from mongoengine import * from tests.utils import MongoDBTestCase __all__ = ("TestDynamicDocument",) class TestDynamicDocument(MongoDBTestCase): def setUp(self): super(TestDynamicDocument, self).setUp() class Person(DynamicDocument): name = StringField() meta = {"allow_inheritance": True} Person.drop_collection() self.Person = Person def test_simple_dynamic_document(self): """Ensures simple dynamic documents are saved correctly""" p = self.Person() p.name = "James" p.age = 34 assert p.to_mongo() == {"_cls": "Person", "name": "James", "age": 34} assert p.to_mongo().keys() == ["_cls", "name", "age"] p.save() assert p.to_mongo().keys() == ["_id", "_cls", "name", "age"] assert self.Person.objects.first().age == 34 # Confirm no changes to self.Person assert not hasattr(self.Person, "age") def test_change_scope_of_variable(self): """Test changing the scope of a dynamic field has no adverse effects""" p = self.Person() p.name = "Dean" p.misc = 22 p.save() p = self.Person.objects.get() p.misc = {"hello": "world"} p.save() p = self.Person.objects.get() assert p.misc == {"hello": "world"} def test_delete_dynamic_field(self): """Test deleting a dynamic field works""" self.Person.drop_collection() p = self.Person() p.name = "Dean" p.misc = 22 p.save() p = self.Person.objects.get() p.misc = {"hello": "world"} p.save() p = self.Person.objects.get() assert p.misc == {"hello": "world"} collection = self.db[self.Person._get_collection_name()] obj = collection.find_one() assert sorted(obj.keys()) == ["_cls", "_id", "misc", "name"] del p.misc p.save() p = self.Person.objects.get() assert not hasattr(p, "misc") obj = collection.find_one() assert sorted(obj.keys()) == ["_cls", "_id", "name"] def test_reload_after_unsetting(self): p = self.Person() p.misc = 22 p.save() p.update(unset__misc=1) p.reload() def test_reload_dynamic_field(self): self.Person.objects.delete() p = self.Person.objects.create() p.update(age=1) assert len(p._data) == 3 assert sorted(p._data.keys()) == ["_cls", "id", "name"] p.reload() assert len(p._data) == 4 assert sorted(p._data.keys()) == ["_cls", "age", "id", "name"] def test_fields_without_underscore(self): """Ensure we can query dynamic fields""" Person = self.Person p = self.Person(name="Dean") p.save() raw_p = Person.objects.as_pymongo().get(id=p.id) assert raw_p == {"_cls": u"Person", "_id": p.id, "name": u"Dean"} p.name = "OldDean" p.newattr = "garbage" p.save() raw_p = Person.objects.as_pymongo().get(id=p.id) assert raw_p == { "_cls": u"Person", "_id": p.id, "name": "OldDean", "newattr": u"garbage", } def test_fields_containing_underscore(self): """Ensure we can query dynamic fields""" class WeirdPerson(DynamicDocument): name = StringField() _name = StringField() WeirdPerson.drop_collection() p = WeirdPerson(name="Dean", _name="Dean") p.save() raw_p = WeirdPerson.objects.as_pymongo().get(id=p.id) assert raw_p == {"_id": p.id, "_name": u"Dean", "name": u"Dean"} p.name = "OldDean" p._name = "NewDean" p._newattr1 = "garbage" # Unknown fields won't be added p.save() raw_p = WeirdPerson.objects.as_pymongo().get(id=p.id) assert raw_p == {"_id": p.id, "_name": u"NewDean", "name": u"OldDean"} def test_dynamic_document_queries(self): """Ensure we can query dynamic fields""" p = self.Person() p.name = "Dean" p.age = 22 p.save() assert 1 == self.Person.objects(age=22).count() p = self.Person.objects(age=22) p = p.get() assert 22 == p.age def test_complex_dynamic_document_queries(self): class Person(DynamicDocument): name = StringField() Person.drop_collection() p = Person(name="test") p.age = "ten" p.save() p1 = Person(name="test1") p1.age = "less then ten and a half" p1.save() p2 = Person(name="test2") p2.age = 10 p2.save() assert Person.objects(age__icontains="ten").count() == 2 assert Person.objects(age__gte=10).count() == 1 def test_complex_data_lookups(self): """Ensure you can query dynamic document dynamic fields""" p = self.Person() p.misc = {"hello": "world"} p.save() assert 1 == self.Person.objects(misc__hello="world").count() def test_three_level_complex_data_lookups(self): """Ensure you can query three level document dynamic fields""" self.Person.objects.create(misc={"hello": {"hello2": "world"}}) assert 1 == self.Person.objects(misc__hello__hello2="world").count() def test_complex_embedded_document_validation(self): """Ensure embedded dynamic documents may be validated""" class Embedded(DynamicEmbeddedDocument): content = URLField() class Doc(DynamicDocument): pass Doc.drop_collection() doc = Doc() embedded_doc_1 = Embedded(content="http://mongoengine.org") embedded_doc_1.validate() embedded_doc_2 = Embedded(content="this is not a url") with pytest.raises(ValidationError): embedded_doc_2.validate() doc.embedded_field_1 = embedded_doc_1 doc.embedded_field_2 = embedded_doc_2 with pytest.raises(ValidationError): doc.validate() def test_inheritance(self): """Ensure that dynamic document plays nice with inheritance""" class Employee(self.Person): salary = IntField() Employee.drop_collection() assert "name" in Employee._fields assert "salary" in Employee._fields assert Employee._get_collection_name() == self.Person._get_collection_name() joe_bloggs = Employee() joe_bloggs.name = "Joe Bloggs" joe_bloggs.salary = 10 joe_bloggs.age = 20 joe_bloggs.save() assert 1 == self.Person.objects(age=20).count() assert 1 == Employee.objects(age=20).count() joe_bloggs = self.Person.objects.first() assert isinstance(joe_bloggs, Employee) def test_embedded_dynamic_document(self): """Test dynamic embedded documents""" class Embedded(DynamicEmbeddedDocument): pass class Doc(DynamicDocument): pass Doc.drop_collection() doc = Doc() embedded_1 = Embedded() embedded_1.string_field = "hello" embedded_1.int_field = 1 embedded_1.dict_field = {"hello": "world"} embedded_1.list_field = ["1", 2, {"hello": "world"}] doc.embedded_field = embedded_1 assert doc.to_mongo() == { "embedded_field": { "_cls": "Embedded", "string_field": "hello", "int_field": 1, "dict_field": {"hello": "world"}, "list_field": ["1", 2, {"hello": "world"}], } } doc.save() doc = Doc.objects.first() assert doc.embedded_field.__class__ == Embedded assert doc.embedded_field.string_field == "hello" assert doc.embedded_field.int_field == 1 assert doc.embedded_field.dict_field == {"hello": "world"} assert doc.embedded_field.list_field == ["1", 2, {"hello": "world"}] def test_complex_embedded_documents(self): """Test complex dynamic embedded documents setups""" class Embedded(DynamicEmbeddedDocument): pass class Doc(DynamicDocument): pass Doc.drop_collection() doc = Doc() embedded_1 = Embedded() embedded_1.string_field = "hello" embedded_1.int_field = 1 embedded_1.dict_field = {"hello": "world"} embedded_2 = Embedded() embedded_2.string_field = "hello" embedded_2.int_field = 1 embedded_2.dict_field = {"hello": "world"} embedded_2.list_field = ["1", 2, {"hello": "world"}] embedded_1.list_field = ["1", 2, embedded_2] doc.embedded_field = embedded_1 assert doc.to_mongo() == { "embedded_field": { "_cls": "Embedded", "string_field": "hello", "int_field": 1, "dict_field": {"hello": "world"}, "list_field": [ "1", 2, { "_cls": "Embedded", "string_field": "hello", "int_field": 1, "dict_field": {"hello": "world"}, "list_field": ["1", 2, {"hello": "world"}], }, ], } } doc.save() doc = Doc.objects.first() assert doc.embedded_field.__class__ == Embedded assert doc.embedded_field.string_field == "hello" assert doc.embedded_field.int_field == 1 assert doc.embedded_field.dict_field == {"hello": "world"} assert doc.embedded_field.list_field[0] == "1" assert doc.embedded_field.list_field[1] == 2 embedded_field = doc.embedded_field.list_field[2] assert embedded_field.__class__ == Embedded assert embedded_field.string_field == "hello" assert embedded_field.int_field == 1 assert embedded_field.dict_field == {"hello": "world"} assert embedded_field.list_field == ["1", 2, {"hello": "world"}] def test_dynamic_and_embedded(self): """Ensure embedded documents play nicely""" class Address(EmbeddedDocument): city = StringField() class Person(DynamicDocument): name = StringField() Person.drop_collection() Person(name="Ross", address=Address(city="London")).save() person = Person.objects.first() person.address.city = "Lundenne" person.save() assert Person.objects.first().address.city == "Lundenne" person = Person.objects.first() person.address = Address(city="Londinium") person.save() assert Person.objects.first().address.city == "Londinium" person = Person.objects.first() person.age = 35 person.save() assert Person.objects.first().age == 35 def test_dynamic_embedded_works_with_only(self): """Ensure custom fieldnames on a dynamic embedded document are found by qs.only()""" class Address(DynamicEmbeddedDocument): city = StringField() class Person(DynamicDocument): address = EmbeddedDocumentField(Address) Person.drop_collection() Person( name="Eric", address=Address(city="San Francisco", street_number="1337") ).save() assert Person.objects.first().address.street_number == "1337" assert ( Person.objects.only("address__street_number").first().address.street_number == "1337" ) def test_dynamic_and_embedded_dict_access(self): """Ensure embedded dynamic documents work with dict[] style access""" class Address(EmbeddedDocument): city = StringField() class Person(DynamicDocument): name = StringField() Person.drop_collection() Person(name="Ross", address=Address(city="London")).save() person = Person.objects.first() person.attrval = "This works" person["phone"] = "555-1212" # but this should too # Same thing two levels deep person["address"]["city"] = "Lundenne" person.save() assert Person.objects.first().address.city == "Lundenne" assert Person.objects.first().phone == "555-1212" person = Person.objects.first() person.address = Address(city="Londinium") person.save() assert Person.objects.first().address.city == "Londinium" person = Person.objects.first() person["age"] = 35 person.save() assert Person.objects.first().age == 35 if __name__ == "__main__": unittest.main()