Merge branch 'master' into dev

This commit is contained in:
Ross Lawley 2012-08-02 08:33:17 +01:00
commit b87ef982f6
7 changed files with 193 additions and 97 deletions

View File

@ -113,4 +113,5 @@ that much better:
* Alexander Koshelev * Alexander Koshelev
* Jaime Irurzun * Jaime Irurzun
* Alexandre González * Alexandre González
* Thomas Steinacher * Thomas Steinacher
* Tommi Komulainen

View File

@ -1,5 +1,5 @@
Copyright (c) 2009-2010 Harry Marr Copyright (c) 2009-2012 See AUTHORS
Permission is hereby granted, free of charge, to any person Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without files (the "Software"), to deal in the Software without
@ -8,10 +8,10 @@ copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following Software is furnished to do so, subject to the following
conditions: conditions:
The above copyright notice and this permission notice shall be The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software. included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND

View File

@ -2,6 +2,15 @@
Changelog Changelog
========= =========
Changes in 0.6.X
================
- Added Binary support to UUID (MongoEngine/mongoengine#47)
- Fixed MapField lookup for fields without declared lookups (MongoEngine/mongoengine#46)
- Fixed BinaryField python value issue (MongoEngine/mongoengine#48)
- Fixed SequenceField non numeric value lookup (MongoEngine/mongoengine#41)
- Fixed queryset manager issue (MongoEngine/mongoengine#52)
- Fixed FileField comparision (hmarr/mongoengine#547)
Changes in 0.6.18 Changes in 0.6.18
================= =================

View File

@ -4,9 +4,9 @@ import decimal
import gridfs import gridfs
import re import re
import uuid import uuid
import warnings
from bson import Binary, DBRef, SON, ObjectId from bson import Binary, DBRef, SON, ObjectId
from base import (BaseField, ComplexBaseField, ObjectIdField, from base import (BaseField, ComplexBaseField, ObjectIdField,
ValidationError, get_document, BaseDocument) ValidationError, get_document, BaseDocument)
from queryset import DO_NOTHING, QuerySet from queryset import DO_NOTHING, QuerySet
@ -845,12 +845,9 @@ class BinaryField(BaseField):
def to_mongo(self, value): def to_mongo(self, value):
return Binary(value) return Binary(value)
def to_python(self, value):
return "%s" % value
def validate(self, value): def validate(self, value):
if not isinstance(value, basestring): if not isinstance(value, (basestring, Binary)):
self.error('BinaryField only accepts string values') self.error('BinaryField only accepts string or bson Binary values')
if self.max_bytes is not None and len(value) > self.max_bytes: if self.max_bytes is not None and len(value) > self.max_bytes:
self.error('Binary value is too long') self.error('Binary value is too long')
@ -907,6 +904,8 @@ class GridFSProxy(object):
return '<%s: %s>' % (self.__class__.__name__, self.grid_id) return '<%s: %s>' % (self.__class__.__name__, self.grid_id)
def __cmp__(self, other): def __cmp__(self, other):
if not isinstance(other, GridFSProxy):
return -1
return cmp((self.grid_id, self.collection_name, self.db_alias), return cmp((self.grid_id, self.collection_name, self.db_alias),
(other.grid_id, other.collection_name, other.db_alias)) (other.grid_id, other.collection_name, other.db_alias))
@ -1289,7 +1288,7 @@ class SequenceField(IntField):
instance._data[self.name] = value instance._data[self.name] = value
instance._mark_as_changed(self.name) instance._mark_as_changed(self.name)
return value return int(value) if value else None
def __set__(self, instance, value): def __set__(self, instance, value):
@ -1309,17 +1308,40 @@ class UUIDField(BaseField):
.. versionadded:: 0.6 .. versionadded:: 0.6
""" """
_binary = None
def __init__(self, **kwargs): def __init__(self, binary=None, **kwargs):
"""
Store UUID data in the database
:param binary: (optional) boolean store as binary.
.. versionchanged:: 0.6.19
"""
if binary is None:
binary = False
msg = ("UUIDFields will soon default to store as binary, please "
"configure binary=False if you wish to store as a string")
warnings.warn(msg, FutureWarning)
self._binary = binary
super(UUIDField, self).__init__(**kwargs) super(UUIDField, self).__init__(**kwargs)
def to_python(self, value): def to_python(self, value):
if not isinstance(value, basestring): if not self.binary:
value = unicode(value) if not isinstance(value, basestring):
return uuid.UUID(value) value = unicode(value)
return uuid.UUID(value)
return value
def to_mongo(self, value): def to_mongo(self, value):
return unicode(value) if not self._binary:
return unicode(value)
return value
def prepare_query_value(self, op, value):
if value is None:
return None
return self.to_mongo(value)
def validate(self, value): def validate(self, value):
if not isinstance(value, uuid.UUID): if not isinstance(value, uuid.UUID):

View File

@ -641,7 +641,7 @@ class QuerySet(object):
from mongoengine.fields import ReferenceField, GenericReferenceField from mongoengine.fields import ReferenceField, GenericReferenceField
if isinstance(field, (ReferenceField, GenericReferenceField)): if isinstance(field, (ReferenceField, GenericReferenceField)):
raise InvalidQueryError('Cannot perform join in mongoDB: %s' % '__'.join(parts)) raise InvalidQueryError('Cannot perform join in mongoDB: %s' % '__'.join(parts))
if getattr(field, 'field', None): if hasattr(getattr(field, 'field', None), 'lookup_member'):
new_field = field.field.lookup_member(field_name) new_field = field.field.lookup_member(field_name)
else: else:
# Look up subfield on the previous field # Look up subfield on the previous field
@ -1886,10 +1886,10 @@ class QuerySetManager(object):
queryset_class = owner._meta['queryset_class'] or QuerySet queryset_class = owner._meta['queryset_class'] or QuerySet
queryset = queryset_class(owner, owner._get_collection()) queryset = queryset_class(owner, owner._get_collection())
if self.get_queryset: if self.get_queryset:
var_names = self.get_queryset.func_code.co_varnames arg_count = self.get_queryset.func_code.co_argcount
if len(var_names) == 1: if arg_count == 1:
queryset = self.get_queryset(queryset) queryset = self.get_queryset(queryset)
elif len(var_names) == 2: elif arg_count == 2:
queryset = self.get_queryset(owner, queryset) queryset = self.get_queryset(owner, queryset)
else: else:
queryset = partial(self.get_queryset, owner, queryset) queryset = partial(self.get_queryset, owner, queryset)

View File

@ -6,6 +6,7 @@ import StringIO
import tempfile import tempfile
import gridfs import gridfs
from bson import Binary
from decimal import Decimal from decimal import Decimal
from mongoengine import * from mongoengine import *
@ -271,25 +272,54 @@ class FieldTest(unittest.TestCase):
person.admin = 'Yes' person.admin = 'Yes'
self.assertRaises(ValidationError, person.validate) self.assertRaises(ValidationError, person.validate)
def test_uuid_validation(self): def test_uuid_field_string(self):
"""Ensure that invalid values cannot be assigned to UUID fields. """Test UUID fields storing as String
""" """
class Person(Document): class Person(Document):
api_key = UUIDField() api_key = UUIDField(binary=False)
Person.drop_collection()
uu = uuid.uuid4()
Person(api_key=uu).save()
self.assertEqual(1, Person.objects(api_key=uu).count())
person = Person() person = Person()
# any uuid type is valid valid = (uuid.uuid4(), uuid.uuid1())
person.api_key = uuid.uuid4() for api_key in valid:
person.validate() person.api_key = api_key
person.api_key = uuid.uuid1() person.validate()
person.validate()
invalid = ('9d159858-549b-4975-9f98-dd2f987c113g',
'9d159858-549b-4975-9f98-dd2f987c113')
for api_key in invalid:
person.api_key = api_key
self.assertRaises(ValidationError, person.validate)
def test_uuid_field_binary(self):
"""Test UUID fields storing as Binary object
"""
class Person(Document):
api_key = UUIDField(binary=True)
Person.drop_collection()
uu = uuid.uuid4()
Person(api_key=uu).save()
self.assertEqual(1, Person.objects(api_key=uu).count())
person = Person()
valid = (uuid.uuid4(), uuid.uuid1())
for api_key in valid:
person.api_key = api_key
person.validate()
invalid = ('9d159858-549b-4975-9f98-dd2f987c113g',
'9d159858-549b-4975-9f98-dd2f987c113')
for api_key in invalid:
person.api_key = api_key
self.assertRaises(ValidationError, person.validate)
# last g cannot belong to an hex number
person.api_key = '9d159858-549b-4975-9f98-dd2f987c113g'
self.assertRaises(ValidationError, person.validate)
# short strings don't validate
person.api_key = '9d159858-549b-4975-9f98-dd2f987c113'
self.assertRaises(ValidationError, person.validate)
def test_datetime_validation(self): def test_datetime_validation(self):
"""Ensure that invalid values cannot be assigned to datetime fields. """Ensure that invalid values cannot be assigned to datetime fields.
@ -928,6 +958,19 @@ class FieldTest(unittest.TestCase):
doc = self.db.test.find_one() doc = self.db.test.find_one()
self.assertEqual(doc['x']['DICTIONARY_KEY']['i'], 2) self.assertEqual(doc['x']['DICTIONARY_KEY']['i'], 2)
def test_map_field_lookup(self):
"""Ensure MapField lookups succeed on Fields without a lookup method"""
class Log(Document):
name = StringField()
visited = MapField(DateTimeField())
Log.drop_collection()
Log(name="wilson", visited={'friends': datetime.datetime.now()}).save()
self.assertEqual(1, Log.objects(
visited__friends__exists=True).count())
def test_embedded_db_field(self): def test_embedded_db_field(self):
class Embedded(EmbeddedDocument): class Embedded(EmbeddedDocument):
@ -1428,7 +1471,7 @@ class FieldTest(unittest.TestCase):
attachment_1 = Attachment.objects().first() attachment_1 = Attachment.objects().first()
self.assertEqual(MIME_TYPE, attachment_1.content_type) self.assertEqual(MIME_TYPE, attachment_1.content_type)
self.assertEqual(BLOB, attachment_1.blob) self.assertEqual(BLOB, str(attachment_1.blob))
Attachment.drop_collection() Attachment.drop_collection()
@ -1455,7 +1498,7 @@ class FieldTest(unittest.TestCase):
attachment_required = AttachmentRequired() attachment_required = AttachmentRequired()
self.assertRaises(ValidationError, attachment_required.validate) self.assertRaises(ValidationError, attachment_required.validate)
attachment_required.blob = '\xe6\x00\xc4\xff\x07' attachment_required.blob = Binary('\xe6\x00\xc4\xff\x07')
attachment_required.validate() attachment_required.validate()
attachment_size_limit = AttachmentSizeLimit(blob='\xe6\x00\xc4\xff\x07') attachment_size_limit = AttachmentSizeLimit(blob='\xe6\x00\xc4\xff\x07')
@ -1467,6 +1510,18 @@ class FieldTest(unittest.TestCase):
AttachmentRequired.drop_collection() AttachmentRequired.drop_collection()
AttachmentSizeLimit.drop_collection() AttachmentSizeLimit.drop_collection()
def test_binary_field_primary(self):
class Attachment(Document):
id = BinaryField(primary_key=True)
Attachment.drop_collection()
att = Attachment(id=uuid.uuid4().bytes).save()
att.delete()
self.assertEqual(0, Attachment.objects.count())
def test_choices_validation(self): def test_choices_validation(self):
"""Ensure that value is in a container of allowed values. """Ensure that value is in a container of allowed values.
""" """
@ -1567,13 +1622,13 @@ class FieldTest(unittest.TestCase):
"""Ensure that file fields can be written to and their data retrieved """Ensure that file fields can be written to and their data retrieved
""" """
class PutFile(Document): class PutFile(Document):
file = FileField() the_file = FileField()
class StreamFile(Document): class StreamFile(Document):
file = FileField() the_file = FileField()
class SetFile(Document): class SetFile(Document):
file = FileField() the_file = FileField()
text = 'Hello, World!' text = 'Hello, World!'
more_text = 'Foo Bar' more_text = 'Foo Bar'
@ -1584,14 +1639,14 @@ class FieldTest(unittest.TestCase):
SetFile.drop_collection() SetFile.drop_collection()
putfile = PutFile() putfile = PutFile()
putfile.file.put(text, content_type=content_type) putfile.the_file.put(text, content_type=content_type)
putfile.save() putfile.save()
putfile.validate() putfile.validate()
result = PutFile.objects.first() result = PutFile.objects.first()
self.assertTrue(putfile == result) self.assertTrue(putfile == result)
self.assertEquals(result.file.read(), text) self.assertEquals(result.the_file.read(), text)
self.assertEquals(result.file.content_type, content_type) self.assertEquals(result.the_file.content_type, content_type)
result.file.delete() # Remove file from GridFS result.the_file.delete() # Remove file from GridFS
PutFile.objects.delete() PutFile.objects.delete()
# Ensure file-like objects are stored # Ensure file-like objects are stored
@ -1599,53 +1654,53 @@ class FieldTest(unittest.TestCase):
putstring = StringIO.StringIO() putstring = StringIO.StringIO()
putstring.write(text) putstring.write(text)
putstring.seek(0) putstring.seek(0)
putfile.file.put(putstring, content_type=content_type) putfile.the_file.put(putstring, content_type=content_type)
putfile.save() putfile.save()
putfile.validate() putfile.validate()
result = PutFile.objects.first() result = PutFile.objects.first()
self.assertTrue(putfile == result) self.assertTrue(putfile == result)
self.assertEquals(result.file.read(), text) self.assertEquals(result.the_file.read(), text)
self.assertEquals(result.file.content_type, content_type) self.assertEquals(result.the_file.content_type, content_type)
result.file.delete() result.the_file.delete()
streamfile = StreamFile() streamfile = StreamFile()
streamfile.file.new_file(content_type=content_type) streamfile.the_file.new_file(content_type=content_type)
streamfile.file.write(text) streamfile.the_file.write(text)
streamfile.file.write(more_text) streamfile.the_file.write(more_text)
streamfile.file.close() streamfile.the_file.close()
streamfile.save() streamfile.save()
streamfile.validate() streamfile.validate()
result = StreamFile.objects.first() result = StreamFile.objects.first()
self.assertTrue(streamfile == result) self.assertTrue(streamfile == result)
self.assertEquals(result.file.read(), text + more_text) self.assertEquals(result.the_file.read(), text + more_text)
self.assertEquals(result.file.content_type, content_type) self.assertEquals(result.the_file.content_type, content_type)
result.file.seek(0) result.the_file.seek(0)
self.assertEquals(result.file.tell(), 0) self.assertEquals(result.the_file.tell(), 0)
self.assertEquals(result.file.read(len(text)), text) self.assertEquals(result.the_file.read(len(text)), text)
self.assertEquals(result.file.tell(), len(text)) self.assertEquals(result.the_file.tell(), len(text))
self.assertEquals(result.file.read(len(more_text)), more_text) self.assertEquals(result.the_file.read(len(more_text)), more_text)
self.assertEquals(result.file.tell(), len(text + more_text)) self.assertEquals(result.the_file.tell(), len(text + more_text))
result.file.delete() result.the_file.delete()
# Ensure deleted file returns None # Ensure deleted file returns None
self.assertTrue(result.file.read() == None) self.assertTrue(result.the_file.read() == None)
setfile = SetFile() setfile = SetFile()
setfile.file = text setfile.the_file = text
setfile.save() setfile.save()
setfile.validate() setfile.validate()
result = SetFile.objects.first() result = SetFile.objects.first()
self.assertTrue(setfile == result) self.assertTrue(setfile == result)
self.assertEquals(result.file.read(), text) self.assertEquals(result.the_file.read(), text)
# Try replacing file with new one # Try replacing file with new one
result.file.replace(more_text) result.the_file.replace(more_text)
result.save() result.save()
result.validate() result.validate()
result = SetFile.objects.first() result = SetFile.objects.first()
self.assertTrue(setfile == result) self.assertTrue(setfile == result)
self.assertEquals(result.file.read(), more_text) self.assertEquals(result.the_file.read(), more_text)
result.file.delete() result.the_file.delete()
PutFile.drop_collection() PutFile.drop_collection()
StreamFile.drop_collection() StreamFile.drop_collection()
@ -1653,7 +1708,7 @@ class FieldTest(unittest.TestCase):
# Make sure FileField is optional and not required # Make sure FileField is optional and not required
class DemoFile(Document): class DemoFile(Document):
file = FileField() the_file = FileField()
DemoFile.objects.create() DemoFile.objects.create()
@ -1704,20 +1759,20 @@ class FieldTest(unittest.TestCase):
""" """
class TestFile(Document): class TestFile(Document):
name = StringField() name = StringField()
file = FileField() the_file = FileField()
# First instance # First instance
testfile = TestFile() test_file = TestFile()
testfile.name = "Hello, World!" test_file.name = "Hello, World!"
testfile.file.put('Hello, World!') test_file.the_file.put('Hello, World!')
testfile.save() test_file.save()
# Second instance # Second instance
testfiledupe = TestFile() test_file_dupe = TestFile()
data = testfiledupe.file.read() # Should be None data = test_file_dupe.the_file.read() # Should be None
self.assertTrue(testfile.name != testfiledupe.name) self.assertTrue(test_file.name != test_file_dupe.name)
self.assertTrue(testfile.file.read() != data) self.assertTrue(test_file.the_file.read() != data)
TestFile.drop_collection() TestFile.drop_collection()
@ -1725,17 +1780,25 @@ class FieldTest(unittest.TestCase):
"""Ensure that a boolean test of a FileField indicates its presence """Ensure that a boolean test of a FileField indicates its presence
""" """
class TestFile(Document): class TestFile(Document):
file = FileField() the_file = FileField()
testfile = TestFile() test_file = TestFile()
self.assertFalse(bool(testfile.file)) self.assertFalse(bool(test_file.the_file))
testfile.file = 'Hello, World!' test_file.the_file = 'Hello, World!'
testfile.file.content_type = 'text/plain' test_file.the_file.content_type = 'text/plain'
testfile.save() test_file.save()
self.assertTrue(bool(testfile.file)) self.assertTrue(bool(test_file.the_file))
TestFile.drop_collection() TestFile.drop_collection()
def test_file_cmp(self):
"""Test comparing against other types"""
class TestFile(Document):
the_file = FileField()
test_file = TestFile()
self.assertFalse(test_file.the_file in [{"test": 1}])
def test_image_field(self): def test_image_field(self):
class TestImage(Document): class TestImage(Document):
@ -1799,30 +1862,30 @@ class FieldTest(unittest.TestCase):
def test_file_multidb(self): def test_file_multidb(self):
register_connection('testfiles', 'testfiles') register_connection('test_files', 'test_files')
class TestFile(Document): class TestFile(Document):
name = StringField() name = StringField()
file = FileField(db_alias="testfiles", the_file = FileField(db_alias="test_files",
collection_name="macumba") collection_name="macumba")
TestFile.drop_collection() TestFile.drop_collection()
# delete old filesystem # delete old filesystem
get_db("testfiles").macumba.files.drop() get_db("test_files").macumba.files.drop()
get_db("testfiles").macumba.chunks.drop() get_db("test_files").macumba.chunks.drop()
# First instance # First instance
testfile = TestFile() test_file = TestFile()
testfile.name = "Hello, World!" test_file.name = "Hello, World!"
testfile.file.put('Hello, World!', test_file.the_file.put('Hello, World!',
name="hello.txt") name="hello.txt")
testfile.save() test_file.save()
data = get_db("testfiles").macumba.files.find_one() data = get_db("test_files").macumba.files.find_one()
self.assertEquals(data.get('name'), 'hello.txt') self.assertEquals(data.get('name'), 'hello.txt')
testfile = TestFile.objects.first() test_file = TestFile.objects.first()
self.assertEquals(testfile.file.read(), self.assertEquals(test_file.the_file.read(),
'Hello, World!') 'Hello, World!')
def test_geo_indexes(self): def test_geo_indexes(self):

View File

@ -2287,7 +2287,8 @@ class QuerySetTest(unittest.TestCase):
@queryset_manager @queryset_manager
def objects(cls, qryset): def objects(cls, qryset):
return qryset(deleted=False) opts = {"deleted": False}
return qryset(**opts)
@queryset_manager @queryset_manager
def music_posts(doc_cls, queryset, deleted=False): def music_posts(doc_cls, queryset, deleted=False):