Compare commits
6 Commits
v0.18.1
...
base-docum
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a659f9aa8d | ||
|
|
f884839d17 | ||
|
|
a4fe091a51 | ||
|
|
216217e2c6 | ||
|
|
799775b3a7 | ||
|
|
ae0384df29 |
@@ -6,6 +6,7 @@ Changelog
|
||||
Development
|
||||
===========
|
||||
- (Fill this out as you fix issues and develop your features).
|
||||
- Add a `BaseDocument.to_dict` method #2101
|
||||
|
||||
Changes in 0.18.1
|
||||
=================
|
||||
|
||||
@@ -714,11 +714,16 @@ subsequent calls to :meth:`~mongoengine.queryset.QuerySet.order_by`. ::
|
||||
Shard keys
|
||||
==========
|
||||
|
||||
If your collection is sharded, then you need to specify the shard key as a tuple,
|
||||
using the :attr:`shard_key` attribute of :attr:`~mongoengine.Document.meta`.
|
||||
This ensures that the shard key is sent with the query when calling the
|
||||
:meth:`~mongoengine.document.Document.save` or
|
||||
:meth:`~mongoengine.document.Document.update` method on an existing
|
||||
If your collection is sharded by multiple keys, then you can improve shard
|
||||
routing (and thus the performance of your application) by specifying the shard
|
||||
key, using the :attr:`shard_key` attribute of
|
||||
:attr:`~mongoengine.Document.meta`. The shard key should be defined as a tuple.
|
||||
|
||||
This ensures that the full shard key is sent with the query when calling
|
||||
methods such as :meth:`~mongoengine.document.Document.save`,
|
||||
:meth:`~mongoengine.document.Document.update`,
|
||||
:meth:`~mongoengine.document.Document.modify`, or
|
||||
:meth:`~mongoengine.document.Document.delete` on an existing
|
||||
:class:`~mongoengine.Document` instance::
|
||||
|
||||
class LogEntry(Document):
|
||||
@@ -728,7 +733,8 @@ This ensures that the shard key is sent with the query when calling the
|
||||
data = StringField()
|
||||
|
||||
meta = {
|
||||
'shard_key': ('machine', 'timestamp',)
|
||||
'shard_key': ('machine', 'timestamp'),
|
||||
'indexes': ('machine', 'timestamp'),
|
||||
}
|
||||
|
||||
.. _document-inheritance:
|
||||
|
||||
@@ -11,18 +11,20 @@ __all__ = ('BaseDict', 'StrictDict', 'BaseList', 'EmbeddedDocumentList', 'LazyRe
|
||||
|
||||
|
||||
def mark_as_changed_wrapper(parent_method):
|
||||
"""Decorators that ensures _mark_as_changed method gets called"""
|
||||
"""Decorator that ensures _mark_as_changed method gets called."""
|
||||
def wrapper(self, *args, **kwargs):
|
||||
result = parent_method(self, *args, **kwargs) # Can't use super() in the decorator
|
||||
# Can't use super() in the decorator.
|
||||
result = parent_method(self, *args, **kwargs)
|
||||
self._mark_as_changed()
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
|
||||
def mark_key_as_changed_wrapper(parent_method):
|
||||
"""Decorators that ensures _mark_as_changed method gets called with the key argument"""
|
||||
"""Decorator that ensures _mark_as_changed method gets called with the key argument"""
|
||||
def wrapper(self, key, *args, **kwargs):
|
||||
result = parent_method(self, key, *args, **kwargs) # Can't use super() in the decorator
|
||||
# Can't use super() in the decorator.
|
||||
result = parent_method(self, key, *args, **kwargs)
|
||||
self._mark_as_changed(key)
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
@@ -309,9 +309,7 @@ class BaseDocument(object):
|
||||
return self._data['_text_score']
|
||||
|
||||
def to_mongo(self, use_db_field=True, fields=None):
|
||||
"""
|
||||
Return as SON data ready for use with MongoDB.
|
||||
"""
|
||||
"""Return as SON data ready for use with MongoDB."""
|
||||
fields = fields or []
|
||||
|
||||
data = SON()
|
||||
@@ -412,12 +410,35 @@ class BaseDocument(object):
|
||||
message = 'ValidationError (%s:%s) ' % (self._class_name, pk)
|
||||
raise ValidationError(message, errors=errors)
|
||||
|
||||
def to_dict(self):
|
||||
"""Serialize this document into a dict.
|
||||
|
||||
Return field names as they're defined on the document (as opposed to
|
||||
e.g. how they're stored in MongoDB). Return values in their
|
||||
deserialized form (i.e. the same form that you get when you access
|
||||
`doc.some_field_name`). Serialize embedded documents recursively.
|
||||
|
||||
The resultant dict can be consumed easily by other modules which
|
||||
don't need to be aware of MongoEngine-specific object types.
|
||||
|
||||
:return dict: dictionary of field name & value pairs.
|
||||
"""
|
||||
data_dict = {}
|
||||
for field_name in self._fields:
|
||||
value = getattr(self, field_name)
|
||||
if isinstance(value, BaseDocument):
|
||||
data_dict[field_name] = value.to_dict()
|
||||
else:
|
||||
data_dict[field_name] = value
|
||||
return data_dict
|
||||
|
||||
def to_json(self, *args, **kwargs):
|
||||
"""Convert this document to JSON.
|
||||
|
||||
:param use_db_field: Serialize field names as they appear in
|
||||
MongoDB (as opposed to attribute names on this document).
|
||||
Defaults to True.
|
||||
:return str: string representing the jsonified document.
|
||||
"""
|
||||
use_db_field = kwargs.pop('use_db_field', True)
|
||||
return json_util.dumps(self.to_mongo(use_db_field), *args, **kwargs)
|
||||
@@ -426,12 +447,13 @@ class BaseDocument(object):
|
||||
def from_json(cls, json_data, created=False):
|
||||
"""Converts json data to a Document instance
|
||||
|
||||
:param json_data: The json data to load into the Document
|
||||
:param created: If True, the document will be considered as a brand new document
|
||||
If False and an id is provided, it will consider that the data being
|
||||
loaded corresponds to what's already in the database (This has an impact of subsequent call to .save())
|
||||
If False and no id is provided, it will consider the data as a new document
|
||||
(default ``False``)
|
||||
:param str json_data: The json data to load into the Document
|
||||
:param bool created: If True, the document will be considered as
|
||||
a brand new document. If False and an ID is provided, it will
|
||||
consider that the data being loaded corresponds to what's already
|
||||
in the database (This has an impact of subsequent call to .save())
|
||||
If False and no id is provided, it will consider the data as a new
|
||||
document (default ``False``)
|
||||
"""
|
||||
return cls._from_son(json_util.loads(json_data), created=created)
|
||||
|
||||
|
||||
@@ -128,10 +128,9 @@ class BaseField(object):
|
||||
return instance._data.get(self.name)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
"""Descriptor for assigning a value to a field in a document.
|
||||
"""
|
||||
# If setting to None and there is a default
|
||||
# Then set the value to the default value
|
||||
"""Descriptor for assigning a value to a field in a document."""
|
||||
# If setting to None and there is a default value provided for this
|
||||
# field, then set the value to the default value.
|
||||
if value is None:
|
||||
if self.null:
|
||||
value = None
|
||||
@@ -142,12 +141,16 @@ class BaseField(object):
|
||||
|
||||
if instance._initialised:
|
||||
try:
|
||||
if (self.name not in instance._data or
|
||||
instance._data[self.name] != value):
|
||||
value_has_changed = (
|
||||
self.name not in instance._data or
|
||||
instance._data[self.name] != value
|
||||
)
|
||||
if value_has_changed:
|
||||
instance._mark_as_changed(self.name)
|
||||
except Exception:
|
||||
# Values cant be compared eg: naive and tz datetimes
|
||||
# So mark it as changed
|
||||
# Some values can't be compared and throw an error when we
|
||||
# attempt to do so (e.g. tz-naive and tz-aware datetimes).
|
||||
# Mark the field as changed in such cases.
|
||||
instance._mark_as_changed(self.name)
|
||||
|
||||
EmbeddedDocument = _import_class('EmbeddedDocument')
|
||||
@@ -157,6 +160,7 @@ class BaseField(object):
|
||||
for v in value:
|
||||
if isinstance(v, EmbeddedDocument):
|
||||
v._instance = weakref.proxy(instance)
|
||||
|
||||
instance._data[self.name] = value
|
||||
|
||||
def error(self, message='', errors=None, field_name=None):
|
||||
|
||||
@@ -544,7 +544,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
||||
|
||||
@property
|
||||
def _qs(self):
|
||||
"""Return the queryset to use for updating / reloading / deletions."""
|
||||
"""Return the default queryset corresponding to this document."""
|
||||
if not hasattr(self, '__objects'):
|
||||
self.__objects = QuerySet(self, self._get_collection())
|
||||
return self.__objects
|
||||
@@ -552,9 +552,11 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
||||
@property
|
||||
def _object_key(self):
|
||||
"""Get the query dict that can be used to fetch this object from
|
||||
the database. Most of the time it's a simple PK lookup, but in
|
||||
case of a sharded collection with a compound shard key, it can
|
||||
contain a more complex query.
|
||||
the database.
|
||||
|
||||
Most of the time the dict is a simple PK lookup, but in case of
|
||||
a sharded collection with a compound shard key, it can contain a more
|
||||
complex query.
|
||||
"""
|
||||
select_dict = {'pk': self.pk}
|
||||
shard_key = self.__class__._meta.get('shard_key', tuple())
|
||||
|
||||
@@ -73,6 +73,7 @@ class BaseQuerySet(object):
|
||||
self._initial_query = {
|
||||
'_cls': {'$in': self._document._subclasses}}
|
||||
self._loaded_fields = QueryFieldList(always_include=['_cls'])
|
||||
|
||||
self._cursor_obj = None
|
||||
self._limit = None
|
||||
self._skip = None
|
||||
@@ -707,8 +708,9 @@ class BaseQuerySet(object):
|
||||
return queryset
|
||||
|
||||
def no_sub_classes(self):
|
||||
"""
|
||||
Only return instances of this document and not any inherited documents
|
||||
"""Filter for only the instances of this specific document.
|
||||
|
||||
Do NOT return any inherited documents.
|
||||
"""
|
||||
if self._document._meta.get('allow_inheritance') is True:
|
||||
self._initial_query = {'_cls': self._document._class_name}
|
||||
|
||||
@@ -3525,5 +3525,76 @@ class InstanceTest(MongoDBTestCase):
|
||||
User.objects().select_related()
|
||||
|
||||
|
||||
class DocumentToDictTest(MongoDBTestCase):
|
||||
"""Class for testing the BaseDocument.to_dict method."""
|
||||
|
||||
def test_to_dict(self):
|
||||
class Person(Document):
|
||||
name = StringField()
|
||||
age = IntField()
|
||||
|
||||
p = Person(name='Tom', age=30)
|
||||
self.assertEqual(p.to_dict(), {'id': None, 'name': 'Tom', 'age': 30})
|
||||
|
||||
def test_to_dict_with_a_persisted_doc(self):
|
||||
class Person(Document):
|
||||
name = StringField()
|
||||
age = IntField()
|
||||
|
||||
p = Person.objects.create(name='Tom', age=30)
|
||||
p_dict = p.to_dict()
|
||||
self.assertTrue(p_dict['id'])
|
||||
self.assertEqual(p_dict['name'], 'Tom')
|
||||
self.assertEqual(p_dict['age'], 30)
|
||||
|
||||
def test_to_dict_empty_doc(self):
|
||||
class Person(Document):
|
||||
name = StringField()
|
||||
age = IntField()
|
||||
|
||||
p = Person()
|
||||
self.assertEqual(p.to_dict(), {'id': None, 'name': None, 'age': None})
|
||||
|
||||
def test_to_dict_with_default_values(self):
|
||||
class Person(Document):
|
||||
name = StringField(default='Unknown')
|
||||
age = IntField(default=0)
|
||||
|
||||
p = Person()
|
||||
self.assertEqual(
|
||||
p.to_dict(),
|
||||
{'id': None, 'name': 'Unknown', 'age': 0}
|
||||
)
|
||||
|
||||
def test_to_dict_with_a_db_field(self):
|
||||
class Person(Document):
|
||||
name = StringField(db_field='db_name')
|
||||
|
||||
p = Person(name='Tom')
|
||||
self.assertEqual(p.to_dict(), {'id': None, 'name': 'Tom'})
|
||||
|
||||
def test_to_dict_with_a_primary_key(self):
|
||||
class Person(Document):
|
||||
username = StringField(primary_key=True)
|
||||
|
||||
p = Person(username='tomtom')
|
||||
self.assertEqual(p.to_dict(), {'username': 'tomtom'})
|
||||
|
||||
def test_to_dict_with_an_embedded_document(self):
|
||||
class Book(EmbeddedDocument):
|
||||
title = StringField()
|
||||
|
||||
class Author(Document):
|
||||
name = StringField()
|
||||
book = EmbeddedDocumentField(Book)
|
||||
|
||||
a = Author(name='Yuval', book=Book(title='Sapiens'))
|
||||
self.assertEqual(a.to_dict(), {
|
||||
'id': None,
|
||||
'name': 'Yuval',
|
||||
'book': {'title': 'Sapiens'}
|
||||
})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user