Merge branch 'master' into 0.7
Conflicts: mongoengine/base.py
This commit is contained in:
commit
52d80ac23c
1
AUTHORS
1
AUTHORS
@ -116,3 +116,4 @@ that much better:
|
||||
* Alexandre González
|
||||
* Thomas Steinacher
|
||||
* Tommi Komulainen
|
||||
* Peter Landry
|
||||
|
@ -2,6 +2,7 @@
|
||||
MongoEngine
|
||||
===========
|
||||
:Info: MongoEngine is an ORM-like layer on top of PyMongo.
|
||||
:Repository: https://github.com/MongoEngine/mongoengine
|
||||
:Author: Harry Marr (http://github.com/hmarr)
|
||||
:Maintainer: Ross Lawley (http://github.com/rozza)
|
||||
|
||||
|
@ -2,8 +2,14 @@
|
||||
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)
|
||||
- Fixed MapField lookup for fields without declared lookups (MongoEngine/mongoengine#46)
|
||||
|
@ -232,7 +232,7 @@ custom manager methods as you like::
|
||||
BlogPost(title='test1', published=False).save()
|
||||
BlogPost(title='test2', published=True).save()
|
||||
assert len(BlogPost.objects) == 2
|
||||
assert len(BlogPost.live_posts) == 1
|
||||
assert len(BlogPost.live_posts()) == 1
|
||||
|
||||
Custom QuerySets
|
||||
================
|
||||
@ -243,11 +243,16 @@ a document, set ``queryset_class`` to the custom class in a
|
||||
:class:`~mongoengine.Document`\ s ``meta`` dictionary::
|
||||
|
||||
class AwesomerQuerySet(QuerySet):
|
||||
pass
|
||||
|
||||
def get_awesome(self):
|
||||
return self.filter(awesome=True)
|
||||
|
||||
class Page(Document):
|
||||
meta = {'queryset_class': AwesomerQuerySet}
|
||||
|
||||
# To call:
|
||||
Page.objects.get_awesome()
|
||||
|
||||
.. versionadded:: 0.4
|
||||
|
||||
Aggregation
|
||||
|
@ -12,7 +12,7 @@ from signals import *
|
||||
__all__ = (document.__all__ + fields.__all__ + connection.__all__ +
|
||||
queryset.__all__ + signals.__all__)
|
||||
|
||||
VERSION = (0, 6, 18)
|
||||
VERSION = (0, 6, 20)
|
||||
|
||||
|
||||
def get_version():
|
||||
|
@ -16,7 +16,6 @@ import pymongo
|
||||
from bson import ObjectId
|
||||
from bson.dbref import DBRef
|
||||
|
||||
|
||||
class NotRegistered(Exception):
|
||||
pass
|
||||
|
||||
@ -112,6 +111,7 @@ class ValidationError(AssertionError):
|
||||
|
||||
|
||||
_document_registry = {}
|
||||
_module_registry = {}
|
||||
|
||||
|
||||
def get_document(name):
|
||||
@ -199,7 +199,8 @@ class BaseField(object):
|
||||
"""Descriptor for assigning a value to a field in a document.
|
||||
"""
|
||||
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):
|
||||
"""Raises a ValidationError.
|
||||
@ -264,7 +265,7 @@ class ComplexBaseField(BaseField):
|
||||
"""
|
||||
|
||||
field = None
|
||||
_dereference = False
|
||||
__dereference = False
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
"""Descriptor to automatically dereference references.
|
||||
@ -276,8 +277,6 @@ class ComplexBaseField(BaseField):
|
||||
dereference = self.field is None or isinstance(self.field,
|
||||
(GenericReferenceField, ReferenceField))
|
||||
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.get(self.name), max_depth=1, instance=instance,
|
||||
name=self.name
|
||||
@ -293,14 +292,13 @@ class ComplexBaseField(BaseField):
|
||||
value = BaseDict(value, instance, self.name)
|
||||
instance._data[self.name] = value
|
||||
|
||||
if self._dereference and instance._initialised and \
|
||||
isinstance(value, (BaseList, BaseDict)) and not value._dereferenced:
|
||||
if (instance._initialised and isinstance(value, (BaseList, BaseDict))
|
||||
and not value._dereferenced):
|
||||
value = self._dereference(
|
||||
value, max_depth=1, instance=instance, name=self.name
|
||||
)
|
||||
value._dereferenced = True
|
||||
instance._data[self.name] = value
|
||||
|
||||
return value
|
||||
|
||||
def __set__(self, instance, value):
|
||||
@ -441,6 +439,13 @@ class ComplexBaseField(BaseField):
|
||||
|
||||
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):
|
||||
"""An field wrapper around MongoDB's ObjectIds.
|
||||
@ -473,6 +478,7 @@ class DocumentMetaclass(type):
|
||||
"""
|
||||
|
||||
def __new__(cls, name, bases, attrs):
|
||||
|
||||
def _get_mixin_fields(base):
|
||||
attrs = {}
|
||||
attrs.update(dict([(k, v) for k, v in base.__dict__.items()
|
||||
@ -501,9 +507,7 @@ class DocumentMetaclass(type):
|
||||
class_name = [name]
|
||||
superclasses = {}
|
||||
simple_class = True
|
||||
|
||||
for base in bases:
|
||||
|
||||
# Include all fields present in superclasses
|
||||
if hasattr(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']:
|
||||
raise ValueError('Only direct subclasses of Document may set '
|
||||
'"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
|
||||
field_names = {}
|
||||
for attr_name, attr_value in attrs.items():
|
||||
if hasattr(attr_value, "__class__") and \
|
||||
issubclass(attr_value.__class__, BaseField):
|
||||
attr_value.name = attr_name
|
||||
if not attr_value.db_field:
|
||||
attr_value.db_field = attr_name
|
||||
doc_fields[attr_name] = attr_value
|
||||
field_names[attr_value.db_field] = field_names.get(attr_value.db_field, 0) + 1
|
||||
for attr_name, attr_value in attrs.iteritems():
|
||||
if not isinstance(attr_value, BaseField):
|
||||
continue
|
||||
attr_value.name = attr_name
|
||||
if not attr_value.db_field:
|
||||
attr_value.db_field = attr_name
|
||||
doc_fields[attr_name] = attr_value
|
||||
|
||||
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]
|
||||
if duplicate_db_fields:
|
||||
@ -564,11 +566,24 @@ class DocumentMetaclass(type):
|
||||
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['_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)
|
||||
for field in new_class._fields.values():
|
||||
|
||||
for field in new_class._fields.itervalues():
|
||||
field.owner_document = new_class
|
||||
|
||||
delete_rule = getattr(field, 'reverse_delete_rule', DO_NOTHING)
|
||||
@ -738,7 +753,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
|
||||
unique_indexes = cls._unique_with_indexes(new_class)
|
||||
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
|
||||
if field.primary_key:
|
||||
current_pk = new_class._meta['id_field']
|
||||
@ -809,21 +824,23 @@ class BaseDocument(object):
|
||||
self._data = {}
|
||||
|
||||
# Assign default values to instance
|
||||
for attr_name, field in self._fields.items():
|
||||
value = getattr(self, attr_name, None)
|
||||
setattr(self, attr_name, value)
|
||||
for key, field in self._fields.iteritems():
|
||||
if self._db_field_map.get(key, key) in values:
|
||||
continue
|
||||
value = getattr(self, key, None)
|
||||
setattr(self, key, value)
|
||||
|
||||
# Set passed values after initialisation
|
||||
if self._dynamic:
|
||||
self._dynamic_fields = {}
|
||||
dynamic_data = {}
|
||||
for key, value in values.items():
|
||||
for key, value in values.iteritems():
|
||||
if key in self._fields or key == '_id':
|
||||
setattr(self, key, value)
|
||||
elif self._dynamic:
|
||||
dynamic_data[key] = value
|
||||
else:
|
||||
for key, value in values.items():
|
||||
for key, value in values.iteritems():
|
||||
key = self._reverse_db_field_map.get(key, key)
|
||||
setattr(self, key, value)
|
||||
|
||||
@ -832,7 +849,7 @@ class BaseDocument(object):
|
||||
|
||||
if self._dynamic:
|
||||
self._dynamic_lock = False
|
||||
for key, value in dynamic_data.items():
|
||||
for key, value in dynamic_data.iteritems():
|
||||
setattr(self, key, value)
|
||||
|
||||
# Flag initialised
|
||||
|
@ -34,7 +34,9 @@ class DeReference(object):
|
||||
|
||||
doc_type = None
|
||||
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):
|
||||
doc_type = doc_type.document_type
|
||||
|
@ -1333,7 +1333,7 @@ class UUIDField(BaseField):
|
||||
super(UUIDField, self).__init__(**kwargs)
|
||||
|
||||
def to_python(self, value):
|
||||
if not self.binary:
|
||||
if not self._binary:
|
||||
if not isinstance(value, basestring):
|
||||
value = unicode(value)
|
||||
return uuid.UUID(value)
|
||||
|
@ -329,6 +329,7 @@ class QuerySet(object):
|
||||
"""
|
||||
|
||||
__already_indexed = set()
|
||||
__dereference = False
|
||||
|
||||
def __init__(self, document, collection):
|
||||
self._document = document
|
||||
@ -600,7 +601,6 @@ class QuerySet(object):
|
||||
|
||||
if self._hint != -1:
|
||||
self._cursor_obj.hint(self._hint)
|
||||
|
||||
return self._cursor_obj
|
||||
|
||||
@classmethod
|
||||
@ -765,8 +765,22 @@ class QuerySet(object):
|
||||
key = '.'.join(parts)
|
||||
if op is None or key not in mongo_query:
|
||||
mongo_query[key] = value
|
||||
elif key in mongo_query and isinstance(mongo_query[key], dict):
|
||||
mongo_query[key].update(value)
|
||||
elif key in mongo_query:
|
||||
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
|
||||
|
||||
@ -1152,9 +1166,10 @@ class QuerySet(object):
|
||||
|
||||
.. versionadded:: 0.4
|
||||
.. versionchanged:: 0.5 - Fixed handling references
|
||||
.. versionchanged:: 0.6 - Improved db_field refrence handling
|
||||
"""
|
||||
from dereference import DeReference
|
||||
return DeReference()(self._cursor.distinct(field), 1)
|
||||
return self._dereference(self._cursor.distinct(field), 1,
|
||||
name=field, instance=self._document)
|
||||
|
||||
def only(self, *fields):
|
||||
"""Load only a subset of this document's fields. ::
|
||||
@ -1854,13 +1869,30 @@ class QuerySet(object):
|
||||
|
||||
.. versionadded:: 0.5
|
||||
"""
|
||||
from dereference import DeReference
|
||||
# Make select related work the same for querysets
|
||||
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):
|
||||
"""
|
||||
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
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
%define srcname mongoengine
|
||||
|
||||
Name: python-%{srcname}
|
||||
Version: 0.6.18
|
||||
Version: 0.6.20
|
||||
Release: 1%{?dist}
|
||||
Summary: A Python Document-Object Mapper for working with MongoDB
|
||||
|
||||
|
@ -284,6 +284,7 @@ class FieldTest(unittest.TestCase):
|
||||
uu = uuid.uuid4()
|
||||
Person(api_key=uu).save()
|
||||
self.assertEqual(1, Person.objects(api_key=uu).count())
|
||||
self.assertEqual(uu, Person.objects.first().api_key)
|
||||
|
||||
person = Person()
|
||||
valid = (uuid.uuid4(), uuid.uuid1())
|
||||
@ -308,6 +309,7 @@ class FieldTest(unittest.TestCase):
|
||||
uu = uuid.uuid4()
|
||||
Person(api_key=uu).save()
|
||||
self.assertEqual(1, Person.objects(api_key=uu).count())
|
||||
self.assertEqual(uu, Person.objects.first().api_key)
|
||||
|
||||
person = Person()
|
||||
valid = (uuid.uuid4(), uuid.uuid1())
|
||||
|
@ -830,7 +830,11 @@ class QuerySetTest(unittest.TestCase):
|
||||
def test_filter_chaining(self):
|
||||
"""Ensure filters can be chained together.
|
||||
"""
|
||||
class Blog(Document):
|
||||
id = StringField(unique=True, primary_key=True)
|
||||
|
||||
class BlogPost(Document):
|
||||
blog = ReferenceField(Blog)
|
||||
title = StringField()
|
||||
is_published = BooleanField()
|
||||
published_date = DateTimeField()
|
||||
@ -839,13 +843,24 @@ class QuerySetTest(unittest.TestCase):
|
||||
def published(doc_cls, queryset):
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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))
|
||||
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()
|
||||
Blog.drop_collection()
|
||||
|
||||
def test_ordering(self):
|
||||
"""Ensure default ordering is applied and can be overridden.
|
||||
@ -2280,6 +2302,28 @@ class QuerySetTest(unittest.TestCase):
|
||||
|
||||
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):
|
||||
"""Ensure that custom QuerySetManager instances work as expected.
|
||||
"""
|
||||
|
Loading…
x
Reference in New Issue
Block a user