Compare commits

..

2 Commits

Author SHA1 Message Date
Stefan Wojcik
2d9e21f639 one more test 2017-03-05 17:39:53 -05:00
Stefan Wojcik
4631b75553 respect db fields in multiple layers of embedded docs 2017-03-05 17:35:37 -05:00
9 changed files with 47 additions and 239 deletions

View File

@@ -5,21 +5,11 @@ Changelog
Development
===========
- (Fill this out as you fix issues and develop your features).
Changes in 0.12.0
=================
- Fixed using sets in field choices #1481
- POTENTIAL BREAKING CHANGE: Fixed limit/skip/hint/batch_size chaining #1476
- POTENTIAL BREAKING CHANGE: Changed a public `QuerySet.clone_into` method to a private `QuerySet._clone_into` #1476
- Fixed connecting to a replica set with PyMongo 2.x #1436
- Fixed using sets in field choices #1481
- Fixed deleting items from a `ListField` #1318
- Fixed an obscure error message when filtering by `field__in=non_iterable`. #1237
- Fixed behavior of a `dec` update operator #1450
- Added a `rename` update operator #1454
- Added validation for the `db_field` parameter #1448
- Fixed the error message displayed when querying an `EmbeddedDocumentField` by an invalid value #1440
- Fixed the error message displayed when validating unicode URLs #1486
- Raise an error when trying to save an abstract document #1449
Changes in 0.11.0
=================

View File

@@ -6,9 +6,6 @@ Development
***********
(Fill this out whenever you introduce breaking changes to MongoEngine)
0.12.0
******
This release includes various fixes for the `BaseQuerySet` methods and how they
are chained together. Since version 0.10.1 applying limit/skip/hint/batch_size
to an already-existing queryset wouldn't modify the underlying PyMongo cursor.

View File

@@ -23,7 +23,7 @@ __all__ = (list(document.__all__) + list(fields.__all__) +
list(signals.__all__) + list(errors.__all__))
VERSION = (0, 12, 0)
VERSION = (0, 11, 0)
def get_version():

View File

@@ -1,4 +1,3 @@
from collections import OrderedDict
from bson import DBRef, SON
import six
@@ -202,10 +201,6 @@ class DeReference(object):
as_tuple = isinstance(items, tuple)
iterator = enumerate(items)
data = []
elif isinstance(items, OrderedDict):
is_list = False
iterator = items.iteritems()
data = OrderedDict()
else:
is_list = False
iterator = items.iteritems()

View File

@@ -5,7 +5,6 @@ import re
import time
import uuid
import warnings
from collections import Mapping
from operator import itemgetter
from bson import Binary, DBRef, ObjectId, SON
@@ -620,14 +619,6 @@ class DynamicField(BaseField):
Used by :class:`~mongoengine.DynamicDocument` to handle dynamic data"""
def __init__(self, container_class=dict, *args, **kwargs):
self._container_cls = container_class
if not issubclass(self._container_cls, Mapping):
self.error('The class that is specified in `container_class` parameter '
'must be a subclass of `dict`.')
super(DynamicField, self).__init__(*args, **kwargs)
def to_mongo(self, value, use_db_field=True, fields=None):
"""Convert a Python type to a MongoDB compatible type.
"""
@@ -653,7 +644,7 @@ class DynamicField(BaseField):
is_list = True
value = {k: v for k, v in enumerate(value)}
data = self._container_cls()
data = {}
for k, v in value.iteritems():
data[k] = self.to_mongo(v, use_db_field, fields)
@@ -1007,8 +998,8 @@ class ReferenceField(BaseField):
def validate(self, value):
if not isinstance(value, (self.document_type, DBRef, ObjectId)):
self.error('A ReferenceField only accepts DBRef, ObjectId or documents')
if not isinstance(value, (self.document_type, DBRef)):
self.error('A ReferenceField only accepts DBRef or documents')
if isinstance(value, Document) and value.id is None:
self.error('You can only reference documents once they have been '

View File

@@ -158,49 +158,44 @@ class BaseQuerySet(object):
# self._cursor
def __getitem__(self, key):
"""Return a document instance corresponding to a given index if
the key is an integer. If the key is a slice, translate its
bounds into a skip and a limit, and return a cloned queryset
with that skip/limit applied. For example:
>>> User.objects[0]
<User: User object>
>>> User.objects[1:3]
[<User: User object>, <User: User object>]
"""
"""Support skip and limit using getitem and slicing syntax."""
queryset = self.clone()
# Handle a slice
# Slice provided
if isinstance(key, slice):
queryset._cursor_obj = queryset._cursor[key]
queryset._skip, queryset._limit = key.start, key.stop
if key.start and key.stop:
queryset._limit = key.stop - key.start
try:
queryset._cursor_obj = queryset._cursor[key]
queryset._skip, queryset._limit = key.start, key.stop
if key.start and key.stop:
queryset._limit = key.stop - key.start
except IndexError as err:
# PyMongo raises an error if key.start == key.stop, catch it,
# bin it, kill it.
start = key.start or 0
if start >= 0 and key.stop >= 0 and key.step is None:
if start == key.stop:
queryset.limit(0)
queryset._skip = key.start
queryset._limit = key.stop - start
return queryset
raise err
# Allow further QuerySet modifications to be performed
return queryset
# Handle an index
# Integer index provided
elif isinstance(key, int):
if queryset._scalar:
return queryset._get_scalar(
queryset._document._from_son(
queryset._cursor[key],
_auto_dereference=self._auto_dereference,
only_fields=self.only_fields
)
)
queryset._document._from_son(queryset._cursor[key],
_auto_dereference=self._auto_dereference,
only_fields=self.only_fields))
if queryset._as_pymongo:
return queryset._get_as_pymongo(queryset._cursor[key])
return queryset._document._from_son(queryset._cursor[key],
_auto_dereference=self._auto_dereference,
only_fields=self.only_fields)
return queryset._document._from_son(
queryset._cursor[key],
_auto_dereference=self._auto_dereference,
only_fields=self.only_fields
)
raise AttributeError('Provide a slice or an integer index')
raise AttributeError
def __iter__(self):
raise NotImplementedError

View File

@@ -5,10 +5,8 @@ import uuid
import math
import itertools
import re
import pymongo
from nose.plugins.skip import SkipTest
from collections import OrderedDict
import six
try:
@@ -27,12 +25,9 @@ except ImportError:
from mongoengine import *
from mongoengine.connection import get_db
from mongoengine.base import (BaseDict, BaseField, EmbeddedDocumentList,
_document_registry, TopLevelDocumentMetaclass)
_document_registry)
from tests.utils import MongoDBTestCase, MONGO_TEST_DB
from mongoengine.python_support import IS_PYMONGO_3
if IS_PYMONGO_3:
from bson import CodecOptions
from tests.utils import MongoDBTestCase
__all__ = ("FieldTest", "EmbeddedDocumentListFieldTestCase")
@@ -1178,14 +1173,6 @@ class FieldTest(MongoDBTestCase):
post.reload()
self.assertEqual(post.info, ['0', '1', '2', '3', 'a', '5'])
# __setitem__(index, value) with a negative index
reset_post()
post.info[-2] = 'a'
self.assertEqual(post.info, ['0', '1', '2', '3', 'a', '5'])
post.save()
post.reload()
self.assertEqual(post.info, ['0', '1', '2', '3', 'a', '5'])
# '__setitem__(slice(i, j), listB)'
# aka 'listA[i:j] = listB'
# aka 'setitem(listA, slice(i, j), listB)'
@@ -1196,16 +1183,6 @@ class FieldTest(MongoDBTestCase):
post.reload()
self.assertEqual(post.info, ['0', 'h', 'e', 'l', 'l', 'o', '3', '4', '5'])
# '__setitem__(slice(i, j), listB)' with negative i and j
reset_post()
post.info[-5:-3] = ['h', 'e', 'l', 'l', 'o']
self.assertEqual(post.info, ['0', 'h', 'e', 'l', 'l', 'o', '3', '4', '5'])
post.save()
post.reload()
self.assertEqual(post.info, ['0', 'h', 'e', 'l', 'l', 'o', '3', '4', '5'])
# negative
# 'append'
reset_post()
post.info.append('h')
@@ -2093,12 +2070,6 @@ class FieldTest(MongoDBTestCase):
post1.author = post2
self.assertRaises(ValidationError, post1.validate)
# Ensure ObjectID's are accepted as references
user_object_id = user.pk
post3 = BlogPost(content="Chips and curry sauce taste good.")
post3.author = user_object_id
post3.save()
# Make sure referencing a saved document of the right type works
user.save()
post1.author = user
@@ -2109,20 +2080,6 @@ class FieldTest(MongoDBTestCase):
post1.author = post2
self.assertRaises(ValidationError, post1.validate)
def test_objectid_reference_fields(self):
"""Make sure storing Object ID references works."""
class Person(Document):
name = StringField()
parent = ReferenceField('self')
Person.drop_collection()
p1 = Person(name="John").save()
Person(name="Ross", parent=p1.pk).save()
p = Person.objects.get(name="Ross")
self.assertEqual(p.parent, p1)
def test_dbref_reference_fields(self):
"""Make sure storing references as bson.dbref.DBRef works."""
class Person(Document):
@@ -4115,67 +4072,6 @@ class EmbeddedDocumentListFieldTestCase(MongoDBTestCase):
self.assertTrue(hasattr(CustomData.c_field, 'custom_data'))
self.assertEqual(custom_data['a'], CustomData.c_field.custom_data['a'])
def test_dynamicfield_with_container_class(self):
"""
Tests that object can be stored in order by DynamicField class
with container_class parameter.
"""
raw_data = [('d', 1), ('c', 2), ('b', 3), ('a', 4)]
class Doc(Document):
ordered_data = DynamicField(container_class=OrderedDict)
unordered_data = DynamicField()
Doc.drop_collection()
doc = Doc(ordered_data=OrderedDict(raw_data), unordered_data=dict(raw_data)).save()
# checks that the data is in order
self.assertEqual(type(doc.ordered_data), OrderedDict)
self.assertEqual(type(doc.unordered_data), dict)
self.assertEqual(','.join(doc.ordered_data.keys()), 'd,c,b,a')
# checks that the data is stored to the database in order
pymongo_db = pymongo.MongoClient()[MONGO_TEST_DB]
if IS_PYMONGO_3:
codec_option = CodecOptions(document_class=OrderedDict)
db_doc = pymongo_db.doc.with_options(codec_options=codec_option).find_one()
else:
db_doc = pymongo_db.doc.find_one(as_class=OrderedDict)
self.assertEqual(','.join(doc.ordered_data.keys()), 'd,c,b,a')
def test_dynamicfield_with_wrong_container_class(self):
with self.assertRaises(ValidationError):
class DocWithInvalidField:
data = DynamicField(container_class=list)
def test_dynamicfield_with_wrong_container_class_and_reload_docuemnt(self):
# This is because 'codec_options' is supported on pymongo3 or later
if IS_PYMONGO_3:
class OrderedDocument(Document):
my_metaclass = TopLevelDocumentMetaclass
__metaclass__ = TopLevelDocumentMetaclass
@classmethod
def _get_collection(cls):
collection = super(OrderedDocument, cls)._get_collection()
opts = CodecOptions(document_class=OrderedDict)
return collection.with_options(codec_options=opts)
raw_data = [('d', 1), ('c', 2), ('b', 3), ('a', 4)]
class Doc(OrderedDocument):
data = DynamicField(container_class=OrderedDict)
Doc.drop_collection()
doc = Doc(data=OrderedDict(raw_data)).save()
doc.reload()
self.assertEqual(type(doc.data), OrderedDict)
self.assertEqual(','.join(doc.data.keys()), 'd,c,b,a')
class CachedReferenceFieldTest(MongoDBTestCase):

View File

@@ -4962,6 +4962,20 @@ class QuerySetTest(unittest.TestCase):
for p in Person.objects():
self.assertEqual(p.name, 'a')
def test_last_field_name_like_operator(self):
class EmbeddedItem(EmbeddedDocument):
type = StringField()
class Doc(Document):
item = EmbeddedDocumentField(EmbeddedItem)
Doc.drop_collection()
doc = Doc(item=EmbeddedItem(type="axe"))
doc.save()
self.assertEqual(1, Doc.objects(item__type__="axe").count())
def test_len_during_iteration(self):
"""Tests that calling len on a queyset during iteration doesn't
stop paging.

View File

@@ -2,15 +2,10 @@
import unittest
from bson import DBRef, ObjectId
from collections import OrderedDict
from mongoengine import *
from mongoengine.connection import get_db
from mongoengine.context_managers import query_counter
from mongoengine.python_support import IS_PYMONGO_3
from mongoengine.base import TopLevelDocumentMetaclass
if IS_PYMONGO_3:
from bson import CodecOptions
class FieldTest(unittest.TestCase):
@@ -1292,70 +1287,5 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 2)
def test_dynamic_field_dereference(self):
class Merchandise(Document):
name = StringField()
price = IntField()
class Store(Document):
merchandises = DynamicField()
Merchandise.drop_collection()
Store.drop_collection()
merchandises = {
'#1': Merchandise(name='foo', price=100).save(),
'#2': Merchandise(name='bar', price=120).save(),
'#3': Merchandise(name='baz', price=110).save(),
}
Store(merchandises=merchandises).save()
store = Store.objects().first()
for obj in store.merchandises.values():
self.assertFalse(isinstance(obj, Merchandise))
store.select_related()
for obj in store.merchandises.values():
self.assertTrue(isinstance(obj, Merchandise))
def test_dynamic_field_dereference_with_ordering_guarantee_on_pymongo3(self):
# This is because 'codec_options' is supported on pymongo3 or later
if IS_PYMONGO_3:
class OrderedDocument(Document):
my_metaclass = TopLevelDocumentMetaclass
__metaclass__ = TopLevelDocumentMetaclass
@classmethod
def _get_collection(cls):
collection = super(OrderedDocument, cls)._get_collection()
opts = CodecOptions(document_class=OrderedDict)
return collection.with_options(codec_options=opts)
class Merchandise(Document):
name = StringField()
price = IntField()
class Store(OrderedDocument):
merchandises = DynamicField(container_class=OrderedDict)
Merchandise.drop_collection()
Store.drop_collection()
merchandises = OrderedDict()
merchandises['#1'] = Merchandise(name='foo', price=100).save()
merchandises['#2'] = Merchandise(name='bar', price=120).save()
merchandises['#3'] = Merchandise(name='baz', price=110).save()
Store(merchandises=merchandises).save()
store = Store.objects().first()
store.select_related()
# confirms that the load data order is same with the one at storing
self.assertTrue(type(store.merchandises), OrderedDict)
self.assertEqual(','.join(store.merchandises.keys()), '#1,#2,#3')
if __name__ == '__main__':
unittest.main()