Merge branch 'master' into 0.7

Conflicts:
	mongoengine/base.py
This commit is contained in:
Ross Lawley 2012-08-07 10:41:49 +01:00
commit 52d80ac23c
12 changed files with 163 additions and 53 deletions

View File

@ -116,3 +116,4 @@ that much better:
* Alexandre González * Alexandre González
* Thomas Steinacher * Thomas Steinacher
* Tommi Komulainen * Tommi Komulainen
* Peter Landry

View File

@ -2,6 +2,7 @@
MongoEngine MongoEngine
=========== ===========
:Info: MongoEngine is an ORM-like layer on top of PyMongo. :Info: MongoEngine is an ORM-like layer on top of PyMongo.
:Repository: https://github.com/MongoEngine/mongoengine
:Author: Harry Marr (http://github.com/hmarr) :Author: Harry Marr (http://github.com/hmarr)
:Maintainer: Ross Lawley (http://github.com/rozza) :Maintainer: Ross Lawley (http://github.com/rozza)

View File

@ -2,8 +2,14 @@
Changelog Changelog
========= =========
Changes in 0.6.X Changes in 0.6.20
================ =================
- Added support for distinct and db_alias (MongoEngine/mongoengine#59)
- Improved support for chained querysets when constraining the same fields (hmarr/mongoengine#554)
- Fixed BinaryField lookup re (MongoEngine/mongoengine#48)
Changes in 0.6.19
=================
- Added Binary support to UUID (MongoEngine/mongoengine#47) - Added Binary support to UUID (MongoEngine/mongoengine#47)
- Fixed MapField lookup for fields without declared lookups (MongoEngine/mongoengine#46) - Fixed MapField lookup for fields without declared lookups (MongoEngine/mongoengine#46)

View File

@ -232,7 +232,7 @@ custom manager methods as you like::
BlogPost(title='test1', published=False).save() BlogPost(title='test1', published=False).save()
BlogPost(title='test2', published=True).save() BlogPost(title='test2', published=True).save()
assert len(BlogPost.objects) == 2 assert len(BlogPost.objects) == 2
assert len(BlogPost.live_posts) == 1 assert len(BlogPost.live_posts()) == 1
Custom QuerySets Custom QuerySets
================ ================
@ -243,11 +243,16 @@ a document, set ``queryset_class`` to the custom class in a
:class:`~mongoengine.Document`\ s ``meta`` dictionary:: :class:`~mongoengine.Document`\ s ``meta`` dictionary::
class AwesomerQuerySet(QuerySet): class AwesomerQuerySet(QuerySet):
pass
def get_awesome(self):
return self.filter(awesome=True)
class Page(Document): class Page(Document):
meta = {'queryset_class': AwesomerQuerySet} meta = {'queryset_class': AwesomerQuerySet}
# To call:
Page.objects.get_awesome()
.. versionadded:: 0.4 .. versionadded:: 0.4
Aggregation Aggregation

View File

@ -12,7 +12,7 @@ from signals import *
__all__ = (document.__all__ + fields.__all__ + connection.__all__ + __all__ = (document.__all__ + fields.__all__ + connection.__all__ +
queryset.__all__ + signals.__all__) queryset.__all__ + signals.__all__)
VERSION = (0, 6, 18) VERSION = (0, 6, 20)
def get_version(): def get_version():

View File

@ -16,7 +16,6 @@ import pymongo
from bson import ObjectId from bson import ObjectId
from bson.dbref import DBRef from bson.dbref import DBRef
class NotRegistered(Exception): class NotRegistered(Exception):
pass pass
@ -112,6 +111,7 @@ class ValidationError(AssertionError):
_document_registry = {} _document_registry = {}
_module_registry = {}
def get_document(name): def get_document(name):
@ -199,7 +199,8 @@ class BaseField(object):
"""Descriptor for assigning a value to a field in a document. """Descriptor for assigning a value to a field in a document.
""" """
instance._data[self.name] = value instance._data[self.name] = value
instance._mark_as_changed(self.name) if instance._initialised:
instance._mark_as_changed(self.name)
def error(self, message="", errors=None, field_name=None): def error(self, message="", errors=None, field_name=None):
"""Raises a ValidationError. """Raises a ValidationError.
@ -264,7 +265,7 @@ class ComplexBaseField(BaseField):
""" """
field = None field = None
_dereference = False __dereference = False
def __get__(self, instance, owner): def __get__(self, instance, owner):
"""Descriptor to automatically dereference references. """Descriptor to automatically dereference references.
@ -276,8 +277,6 @@ class ComplexBaseField(BaseField):
dereference = self.field is None or isinstance(self.field, dereference = self.field is None or isinstance(self.field,
(GenericReferenceField, ReferenceField)) (GenericReferenceField, ReferenceField))
if not self._dereference and instance._initialised and dereference: if not self._dereference and instance._initialised and dereference:
from dereference import DeReference
self._dereference = DeReference() # Cached
instance._data[self.name] = self._dereference( instance._data[self.name] = self._dereference(
instance._data.get(self.name), max_depth=1, instance=instance, instance._data.get(self.name), max_depth=1, instance=instance,
name=self.name name=self.name
@ -293,14 +292,13 @@ class ComplexBaseField(BaseField):
value = BaseDict(value, instance, self.name) value = BaseDict(value, instance, self.name)
instance._data[self.name] = value instance._data[self.name] = value
if self._dereference and instance._initialised and \ if (instance._initialised and isinstance(value, (BaseList, BaseDict))
isinstance(value, (BaseList, BaseDict)) and not value._dereferenced: and not value._dereferenced):
value = self._dereference( value = self._dereference(
value, max_depth=1, instance=instance, name=self.name value, max_depth=1, instance=instance, name=self.name
) )
value._dereferenced = True value._dereferenced = True
instance._data[self.name] = value instance._data[self.name] = value
return value return value
def __set__(self, instance, value): def __set__(self, instance, value):
@ -441,6 +439,13 @@ class ComplexBaseField(BaseField):
owner_document = property(_get_owner_document, _set_owner_document) owner_document = property(_get_owner_document, _set_owner_document)
@property
def _dereference(self,):
if not self.__dereference:
from dereference import DeReference
self.__dereference = DeReference() # Cached
return self.__dereference
class ObjectIdField(BaseField): class ObjectIdField(BaseField):
"""An field wrapper around MongoDB's ObjectIds. """An field wrapper around MongoDB's ObjectIds.
@ -473,6 +478,7 @@ class DocumentMetaclass(type):
""" """
def __new__(cls, name, bases, attrs): def __new__(cls, name, bases, attrs):
def _get_mixin_fields(base): def _get_mixin_fields(base):
attrs = {} attrs = {}
attrs.update(dict([(k, v) for k, v in base.__dict__.items() attrs.update(dict([(k, v) for k, v in base.__dict__.items()
@ -501,9 +507,7 @@ class DocumentMetaclass(type):
class_name = [name] class_name = [name]
superclasses = {} superclasses = {}
simple_class = True simple_class = True
for base in bases: for base in bases:
# Include all fields present in superclasses # Include all fields present in superclasses
if hasattr(base, '_fields'): if hasattr(base, '_fields'):
doc_fields.update(base._fields) doc_fields.update(base._fields)
@ -543,20 +547,18 @@ class DocumentMetaclass(type):
if not simple_class and not meta['allow_inheritance'] and not meta['abstract']: if not simple_class and not meta['allow_inheritance'] and not meta['abstract']:
raise ValueError('Only direct subclasses of Document may set ' raise ValueError('Only direct subclasses of Document may set '
'"allow_inheritance" to False') '"allow_inheritance" to False')
attrs['_meta'] = meta
attrs['_class_name'] = doc_class_name
attrs['_superclasses'] = superclasses
# Add the document's fields to the _fields attribute # Add the document's fields to the _fields attribute
field_names = {} field_names = {}
for attr_name, attr_value in attrs.items(): for attr_name, attr_value in attrs.iteritems():
if hasattr(attr_value, "__class__") and \ if not isinstance(attr_value, BaseField):
issubclass(attr_value.__class__, BaseField): continue
attr_value.name = attr_name attr_value.name = attr_name
if not attr_value.db_field: if not attr_value.db_field:
attr_value.db_field = attr_name attr_value.db_field = attr_name
doc_fields[attr_name] = attr_value doc_fields[attr_name] = attr_value
field_names[attr_value.db_field] = field_names.get(attr_value.db_field, 0) + 1
field_names[attr_value.db_field] = field_names.get(attr_value.db_field, 0) + 1
duplicate_db_fields = [k for k, v in field_names.items() if v > 1] duplicate_db_fields = [k for k, v in field_names.items() if v > 1]
if duplicate_db_fields: if duplicate_db_fields:
@ -564,11 +566,24 @@ class DocumentMetaclass(type):
attrs['_fields'] = doc_fields attrs['_fields'] = doc_fields
attrs['_db_field_map'] = dict([(k, v.db_field) for k, v in doc_fields.items() if k != v.db_field]) attrs['_db_field_map'] = dict([(k, v.db_field) for k, v in doc_fields.items() if k != v.db_field])
attrs['_reverse_db_field_map'] = dict([(v, k) for k, v in attrs['_db_field_map'].items()]) attrs['_reverse_db_field_map'] = dict([(v, k) for k, v in attrs['_db_field_map'].items()])
attrs['_meta'] = meta
attrs['_class_name'] = doc_class_name
attrs['_superclasses'] = superclasses
from mongoengine import Document, EmbeddedDocument, DictField if 'Document' not in _module_registry:
from mongoengine.document import Document, EmbeddedDocument
from mongoengine.fields import DictField
_module_registry['Document'] = Document
_module_registry['EmbeddedDocument'] = EmbeddedDocument
_module_registry['DictField'] = DictField
else:
Document = _module_registry.get('Document')
EmbeddedDocument = _module_registry.get('EmbeddedDocument')
DictField = _module_registry.get('DictField')
new_class = super_new(cls, name, bases, attrs) new_class = super_new(cls, name, bases, attrs)
for field in new_class._fields.values():
for field in new_class._fields.itervalues():
field.owner_document = new_class field.owner_document = new_class
delete_rule = getattr(field, 'reverse_delete_rule', DO_NOTHING) delete_rule = getattr(field, 'reverse_delete_rule', DO_NOTHING)
@ -738,7 +753,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
unique_indexes = cls._unique_with_indexes(new_class) unique_indexes = cls._unique_with_indexes(new_class)
new_class._meta['unique_indexes'] = unique_indexes new_class._meta['unique_indexes'] = unique_indexes
for field_name, field in new_class._fields.items(): for field_name, field in new_class._fields.iteritems():
# Check for custom primary key # Check for custom primary key
if field.primary_key: if field.primary_key:
current_pk = new_class._meta['id_field'] current_pk = new_class._meta['id_field']
@ -809,21 +824,23 @@ class BaseDocument(object):
self._data = {} self._data = {}
# Assign default values to instance # Assign default values to instance
for attr_name, field in self._fields.items(): for key, field in self._fields.iteritems():
value = getattr(self, attr_name, None) if self._db_field_map.get(key, key) in values:
setattr(self, attr_name, value) continue
value = getattr(self, key, None)
setattr(self, key, value)
# Set passed values after initialisation # Set passed values after initialisation
if self._dynamic: if self._dynamic:
self._dynamic_fields = {} self._dynamic_fields = {}
dynamic_data = {} dynamic_data = {}
for key, value in values.items(): for key, value in values.iteritems():
if key in self._fields or key == '_id': if key in self._fields or key == '_id':
setattr(self, key, value) setattr(self, key, value)
elif self._dynamic: elif self._dynamic:
dynamic_data[key] = value dynamic_data[key] = value
else: else:
for key, value in values.items(): for key, value in values.iteritems():
key = self._reverse_db_field_map.get(key, key) key = self._reverse_db_field_map.get(key, key)
setattr(self, key, value) setattr(self, key, value)
@ -832,7 +849,7 @@ class BaseDocument(object):
if self._dynamic: if self._dynamic:
self._dynamic_lock = False self._dynamic_lock = False
for key, value in dynamic_data.items(): for key, value in dynamic_data.iteritems():
setattr(self, key, value) setattr(self, key, value)
# Flag initialised # Flag initialised

View File

@ -34,7 +34,9 @@ class DeReference(object):
doc_type = None doc_type = None
if instance and instance._fields: if instance and instance._fields:
doc_type = instance._fields[name].field doc_type = instance._fields[name]
if hasattr(doc_type, 'field'):
doc_type = doc_type.field
if isinstance(doc_type, ReferenceField): if isinstance(doc_type, ReferenceField):
doc_type = doc_type.document_type doc_type = doc_type.document_type

View File

@ -1333,7 +1333,7 @@ class UUIDField(BaseField):
super(UUIDField, self).__init__(**kwargs) super(UUIDField, self).__init__(**kwargs)
def to_python(self, value): def to_python(self, value):
if not self.binary: if not self._binary:
if not isinstance(value, basestring): if not isinstance(value, basestring):
value = unicode(value) value = unicode(value)
return uuid.UUID(value) return uuid.UUID(value)

View File

@ -329,6 +329,7 @@ class QuerySet(object):
""" """
__already_indexed = set() __already_indexed = set()
__dereference = False
def __init__(self, document, collection): def __init__(self, document, collection):
self._document = document self._document = document
@ -600,7 +601,6 @@ class QuerySet(object):
if self._hint != -1: if self._hint != -1:
self._cursor_obj.hint(self._hint) self._cursor_obj.hint(self._hint)
return self._cursor_obj return self._cursor_obj
@classmethod @classmethod
@ -765,8 +765,22 @@ class QuerySet(object):
key = '.'.join(parts) key = '.'.join(parts)
if op is None or key not in mongo_query: if op is None or key not in mongo_query:
mongo_query[key] = value mongo_query[key] = value
elif key in mongo_query and isinstance(mongo_query[key], dict): elif key in mongo_query:
mongo_query[key].update(value) if isinstance(mongo_query[key], dict) and isinstance(value, dict):
mongo_query[key].update(value)
elif isinstance(mongo_query[key], list):
mongo_query[key].append(value)
else:
mongo_query[key] = [mongo_query[key], value]
for k, v in mongo_query.items():
if isinstance(v, list):
value = [{k:val} for val in v]
if '$and' in mongo_query.keys():
mongo_query['$and'].append(value)
else:
mongo_query['$and'] = value
del mongo_query[k]
return mongo_query return mongo_query
@ -1152,9 +1166,10 @@ class QuerySet(object):
.. versionadded:: 0.4 .. versionadded:: 0.4
.. versionchanged:: 0.5 - Fixed handling references .. versionchanged:: 0.5 - Fixed handling references
.. versionchanged:: 0.6 - Improved db_field refrence handling
""" """
from dereference import DeReference return self._dereference(self._cursor.distinct(field), 1,
return DeReference()(self._cursor.distinct(field), 1) name=field, instance=self._document)
def only(self, *fields): def only(self, *fields):
"""Load only a subset of this document's fields. :: """Load only a subset of this document's fields. ::
@ -1854,13 +1869,30 @@ class QuerySet(object):
.. versionadded:: 0.5 .. versionadded:: 0.5
""" """
from dereference import DeReference
# Make select related work the same for querysets # Make select related work the same for querysets
max_depth += 1 max_depth += 1
return DeReference()(self, max_depth=max_depth) return self._dereference(self, max_depth=max_depth)
@property
def _dereference(self):
if not self.__dereference:
from dereference import DeReference
self.__dereference = DeReference() # Cached
return self.__dereference
class QuerySetManager(object): class QuerySetManager(object):
"""
The default QuerySet Manager.
Custom QuerySet Manager functions can extend this class and users can
add extra queryset functionality. Any custom manager methods must accept a
:class:`~mongoengine.Document` class as its first argument, and a
:class:`~mongoengine.queryset.QuerySet` as its second argument.
The method function should return a :class:`~mongoengine.queryset.QuerySet`
, probably the same one that was passed in, but modified in some way.
"""
get_queryset = None get_queryset = None

View File

@ -5,7 +5,7 @@
%define srcname mongoengine %define srcname mongoengine
Name: python-%{srcname} Name: python-%{srcname}
Version: 0.6.18 Version: 0.6.20
Release: 1%{?dist} Release: 1%{?dist}
Summary: A Python Document-Object Mapper for working with MongoDB Summary: A Python Document-Object Mapper for working with MongoDB

View File

@ -284,6 +284,7 @@ class FieldTest(unittest.TestCase):
uu = uuid.uuid4() uu = uuid.uuid4()
Person(api_key=uu).save() Person(api_key=uu).save()
self.assertEqual(1, Person.objects(api_key=uu).count()) self.assertEqual(1, Person.objects(api_key=uu).count())
self.assertEqual(uu, Person.objects.first().api_key)
person = Person() person = Person()
valid = (uuid.uuid4(), uuid.uuid1()) valid = (uuid.uuid4(), uuid.uuid1())
@ -308,6 +309,7 @@ class FieldTest(unittest.TestCase):
uu = uuid.uuid4() uu = uuid.uuid4()
Person(api_key=uu).save() Person(api_key=uu).save()
self.assertEqual(1, Person.objects(api_key=uu).count()) self.assertEqual(1, Person.objects(api_key=uu).count())
self.assertEqual(uu, Person.objects.first().api_key)
person = Person() person = Person()
valid = (uuid.uuid4(), uuid.uuid1()) valid = (uuid.uuid4(), uuid.uuid1())

View File

@ -830,7 +830,11 @@ class QuerySetTest(unittest.TestCase):
def test_filter_chaining(self): def test_filter_chaining(self):
"""Ensure filters can be chained together. """Ensure filters can be chained together.
""" """
class Blog(Document):
id = StringField(unique=True, primary_key=True)
class BlogPost(Document): class BlogPost(Document):
blog = ReferenceField(Blog)
title = StringField() title = StringField()
is_published = BooleanField() is_published = BooleanField()
published_date = DateTimeField() published_date = DateTimeField()
@ -839,13 +843,24 @@ class QuerySetTest(unittest.TestCase):
def published(doc_cls, queryset): def published(doc_cls, queryset):
return queryset(is_published=True) return queryset(is_published=True)
blog_post_1 = BlogPost(title="Blog Post #1", Blog.drop_collection()
BlogPost.drop_collection()
blog_1 = Blog(id="1")
blog_2 = Blog(id="2")
blog_3 = Blog(id="3")
blog_1.save()
blog_2.save()
blog_3.save()
blog_post_1 = BlogPost(blog=blog_1, title="Blog Post #1",
is_published = True, is_published = True,
published_date=datetime(2010, 1, 5, 0, 0 ,0)) published_date=datetime(2010, 1, 5, 0, 0 ,0))
blog_post_2 = BlogPost(title="Blog Post #2", blog_post_2 = BlogPost(blog=blog_2, title="Blog Post #2",
is_published = True, is_published = True,
published_date=datetime(2010, 1, 6, 0, 0 ,0)) published_date=datetime(2010, 1, 6, 0, 0 ,0))
blog_post_3 = BlogPost(title="Blog Post #3", blog_post_3 = BlogPost(blog=blog_3, title="Blog Post #3",
is_published = True, is_published = True,
published_date=datetime(2010, 1, 7, 0, 0 ,0)) published_date=datetime(2010, 1, 7, 0, 0 ,0))
@ -859,7 +874,14 @@ class QuerySetTest(unittest.TestCase):
published_date__lt=datetime(2010, 1, 7, 0, 0 ,0)) published_date__lt=datetime(2010, 1, 7, 0, 0 ,0))
self.assertEqual(published_posts.count(), 2) self.assertEqual(published_posts.count(), 2)
blog_posts = BlogPost.objects
blog_posts = blog_posts.filter(blog__in=[blog_1, blog_2])
blog_posts = blog_posts.filter(blog=blog_3)
self.assertEqual(blog_posts.count(), 0)
BlogPost.drop_collection() BlogPost.drop_collection()
Blog.drop_collection()
def test_ordering(self): def test_ordering(self):
"""Ensure default ordering is applied and can be overridden. """Ensure default ordering is applied and can be overridden.
@ -2280,6 +2302,28 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(Foo.objects.distinct("bar"), [bar]) self.assertEqual(Foo.objects.distinct("bar"), [bar])
def test_distinct_handles_references_to_alias(self):
register_connection('testdb', 'mongoenginetest2')
class Foo(Document):
bar = ReferenceField("Bar")
meta = {'db_alias': 'testdb'}
class Bar(Document):
text = StringField()
meta = {'db_alias': 'testdb'}
Bar.drop_collection()
Foo.drop_collection()
bar = Bar(text="hi")
bar.save()
foo = Foo(bar=bar)
foo.save()
self.assertEquals(Foo.objects.distinct("bar"), [bar])
def test_custom_manager(self): def test_custom_manager(self):
"""Ensure that custom QuerySetManager instances work as expected. """Ensure that custom QuerySetManager instances work as expected.
""" """