From 5fcb5aba7c99e7752bd32cd45ab46ace820993a4 Mon Sep 17 00:00:00 2001 From: Harry Marr Date: Thu, 19 Nov 2009 16:58:25 +0000 Subject: [PATCH] Added ListField type with unit tests --- mongomap/base.py | 2 +- mongomap/fields.py | 49 +++++++++++++++++++++++++++++++++++++++++++++- tests/document.py | 23 ++++++++++++++++++++++ tests/fields.py | 25 +++++++++++++++++++++++ 4 files changed, 97 insertions(+), 2 deletions(-) diff --git a/mongomap/base.py b/mongomap/base.py index 5401ea2f..be4fd324 100644 --- a/mongomap/base.py +++ b/mongomap/base.py @@ -44,7 +44,7 @@ class BaseField(object): try: value = self._to_python(value) self._validate(value) - except (ValueError, AttributeError): + except (ValueError, AttributeError, AssertionError): raise ValidationError('Invalid value for field of type "' + self.__class__.__name__ + '"') elif self.required: diff --git a/mongomap/fields.py b/mongomap/fields.py index dd4cbebe..52a91d13 100644 --- a/mongomap/fields.py +++ b/mongomap/fields.py @@ -5,7 +5,7 @@ import re import pymongo -__all__ = ['StringField', 'IntField', 'EmbeddedDocumentField', +__all__ = ['StringField', 'IntField', 'EmbeddedDocumentField', 'ListField', 'ObjectIdField', 'ValidationError'] @@ -17,8 +17,14 @@ class StringField(BaseField): self.regex = re.compile(regex) if regex else None self.max_length = max_length super(StringField, self).__init__(**kwargs) + + def _to_python(self, value): + assert(isinstance(value, (str, unicode))) + return unicode(value) def _validate(self, value): + assert(isinstance(value, (str, unicode))) + if self.max_length is not None and len(value) > self.max_length: raise ValidationError('String value is too long') @@ -36,9 +42,12 @@ class IntField(BaseField): super(IntField, self).__init__(**kwargs) def _to_python(self, value): + assert(isinstance(value, int)) return int(value) def _validate(self, value): + assert(isinstance(value, int)) + if self.min_value is not None and value < self.min_value: raise ValidationError('Integer value is too small') @@ -74,3 +83,41 @@ class EmbeddedDocumentField(BaseField): if not isinstance(value, self.document): raise ValidationError('Invalid embedded document instance ' 'provided to an EmbeddedDocumentField') + + +class ListField(BaseField): + """A list field that wraps a standard field, allowing multiple instances + of the field to be used as a list in the database. + """ + + def __init__(self, field, **kwargs): + if not isinstance(field, BaseField): + raise ValidationError('Argument to ListField constructor must be ' + 'a valid field') + self.field = field + super(ListField, self).__init__(**kwargs) + + def _to_python(self, value): + assert(isinstance(value, (list, tuple))) + return list(value) + + def _to_mongo(self, value): + return [self.field._to_mongo(item) for item in value] + + def _validate(self, value): + """Make sure that a list of valid fields is being used. + """ +# print +# print value +# print type(value) +# print isinstance(value, list) +# print + if not isinstance(value, (list, tuple)): + raise ValidationError('Only lists and tuples may be used in a ' + 'list field') + + try: + [self.field._validate(item) for item in value] + except: + raise ValidationError('All items in a list field must be of the ' + 'specified type') diff --git a/tests/document.py b/tests/document.py index 177b5585..738fef15 100644 --- a/tests/document.py +++ b/tests/document.py @@ -111,6 +111,29 @@ class DocumentTest(unittest.TestCase): person_obj = collection.find_one({'name': 'Test User'}) self.assertEqual(str(person_obj['_id']), '497ce96f395f2f052a494fd4') + def test_save_list(self): + """Ensure that a list field may be properly saved. + """ + class Comment(EmbeddedDocument): + content = StringField() + + class BlogPost(Document): + content = StringField() + comments = ListField(EmbeddedDocumentField(Comment)) + tags = ListField(StringField()) + + post = BlogPost(content='Went for a walk today...') + post.tags = tags = ['fun', 'leisure'] + comments = [Comment(content='Good for you'), Comment(content='Yay.')] + post.comments = comments + post.save() + + collection = self.db[BlogPost._meta['collection']] + post_obj = collection.find_one() + self.assertEqual(post_obj['tags'], tags) + for comment_obj, comment in zip(post_obj['comments'], comments): + self.assertEqual(comment_obj['content'], comment['content']) + def test_save_embedded_document(self): """Ensure that a document with an embedded document field may be saved in the database. diff --git a/tests/fields.py b/tests/fields.py index e66974c8..68113ca2 100644 --- a/tests/fields.py +++ b/tests/fields.py @@ -56,6 +56,8 @@ class FieldTest(unittest.TestCase): userid = StringField(r'[0-9a-z_]+$') person = Person() + self.assertRaises(ValidationError, person.__setattr__, 'name', 34) + # Test regex validation on userid self.assertRaises(ValidationError, person.__setattr__, 'userid', 'test.User') @@ -80,6 +82,29 @@ class FieldTest(unittest.TestCase): self.assertRaises(ValidationError, person.__setattr__, 'age', 120) self.assertRaises(ValidationError, person.__setattr__, 'age', 'ten') + def test_list_validation(self): + """Ensure that a list field only accepts lists with valid elements. + """ + class Comment(EmbeddedDocument): + content = StringField() + + class BlogPost(Document): + content = StringField() + comments = ListField(EmbeddedDocumentField(Comment)) + tags = ListField(StringField()) + + post = BlogPost(content='Went for a walk today...') + self.assertRaises(ValidationError, post.__setattr__, 'tags', 'fun') + self.assertRaises(ValidationError, post.__setattr__, 'tags', [1, 2]) + post.tags = ['fun', 'leisure'] + post.tags = ('fun', 'leisure') + + comments = [Comment(content='Good for you'), Comment(content='Yay.')] + self.assertRaises(ValidationError, post.__setattr__, 'comments', ['a']) + self.assertRaises(ValidationError, post.__setattr__, 'comments', 'Yay') + self.assertRaises(ValidationError, post.__setattr__, 'comments', 'Yay') + post.comments = comments + def test_embedded_document_validation(self): """Ensure that invalid embedded documents cannot be assigned to embedded document fields.