Fix querying on (Generic)EmbeddedDocument subclasses fields
This commit is contained in:
		| @@ -4,6 +4,7 @@ Changelog | |||||||
|  |  | ||||||
| Development | Development | ||||||
| =========== | =========== | ||||||
|  | - Fix querying on (Generic)EmbeddedDocument subclasses fields #475 | ||||||
| - expose `mongoengine.connection.disconnect` and `mongoengine.connection.disconnect_all` | - expose `mongoengine.connection.disconnect` and `mongoengine.connection.disconnect_all` | ||||||
| - Fix disconnect function #566 #1599 #605 #607 #1213 #565 | - Fix disconnect function #566 #1599 #605 #607 #1213 #565 | ||||||
| - Improve connect/disconnect documentations | - Improve connect/disconnect documentations | ||||||
|   | |||||||
| @@ -700,7 +700,11 @@ class EmbeddedDocumentField(BaseField): | |||||||
|         self.document_type.validate(value, clean) |         self.document_type.validate(value, clean) | ||||||
|  |  | ||||||
|     def lookup_member(self, member_name): |     def lookup_member(self, member_name): | ||||||
|         return self.document_type._fields.get(member_name) |         doc_and_subclasses = [self.document_type] + self.document_type.__subclasses__() | ||||||
|  |         for doc_type in doc_and_subclasses: | ||||||
|  |             field = doc_type._fields.get(member_name) | ||||||
|  |             if field: | ||||||
|  |                 return field | ||||||
|  |  | ||||||
|     def prepare_query_value(self, op, value): |     def prepare_query_value(self, op, value): | ||||||
|         if value is not None and not isinstance(value, self.document_type): |         if value is not None and not isinstance(value, self.document_type): | ||||||
| @@ -747,12 +751,13 @@ class GenericEmbeddedDocumentField(BaseField): | |||||||
|         value.validate(clean=clean) |         value.validate(clean=clean) | ||||||
|  |  | ||||||
|     def lookup_member(self, member_name): |     def lookup_member(self, member_name): | ||||||
|         if self.choices: |         document_choices = self.choices or [] | ||||||
|             for choice in self.choices: |         for document_choice in document_choices: | ||||||
|                 field = choice._fields.get(member_name) |             doc_and_subclasses = [document_choice] + document_choice.__subclasses__() | ||||||
|  |             for doc_type in doc_and_subclasses: | ||||||
|  |                 field = doc_type._fields.get(member_name) | ||||||
|                 if field: |                 if field: | ||||||
|                     return field |                     return field | ||||||
|         return None |  | ||||||
|  |  | ||||||
|     def to_mongo(self, document, use_db_field=True, fields=None): |     def to_mongo(self, document, use_db_field=True, fields=None): | ||||||
|         if document is None: |         if document is None: | ||||||
|   | |||||||
| @@ -8,11 +8,10 @@ from bson import DBRef, ObjectId, SON | |||||||
|  |  | ||||||
| from mongoengine import Document, StringField, IntField, DateTimeField, DateField, ValidationError, \ | from mongoengine import Document, StringField, IntField, DateTimeField, DateField, ValidationError, \ | ||||||
|     ComplexDateTimeField, FloatField, ListField, ReferenceField, DictField, EmbeddedDocument, EmbeddedDocumentField, \ |     ComplexDateTimeField, FloatField, ListField, ReferenceField, DictField, EmbeddedDocument, EmbeddedDocumentField, \ | ||||||
|     GenericReferenceField, DoesNotExist, NotRegistered, GenericEmbeddedDocumentField, OperationError, DynamicField, \ |     GenericReferenceField, DoesNotExist, NotRegistered, OperationError, DynamicField, \ | ||||||
|     FieldDoesNotExist, EmbeddedDocumentListField, MultipleObjectsReturned, NotUniqueError, BooleanField, ObjectIdField, \ |     FieldDoesNotExist, EmbeddedDocumentListField, MultipleObjectsReturned, NotUniqueError, BooleanField,\ | ||||||
|     SortedListField, GenericLazyReferenceField, LazyReferenceField, DynamicDocument |     ObjectIdField, SortedListField, GenericLazyReferenceField, LazyReferenceField, DynamicDocument | ||||||
| from mongoengine.base import (BaseField, EmbeddedDocumentList, | from mongoengine.base import (BaseField, EmbeddedDocumentList, _document_registry) | ||||||
|                               _document_registry) |  | ||||||
|  |  | ||||||
| from tests.utils import MongoDBTestCase | from tests.utils import MongoDBTestCase | ||||||
|  |  | ||||||
| @@ -1769,79 +1768,6 @@ class FieldTest(MongoDBTestCase): | |||||||
|         with self.assertRaises(ValidationError): |         with self.assertRaises(ValidationError): | ||||||
|             shirt.validate() |             shirt.validate() | ||||||
|  |  | ||||||
|     def test_choices_validation_documents(self): |  | ||||||
|         """ |  | ||||||
|         Ensure fields with document choices validate given a valid choice. |  | ||||||
|         """ |  | ||||||
|         class UserComments(EmbeddedDocument): |  | ||||||
|             author = StringField() |  | ||||||
|             message = StringField() |  | ||||||
|  |  | ||||||
|         class BlogPost(Document): |  | ||||||
|             comments = ListField( |  | ||||||
|                 GenericEmbeddedDocumentField(choices=(UserComments,)) |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|         # Ensure Validation Passes |  | ||||||
|         BlogPost(comments=[ |  | ||||||
|             UserComments(author='user2', message='message2'), |  | ||||||
|         ]).save() |  | ||||||
|  |  | ||||||
|     def test_choices_validation_documents_invalid(self): |  | ||||||
|         """ |  | ||||||
|         Ensure fields with document choices validate given an invalid choice. |  | ||||||
|         This should throw a ValidationError exception. |  | ||||||
|         """ |  | ||||||
|         class UserComments(EmbeddedDocument): |  | ||||||
|             author = StringField() |  | ||||||
|             message = StringField() |  | ||||||
|  |  | ||||||
|         class ModeratorComments(EmbeddedDocument): |  | ||||||
|             author = StringField() |  | ||||||
|             message = StringField() |  | ||||||
|  |  | ||||||
|         class BlogPost(Document): |  | ||||||
|             comments = ListField( |  | ||||||
|                 GenericEmbeddedDocumentField(choices=(UserComments,)) |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|         # Single Entry Failure |  | ||||||
|         post = BlogPost(comments=[ |  | ||||||
|             ModeratorComments(author='mod1', message='message1'), |  | ||||||
|         ]) |  | ||||||
|         self.assertRaises(ValidationError, post.save) |  | ||||||
|  |  | ||||||
|         # Mixed Entry Failure |  | ||||||
|         post = BlogPost(comments=[ |  | ||||||
|             ModeratorComments(author='mod1', message='message1'), |  | ||||||
|             UserComments(author='user2', message='message2'), |  | ||||||
|         ]) |  | ||||||
|         self.assertRaises(ValidationError, post.save) |  | ||||||
|  |  | ||||||
|     def test_choices_validation_documents_inheritance(self): |  | ||||||
|         """ |  | ||||||
|         Ensure fields with document choices validate given subclass of choice. |  | ||||||
|         """ |  | ||||||
|         class Comments(EmbeddedDocument): |  | ||||||
|             meta = { |  | ||||||
|                 'abstract': True |  | ||||||
|             } |  | ||||||
|             author = StringField() |  | ||||||
|             message = StringField() |  | ||||||
|  |  | ||||||
|         class UserComments(Comments): |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|         class BlogPost(Document): |  | ||||||
|             comments = ListField( |  | ||||||
|                 GenericEmbeddedDocumentField(choices=(Comments,)) |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|         # Save Valid EmbeddedDocument Type |  | ||||||
|         BlogPost(comments=[ |  | ||||||
|             UserComments(author='user2', message='message2'), |  | ||||||
|         ]).save() |  | ||||||
|  |  | ||||||
|     def test_choices_get_field_display(self): |     def test_choices_get_field_display(self): | ||||||
|         """Test dynamic helper for returning the display value of a choices |         """Test dynamic helper for returning the display value of a choices | ||||||
|         field. |         field. | ||||||
| @@ -1958,85 +1884,6 @@ class FieldTest(MongoDBTestCase): | |||||||
|             self.assertEqual(error_dict['size'], SIZE_MESSAGE) |             self.assertEqual(error_dict['size'], SIZE_MESSAGE) | ||||||
|             self.assertEqual(error_dict['color'], COLOR_MESSAGE) |             self.assertEqual(error_dict['color'], COLOR_MESSAGE) | ||||||
|  |  | ||||||
|     def test_generic_embedded_document(self): |  | ||||||
|         class Car(EmbeddedDocument): |  | ||||||
|             name = StringField() |  | ||||||
|  |  | ||||||
|         class Dish(EmbeddedDocument): |  | ||||||
|             food = StringField(required=True) |  | ||||||
|             number = IntField() |  | ||||||
|  |  | ||||||
|         class Person(Document): |  | ||||||
|             name = StringField() |  | ||||||
|             like = GenericEmbeddedDocumentField() |  | ||||||
|  |  | ||||||
|         Person.drop_collection() |  | ||||||
|  |  | ||||||
|         person = Person(name='Test User') |  | ||||||
|         person.like = Car(name='Fiat') |  | ||||||
|         person.save() |  | ||||||
|  |  | ||||||
|         person = Person.objects.first() |  | ||||||
|         self.assertIsInstance(person.like, Car) |  | ||||||
|  |  | ||||||
|         person.like = Dish(food="arroz", number=15) |  | ||||||
|         person.save() |  | ||||||
|  |  | ||||||
|         person = Person.objects.first() |  | ||||||
|         self.assertIsInstance(person.like, Dish) |  | ||||||
|  |  | ||||||
|     def test_generic_embedded_document_choices(self): |  | ||||||
|         """Ensure you can limit GenericEmbeddedDocument choices.""" |  | ||||||
|         class Car(EmbeddedDocument): |  | ||||||
|             name = StringField() |  | ||||||
|  |  | ||||||
|         class Dish(EmbeddedDocument): |  | ||||||
|             food = StringField(required=True) |  | ||||||
|             number = IntField() |  | ||||||
|  |  | ||||||
|         class Person(Document): |  | ||||||
|             name = StringField() |  | ||||||
|             like = GenericEmbeddedDocumentField(choices=(Dish,)) |  | ||||||
|  |  | ||||||
|         Person.drop_collection() |  | ||||||
|  |  | ||||||
|         person = Person(name='Test User') |  | ||||||
|         person.like = Car(name='Fiat') |  | ||||||
|         self.assertRaises(ValidationError, person.validate) |  | ||||||
|  |  | ||||||
|         person.like = Dish(food="arroz", number=15) |  | ||||||
|         person.save() |  | ||||||
|  |  | ||||||
|         person = Person.objects.first() |  | ||||||
|         self.assertIsInstance(person.like, Dish) |  | ||||||
|  |  | ||||||
|     def test_generic_list_embedded_document_choices(self): |  | ||||||
|         """Ensure you can limit GenericEmbeddedDocument choices inside |  | ||||||
|         a list field. |  | ||||||
|         """ |  | ||||||
|         class Car(EmbeddedDocument): |  | ||||||
|             name = StringField() |  | ||||||
|  |  | ||||||
|         class Dish(EmbeddedDocument): |  | ||||||
|             food = StringField(required=True) |  | ||||||
|             number = IntField() |  | ||||||
|  |  | ||||||
|         class Person(Document): |  | ||||||
|             name = StringField() |  | ||||||
|             likes = ListField(GenericEmbeddedDocumentField(choices=(Dish,))) |  | ||||||
|  |  | ||||||
|         Person.drop_collection() |  | ||||||
|  |  | ||||||
|         person = Person(name='Test User') |  | ||||||
|         person.likes = [Car(name='Fiat')] |  | ||||||
|         self.assertRaises(ValidationError, person.validate) |  | ||||||
|  |  | ||||||
|         person.likes = [Dish(food="arroz", number=15)] |  | ||||||
|         person.save() |  | ||||||
|  |  | ||||||
|         person = Person.objects.first() |  | ||||||
|         self.assertIsInstance(person.likes[0], Dish) |  | ||||||
|  |  | ||||||
|     def test_recursive_validation(self): |     def test_recursive_validation(self): | ||||||
|         """Ensure that a validation result to_dict is available.""" |         """Ensure that a validation result to_dict is available.""" | ||||||
|         class Author(EmbeddedDocument): |         class Author(EmbeddedDocument): | ||||||
| @@ -2702,44 +2549,5 @@ class EmbeddedDocumentListFieldTestCase(MongoDBTestCase): | |||||||
|         self.assertEqual(custom_data['a'], CustomData.c_field.custom_data['a']) |         self.assertEqual(custom_data['a'], CustomData.c_field.custom_data['a']) | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestEmbeddedDocumentField(MongoDBTestCase): |  | ||||||
|     def test___init___(self): |  | ||||||
|         class MyDoc(EmbeddedDocument): |  | ||||||
|             name = StringField() |  | ||||||
|  |  | ||||||
|         field = EmbeddedDocumentField(MyDoc) |  | ||||||
|         self.assertEqual(field.document_type_obj, MyDoc) |  | ||||||
|  |  | ||||||
|         field2 = EmbeddedDocumentField('MyDoc') |  | ||||||
|         self.assertEqual(field2.document_type_obj, 'MyDoc') |  | ||||||
|  |  | ||||||
|     def test___init___throw_error_if_document_type_is_not_EmbeddedDocument(self): |  | ||||||
|         with self.assertRaises(ValidationError): |  | ||||||
|             EmbeddedDocumentField(dict) |  | ||||||
|  |  | ||||||
|     def test_document_type_throw_error_if_not_EmbeddedDocument_subclass(self): |  | ||||||
|  |  | ||||||
|         class MyDoc(Document): |  | ||||||
|             name = StringField() |  | ||||||
|  |  | ||||||
|         emb = EmbeddedDocumentField('MyDoc') |  | ||||||
|         with self.assertRaises(ValidationError) as ctx: |  | ||||||
|             emb.document_type |  | ||||||
|         self.assertIn('Invalid embedded document class provided to an EmbeddedDocumentField', str(ctx.exception)) |  | ||||||
|  |  | ||||||
|     def test_embedded_document_field_only_allow_subclasses_of_embedded_document(self): |  | ||||||
|         # Relates to #1661 |  | ||||||
|         class MyDoc(Document): |  | ||||||
|             name = StringField() |  | ||||||
|  |  | ||||||
|         with self.assertRaises(ValidationError): |  | ||||||
|             class MyFailingDoc(Document): |  | ||||||
|                 emb = EmbeddedDocumentField(MyDoc) |  | ||||||
|  |  | ||||||
|         with self.assertRaises(ValidationError): |  | ||||||
|             class MyFailingdoc2(Document): |  | ||||||
|                 emb = EmbeddedDocumentField('MyDoc') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|     unittest.main() |     unittest.main() | ||||||
|   | |||||||
							
								
								
									
										316
									
								
								tests/fields/test_embedded_document_field.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										316
									
								
								tests/fields/test_embedded_document_field.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,316 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | from mongoengine import Document, StringField, ValidationError, EmbeddedDocument, EmbeddedDocumentField, \ | ||||||
|  |     InvalidQueryError, LookUpError, IntField, GenericEmbeddedDocumentField, ListField | ||||||
|  |  | ||||||
|  | from tests.utils import MongoDBTestCase | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestEmbeddedDocumentField(MongoDBTestCase): | ||||||
|  |     def test___init___(self): | ||||||
|  |         class MyDoc(EmbeddedDocument): | ||||||
|  |             name = StringField() | ||||||
|  |  | ||||||
|  |         field = EmbeddedDocumentField(MyDoc) | ||||||
|  |         self.assertEqual(field.document_type_obj, MyDoc) | ||||||
|  |  | ||||||
|  |         field2 = EmbeddedDocumentField('MyDoc') | ||||||
|  |         self.assertEqual(field2.document_type_obj, 'MyDoc') | ||||||
|  |  | ||||||
|  |     def test___init___throw_error_if_document_type_is_not_EmbeddedDocument(self): | ||||||
|  |         with self.assertRaises(ValidationError): | ||||||
|  |             EmbeddedDocumentField(dict) | ||||||
|  |  | ||||||
|  |     def test_document_type_throw_error_if_not_EmbeddedDocument_subclass(self): | ||||||
|  |  | ||||||
|  |         class MyDoc(Document): | ||||||
|  |             name = StringField() | ||||||
|  |  | ||||||
|  |         emb = EmbeddedDocumentField('MyDoc') | ||||||
|  |         with self.assertRaises(ValidationError) as ctx: | ||||||
|  |             emb.document_type | ||||||
|  |         self.assertIn('Invalid embedded document class provided to an EmbeddedDocumentField', str(ctx.exception)) | ||||||
|  |  | ||||||
|  |     def test_embedded_document_field_only_allow_subclasses_of_embedded_document(self): | ||||||
|  |         # Relates to #1661 | ||||||
|  |         class MyDoc(Document): | ||||||
|  |             name = StringField() | ||||||
|  |  | ||||||
|  |         with self.assertRaises(ValidationError): | ||||||
|  |             class MyFailingDoc(Document): | ||||||
|  |                 emb = EmbeddedDocumentField(MyDoc) | ||||||
|  |  | ||||||
|  |         with self.assertRaises(ValidationError): | ||||||
|  |             class MyFailingdoc2(Document): | ||||||
|  |                 emb = EmbeddedDocumentField('MyDoc') | ||||||
|  |  | ||||||
|  |     def test_query_embedded_document_attribute(self): | ||||||
|  |         class AdminSettings(EmbeddedDocument): | ||||||
|  |             foo1 = StringField() | ||||||
|  |             foo2 = StringField() | ||||||
|  |  | ||||||
|  |         class Person(Document): | ||||||
|  |             settings = EmbeddedDocumentField(AdminSettings) | ||||||
|  |             name = StringField() | ||||||
|  |  | ||||||
|  |         Person.drop_collection() | ||||||
|  |  | ||||||
|  |         p = Person( | ||||||
|  |             settings=AdminSettings(foo1='bar1', foo2='bar2'), | ||||||
|  |             name='John', | ||||||
|  |         ).save() | ||||||
|  |  | ||||||
|  |         # Test non exiting attribute | ||||||
|  |         with self.assertRaises(InvalidQueryError) as ctx_err: | ||||||
|  |             Person.objects(settings__notexist='bar').first() | ||||||
|  |         self.assertEqual(unicode(ctx_err.exception), u'Cannot resolve field "notexist"') | ||||||
|  |  | ||||||
|  |         with self.assertRaises(LookUpError): | ||||||
|  |             Person.objects.only('settings.notexist') | ||||||
|  |  | ||||||
|  |         # Test existing attribute | ||||||
|  |         self.assertEqual(Person.objects(settings__foo1='bar1').first().id, p.id) | ||||||
|  |         only_p = Person.objects.only('settings.foo1').first() | ||||||
|  |         self.assertEqual(only_p.settings.foo1, p.settings.foo1) | ||||||
|  |         self.assertIsNone(only_p.settings.foo2) | ||||||
|  |         self.assertIsNone(only_p.name) | ||||||
|  |  | ||||||
|  |         exclude_p = Person.objects.exclude('settings.foo1').first() | ||||||
|  |         self.assertIsNone(exclude_p.settings.foo1) | ||||||
|  |         self.assertEqual(exclude_p.settings.foo2, p.settings.foo2) | ||||||
|  |         self.assertEqual(exclude_p.name, p.name) | ||||||
|  |  | ||||||
|  |     def test_query_embedded_document_attribute_with_inheritance(self): | ||||||
|  |         class BaseSettings(EmbeddedDocument): | ||||||
|  |             meta = {'allow_inheritance': True} | ||||||
|  |             base_foo = StringField() | ||||||
|  |  | ||||||
|  |         class AdminSettings(BaseSettings): | ||||||
|  |             sub_foo = StringField() | ||||||
|  |  | ||||||
|  |         class Person(Document): | ||||||
|  |             settings = EmbeddedDocumentField(BaseSettings) | ||||||
|  |  | ||||||
|  |         Person.drop_collection() | ||||||
|  |  | ||||||
|  |         p = Person(settings=AdminSettings(base_foo='basefoo', sub_foo='subfoo')) | ||||||
|  |         p.save() | ||||||
|  |  | ||||||
|  |         # Test non exiting attribute | ||||||
|  |         with self.assertRaises(InvalidQueryError) as ctx_err: | ||||||
|  |             self.assertEqual(Person.objects(settings__notexist='bar').first().id, p.id) | ||||||
|  |         self.assertEqual(unicode(ctx_err.exception), u'Cannot resolve field "notexist"') | ||||||
|  |  | ||||||
|  |         # Test existing attribute | ||||||
|  |         self.assertEqual(Person.objects(settings__base_foo='basefoo').first().id, p.id) | ||||||
|  |         self.assertEqual(Person.objects(settings__sub_foo='subfoo').first().id, p.id) | ||||||
|  |  | ||||||
|  |         only_p = Person.objects.only('settings.base_foo', 'settings._cls').first() | ||||||
|  |         self.assertEqual(only_p.settings.base_foo, 'basefoo') | ||||||
|  |         self.assertIsNone(only_p.settings.sub_foo) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestGenericEmbeddedDocumentField(MongoDBTestCase): | ||||||
|  |  | ||||||
|  |     def test_generic_embedded_document(self): | ||||||
|  |         class Car(EmbeddedDocument): | ||||||
|  |             name = StringField() | ||||||
|  |  | ||||||
|  |         class Dish(EmbeddedDocument): | ||||||
|  |             food = StringField(required=True) | ||||||
|  |             number = IntField() | ||||||
|  |  | ||||||
|  |         class Person(Document): | ||||||
|  |             name = StringField() | ||||||
|  |             like = GenericEmbeddedDocumentField() | ||||||
|  |  | ||||||
|  |         Person.drop_collection() | ||||||
|  |  | ||||||
|  |         person = Person(name='Test User') | ||||||
|  |         person.like = Car(name='Fiat') | ||||||
|  |         person.save() | ||||||
|  |  | ||||||
|  |         person = Person.objects.first() | ||||||
|  |         self.assertIsInstance(person.like, Car) | ||||||
|  |  | ||||||
|  |         person.like = Dish(food="arroz", number=15) | ||||||
|  |         person.save() | ||||||
|  |  | ||||||
|  |         person = Person.objects.first() | ||||||
|  |         self.assertIsInstance(person.like, Dish) | ||||||
|  |  | ||||||
|  |     def test_generic_embedded_document_choices(self): | ||||||
|  |         """Ensure you can limit GenericEmbeddedDocument choices.""" | ||||||
|  |         class Car(EmbeddedDocument): | ||||||
|  |             name = StringField() | ||||||
|  |  | ||||||
|  |         class Dish(EmbeddedDocument): | ||||||
|  |             food = StringField(required=True) | ||||||
|  |             number = IntField() | ||||||
|  |  | ||||||
|  |         class Person(Document): | ||||||
|  |             name = StringField() | ||||||
|  |             like = GenericEmbeddedDocumentField(choices=(Dish,)) | ||||||
|  |  | ||||||
|  |         Person.drop_collection() | ||||||
|  |  | ||||||
|  |         person = Person(name='Test User') | ||||||
|  |         person.like = Car(name='Fiat') | ||||||
|  |         self.assertRaises(ValidationError, person.validate) | ||||||
|  |  | ||||||
|  |         person.like = Dish(food="arroz", number=15) | ||||||
|  |         person.save() | ||||||
|  |  | ||||||
|  |         person = Person.objects.first() | ||||||
|  |         self.assertIsInstance(person.like, Dish) | ||||||
|  |  | ||||||
|  |     def test_generic_list_embedded_document_choices(self): | ||||||
|  |         """Ensure you can limit GenericEmbeddedDocument choices inside | ||||||
|  |         a list field. | ||||||
|  |         """ | ||||||
|  |         class Car(EmbeddedDocument): | ||||||
|  |             name = StringField() | ||||||
|  |  | ||||||
|  |         class Dish(EmbeddedDocument): | ||||||
|  |             food = StringField(required=True) | ||||||
|  |             number = IntField() | ||||||
|  |  | ||||||
|  |         class Person(Document): | ||||||
|  |             name = StringField() | ||||||
|  |             likes = ListField(GenericEmbeddedDocumentField(choices=(Dish,))) | ||||||
|  |  | ||||||
|  |         Person.drop_collection() | ||||||
|  |  | ||||||
|  |         person = Person(name='Test User') | ||||||
|  |         person.likes = [Car(name='Fiat')] | ||||||
|  |         self.assertRaises(ValidationError, person.validate) | ||||||
|  |  | ||||||
|  |         person.likes = [Dish(food="arroz", number=15)] | ||||||
|  |         person.save() | ||||||
|  |  | ||||||
|  |         person = Person.objects.first() | ||||||
|  |         self.assertIsInstance(person.likes[0], Dish) | ||||||
|  |  | ||||||
|  |     def test_choices_validation_documents(self): | ||||||
|  |         """ | ||||||
|  |         Ensure fields with document choices validate given a valid choice. | ||||||
|  |         """ | ||||||
|  |         class UserComments(EmbeddedDocument): | ||||||
|  |             author = StringField() | ||||||
|  |             message = StringField() | ||||||
|  |  | ||||||
|  |         class BlogPost(Document): | ||||||
|  |             comments = ListField( | ||||||
|  |                 GenericEmbeddedDocumentField(choices=(UserComments,)) | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         # Ensure Validation Passes | ||||||
|  |         BlogPost(comments=[ | ||||||
|  |             UserComments(author='user2', message='message2'), | ||||||
|  |         ]).save() | ||||||
|  |  | ||||||
|  |     def test_choices_validation_documents_invalid(self): | ||||||
|  |         """ | ||||||
|  |         Ensure fields with document choices validate given an invalid choice. | ||||||
|  |         This should throw a ValidationError exception. | ||||||
|  |         """ | ||||||
|  |         class UserComments(EmbeddedDocument): | ||||||
|  |             author = StringField() | ||||||
|  |             message = StringField() | ||||||
|  |  | ||||||
|  |         class ModeratorComments(EmbeddedDocument): | ||||||
|  |             author = StringField() | ||||||
|  |             message = StringField() | ||||||
|  |  | ||||||
|  |         class BlogPost(Document): | ||||||
|  |             comments = ListField( | ||||||
|  |                 GenericEmbeddedDocumentField(choices=(UserComments,)) | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         # Single Entry Failure | ||||||
|  |         post = BlogPost(comments=[ | ||||||
|  |             ModeratorComments(author='mod1', message='message1'), | ||||||
|  |         ]) | ||||||
|  |         self.assertRaises(ValidationError, post.save) | ||||||
|  |  | ||||||
|  |         # Mixed Entry Failure | ||||||
|  |         post = BlogPost(comments=[ | ||||||
|  |             ModeratorComments(author='mod1', message='message1'), | ||||||
|  |             UserComments(author='user2', message='message2'), | ||||||
|  |         ]) | ||||||
|  |         self.assertRaises(ValidationError, post.save) | ||||||
|  |  | ||||||
|  |     def test_choices_validation_documents_inheritance(self): | ||||||
|  |         """ | ||||||
|  |         Ensure fields with document choices validate given subclass of choice. | ||||||
|  |         """ | ||||||
|  |         class Comments(EmbeddedDocument): | ||||||
|  |             meta = { | ||||||
|  |                 'abstract': True | ||||||
|  |             } | ||||||
|  |             author = StringField() | ||||||
|  |             message = StringField() | ||||||
|  |  | ||||||
|  |         class UserComments(Comments): | ||||||
|  |             pass | ||||||
|  |  | ||||||
|  |         class BlogPost(Document): | ||||||
|  |             comments = ListField( | ||||||
|  |                 GenericEmbeddedDocumentField(choices=(Comments,)) | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         # Save Valid EmbeddedDocument Type | ||||||
|  |         BlogPost(comments=[ | ||||||
|  |             UserComments(author='user2', message='message2'), | ||||||
|  |         ]).save() | ||||||
|  |  | ||||||
|  |     def test_query_generic_embedded_document_attribute(self): | ||||||
|  |         class AdminSettings(EmbeddedDocument): | ||||||
|  |             foo1 = StringField() | ||||||
|  |  | ||||||
|  |         class NonAdminSettings(EmbeddedDocument): | ||||||
|  |             foo2 = StringField() | ||||||
|  |  | ||||||
|  |         class Person(Document): | ||||||
|  |             settings = GenericEmbeddedDocumentField(choices=(AdminSettings, NonAdminSettings)) | ||||||
|  |  | ||||||
|  |         Person.drop_collection() | ||||||
|  |  | ||||||
|  |         p1 = Person(settings=AdminSettings(foo1='bar1')).save() | ||||||
|  |         p2 = Person(settings=NonAdminSettings(foo2='bar2')).save() | ||||||
|  |  | ||||||
|  |         # Test non exiting attribute | ||||||
|  |         with self.assertRaises(InvalidQueryError) as ctx_err: | ||||||
|  |             Person.objects(settings__notexist='bar').first() | ||||||
|  |         self.assertEqual(unicode(ctx_err.exception), u'Cannot resolve field "notexist"') | ||||||
|  |  | ||||||
|  |         with self.assertRaises(LookUpError): | ||||||
|  |             Person.objects.only('settings.notexist') | ||||||
|  |  | ||||||
|  |         # Test existing attribute | ||||||
|  |         self.assertEqual(Person.objects(settings__foo1='bar1').first().id, p1.id) | ||||||
|  |         self.assertEqual(Person.objects(settings__foo2='bar2').first().id, p2.id) | ||||||
|  |  | ||||||
|  |     def test_query_generic_embedded_document_attribute_with_inheritance(self): | ||||||
|  |         class BaseSettings(EmbeddedDocument): | ||||||
|  |             meta = {'allow_inheritance': True} | ||||||
|  |             base_foo = StringField() | ||||||
|  |  | ||||||
|  |         class AdminSettings(BaseSettings): | ||||||
|  |             sub_foo = StringField() | ||||||
|  |  | ||||||
|  |         class Person(Document): | ||||||
|  |             settings = GenericEmbeddedDocumentField(choices=[BaseSettings]) | ||||||
|  |  | ||||||
|  |         Person.drop_collection() | ||||||
|  |  | ||||||
|  |         p = Person(settings=AdminSettings(base_foo='basefoo', sub_foo='subfoo')) | ||||||
|  |         p.save() | ||||||
|  |  | ||||||
|  |         # Test non exiting attribute | ||||||
|  |         with self.assertRaises(InvalidQueryError) as ctx_err: | ||||||
|  |             self.assertEqual(Person.objects(settings__notexist='bar').first().id, p.id) | ||||||
|  |         self.assertEqual(unicode(ctx_err.exception), u'Cannot resolve field "notexist"') | ||||||
|  |  | ||||||
|  |         # Test existing attribute | ||||||
|  |         self.assertEqual(Person.objects(settings__base_foo='basefoo').first().id, p.id) | ||||||
|  |         self.assertEqual(Person.objects(settings__sub_foo='subfoo').first().id, p.id) | ||||||
		Reference in New Issue
	
	Block a user