From b99421e7eec9378efdab1c6e6c12a18a59825a8e Mon Sep 17 00:00:00 2001 From: Deepak Thukral Date: Fri, 5 Mar 2010 07:55:12 +0100 Subject: [PATCH 1/9] added date_joined and normalization to email address --- mongoengine/django/auth.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/mongoengine/django/auth.py b/mongoengine/django/auth.py index 3c76baf2..d4b0ff0b 100644 --- a/mongoengine/django/auth.py +++ b/mongoengine/django/auth.py @@ -30,6 +30,7 @@ class User(Document): is_active = BooleanField(default=True) is_superuser = BooleanField(default=False) last_login = DateTimeField(default=datetime.datetime.now) + date_joined = DateTimeField(default=datetime.datetime.now) def get_full_name(self): """Returns the users first and last names, separated by a space. @@ -70,7 +71,20 @@ class User(Document): """Create (and save) a new user with the given username, password and email address. """ - user = User(username=username, email=email) + now = datetime.datetime.now() + + # Normalize the address by lowercasing the domain part of the email + # address. + # Not sure why we'r allowing null email when its not allowed in django + if email is not None: + try: + email_name, domain_part = email.strip().split('@', 1) + except ValueError: + pass + else: + email = '@'.join([email_name, domain_part.lower()]) + + user = User(username=username, email=email, date_joined=now) user.set_password(password) user.save() return user From 879bf08d18e0e0cedebd0ba950f2ec191a7c2c81 Mon Sep 17 00:00:00 2001 From: Rached Ben Mustapha Date: Mon, 8 Mar 2010 16:42:23 +0100 Subject: [PATCH 2/9] Simple implementation of BinaryField --- mongoengine/fields.py | 16 +++++++++++++++- tests/fields.py | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 0793f44b..29c86660 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -11,7 +11,8 @@ import decimal __all__ = ['StringField', 'IntField', 'FloatField', 'BooleanField', 'DateTimeField', 'EmbeddedDocumentField', 'ListField', 'DictField', 'ObjectIdField', 'ReferenceField', 'ValidationError', - 'DecimalField', 'URLField', 'GenericReferenceField'] + 'DecimalField', 'URLField', 'GenericReferenceField', + 'BinaryField'] RECURSIVE_REFERENCE_CONSTANT = 'self' @@ -442,3 +443,16 @@ class GenericReferenceField(BaseField): def prepare_query_value(self, op, value): return self.to_mongo(value)['_ref'] + +class BinaryField(BaseField): + """A binary data field. + """ + + def __init__(self, **kwargs): + super(BinaryField, self).__init__(**kwargs) + + def to_mongo(self, value): + return pymongo.binary.Binary(value) + + def to_python(self, value): + return str(value) diff --git a/tests/fields.py b/tests/fields.py index 94f65186..80994b13 100644 --- a/tests/fields.py +++ b/tests/fields.py @@ -495,6 +495,24 @@ class FieldTest(unittest.TestCase): Post.drop_collection() User.drop_collection() + def test_binary_fields(self): + """Ensure that binary fields can be stored and retrieved. + """ + class Attachment(Document): + content_type = StringField() + blob = BinaryField() + + BLOB = '\xe6\x00\xc4\xff\x07' + MIME_TYPE = 'application/octet-stream' + + Attachment.drop_collection() + + attachment = Attachment(content_type=MIME_TYPE, blob=BLOB) + attachment.save() + + attachment_1 = Attachment.objects().first() + self.assertEqual(MIME_TYPE, attachment_1.content_type) + self.assertEqual(BLOB, attachment_1.blob) if __name__ == '__main__': unittest.main() From bb19ba3eb6158d66dbbc32e29b50b4f924f9c6b3 Mon Sep 17 00:00:00 2001 From: Rached Ben Mustapha Date: Mon, 8 Mar 2010 16:43:43 +0100 Subject: [PATCH 3/9] Drop collection at the end of the test --- tests/fields.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/fields.py b/tests/fields.py index 80994b13..fc2edb2c 100644 --- a/tests/fields.py +++ b/tests/fields.py @@ -514,5 +514,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(MIME_TYPE, attachment_1.content_type) self.assertEqual(BLOB, attachment_1.blob) + Attachment.drop_collection() + if __name__ == '__main__': unittest.main() From 0b3af2052f51dc7e64d1d8d8e1ab33727ec5d32a Mon Sep 17 00:00:00 2001 From: Rached Ben Mustapha Date: Mon, 8 Mar 2010 17:06:52 +0100 Subject: [PATCH 4/9] implement binary field size validation --- mongoengine/fields.py | 9 ++++++++- tests/fields.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 29c86660..fcae63ee 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -448,7 +448,8 @@ class BinaryField(BaseField): """A binary data field. """ - def __init__(self, **kwargs): + def __init__(self, max_bytes=None, **kwargs): + self.max_bytes = max_bytes super(BinaryField, self).__init__(**kwargs) def to_mongo(self, value): @@ -456,3 +457,9 @@ class BinaryField(BaseField): def to_python(self, value): return str(value) + + def validate(self, value): + assert isinstance(value, str) + + if self.max_bytes is not None and len(value) > self.max_bytes: + raise ValidationError('Binary value is too long') diff --git a/tests/fields.py b/tests/fields.py index fc2edb2c..986f0e26 100644 --- a/tests/fields.py +++ b/tests/fields.py @@ -516,5 +516,40 @@ class FieldTest(unittest.TestCase): Attachment.drop_collection() + def test_binary_validation(self): + """Ensure that invalid values cannot be assigned to binary fields. + """ + class Attachment(Document): + blob = BinaryField() + + class AttachmentRequired(Document): + blob = BinaryField(required=True) + + class AttachmentSizeLimit(Document): + blob = BinaryField(max_bytes=4) + + Attachment.drop_collection() + AttachmentRequired.drop_collection() + AttachmentSizeLimit.drop_collection() + + attachment = Attachment() + attachment.validate() + attachment.blob = 2 + self.assertRaises(ValidationError, attachment.validate) + + attachment_required = AttachmentRequired() + self.assertRaises(ValidationError, attachment_required.validate) + attachment_required.blob = '\xe6\x00\xc4\xff\x07' + attachment_required.validate() + + attachment_size_limit = AttachmentSizeLimit(blob='\xe6\x00\xc4\xff\x07') + self.assertRaises(ValidationError, attachment_size_limit.validate) + attachment_size_limit.blob = '\xe6\x00\xc4\xff' + attachment_size_limit.validate() + + Attachment.drop_collection() + AttachmentRequired.drop_collection() + AttachmentSizeLimit.drop_collection() + if __name__ == '__main__': unittest.main() From 5390117275be344919b62cefa808cb1db32f8a1c Mon Sep 17 00:00:00 2001 From: Harry Marr Date: Mon, 8 Mar 2010 21:58:19 +0000 Subject: [PATCH 5/9] Modified AUTHORS --- AUTHORS | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index af23070b..93ecfa8d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,3 +1,4 @@ -Harry Marr +Harry Marr Matt Dennewitz -Deepak Thukral \ No newline at end of file +Deepak Thukral +Florian Schlachter From ac3c857e1a8a07bbc24730c23f44a1a57d11535c Mon Sep 17 00:00:00 2001 From: Harry Marr Date: Mon, 8 Mar 2010 22:15:40 +0000 Subject: [PATCH 6/9] Added rewind to QuerySet, which is implicitly called when iteration finishes --- mongoengine/queryset.py | 15 ++++++++++++--- tests/queryset.py | 12 ++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index 8c13dc93..71e18932 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -416,9 +416,18 @@ class QuerySet(object): def next(self): """Wrap the result in a :class:`~mongoengine.Document` object. """ - if self._limit == 0: - raise StopIteration - return self._document._from_son(self._cursor.next()) + try: + if self._limit == 0: + raise StopIteration + return self._document._from_son(self._cursor.next()) + except StopIteration, e: + self.rewind() + raise e + + def rewind(self): + """Rewind the cursor to its unevaluated state. + """ + self._cursor.rewind() def count(self): """Count the selected elements in the query. diff --git a/tests/queryset.py b/tests/queryset.py index b55c1876..e9d8e754 100644 --- a/tests/queryset.py +++ b/tests/queryset.py @@ -186,6 +186,18 @@ class QuerySetTest(unittest.TestCase): person = self.Person.objects.get(age=50) self.assertEqual(person.name, "User C") + def test_repeated_iteration(self): + """Ensure that QuerySet rewinds itself one iteration finishes. + """ + self.Person(name='Person 1').save() + self.Person(name='Person 2').save() + + queryset = self.Person.objects + people1 = [person for person in queryset] + people2 = [person for person in queryset] + + self.assertEqual(people1, people2) + def test_regex_query_shortcuts(self): """Ensure that contains, startswith, endswith, etc work. """ From d7086fc4a3eea94fbd5b6360ddbeb8b32d8e280d Mon Sep 17 00:00:00 2001 From: Harry Marr Date: Mon, 8 Mar 2010 22:23:40 +0000 Subject: [PATCH 7/9] Added rewind behaviour and BinaryField to docs --- docs/apireference.rst | 2 ++ docs/guide/querying.rst | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/docs/apireference.rst b/docs/apireference.rst index ec78efa0..2433ddfe 100644 --- a/docs/apireference.rst +++ b/docs/apireference.rst @@ -54,6 +54,8 @@ Fields .. autoclass:: mongoengine.ListField +.. autoclass:: mongoengine.BinaryField + .. autoclass:: mongoengine.ObjectIdField .. autoclass:: mongoengine.ReferenceField diff --git a/docs/guide/querying.rst b/docs/guide/querying.rst index 6b14b12e..ddab385e 100644 --- a/docs/guide/querying.rst +++ b/docs/guide/querying.rst @@ -13,6 +13,13 @@ fetch documents from the database:: for user in User.objects: print user.name +.. note:: + Once the iteration finishes (when :class:`StopIteration` is raised), + :meth:`~mongoengine.queryset.QuerySet.rewind` will be called so that the + :class:`~mongoengine.queryset.QuerySet` may be iterated over again. The + results of the first iteration are *not* cached, so the database will be hit + each time the :class:`~mongoengine.queryset.QuerySet` is iterated over. + Filtering queries ================= The query may be filtered by calling the From df8f4e7251c9b70eef642141058f71cca142ddb8 Mon Sep 17 00:00:00 2001 From: Deepak Thukral Date: Thu, 11 Mar 2010 20:42:27 +0100 Subject: [PATCH 8/9] no message in expection in future version of python --- mongoengine/base.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mongoengine/base.py b/mongoengine/base.py index 1921cba7..05f243b8 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -10,7 +10,12 @@ def get_document(name): class ValidationError(Exception): - pass + def _get_message(self, message): + return self._message + def _set_message(self, message): + self._message = message + message = property(_get_message, _set_message) + class BaseField(object): From 72161a9b71d40bb1b806c9c1e4f28cf7d18122ca Mon Sep 17 00:00:00 2001 From: Deepak Thukral Date: Thu, 11 Mar 2010 21:10:04 +0100 Subject: [PATCH 9/9] no message in expection in future version of python --- mongoengine/base.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/mongoengine/base.py b/mongoengine/base.py index 05f243b8..e0127537 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -10,12 +10,7 @@ def get_document(name): class ValidationError(Exception): - def _get_message(self, message): - return self._message - def _set_message(self, message): - self._message = message - message = property(_get_message, _set_message) - + pass class BaseField(object): @@ -91,7 +86,8 @@ class ObjectIdField(BaseField): try: return pymongo.objectid.ObjectId(str(value)) except Exception, e: - raise ValidationError(e.message) + #e.message attribute has been deprecated since Python 2.6 + raise ValidationError(str(e)) return value def prepare_query_value(self, op, value):