Merge branch 'master' of github.com:MongoEngine/mongoengine into remove_pymongo2_support_dead_code

This commit is contained in:
Bastien Gérard 2019-05-23 21:06:15 +02:00
commit 6a843cc8b2
24 changed files with 306 additions and 67 deletions

View File

@ -25,8 +25,14 @@ elif [ "$MONGODB" = "3.4" ]; then
sudo apt-get update
sudo apt-get install mongodb-org-server=3.4.17
# service should be started automatically
elif [ "$MONGODB" = "3.6" ]; then
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2930ADAE8CAF5059EE73BB4B58712A2291FA4AD5
echo "deb http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.6 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.6.list
sudo apt-get update
sudo apt-get install mongodb-org-server=3.6.12
# service should be started automatically
else
echo "Invalid MongoDB version, expected 2.6, 3.0, 3.2 or 3.4."
echo "Invalid MongoDB version, expected 2.6, 3.0, 3.2, 3.4 or 3.6."
exit 1
fi;

View File

@ -35,6 +35,8 @@ matrix:
env: MONGODB=3.2 PYMONGO=3.x
- python: 3.6
env: MONGODB=3.4 PYMONGO=3.x
- python: 3.6
env: MONGODB=3.6 PYMONGO=3.x
before_install:
- bash .install_mongodb_on_travis.sh

View File

@ -26,10 +26,10 @@ an `API reference <https://mongoengine-odm.readthedocs.io/apireference.html>`_.
Supported MongoDB Versions
==========================
MongoEngine is currently tested against MongoDB v2.6, v3.0, v3.2 and v3.4. Future
MongoEngine is currently tested against MongoDB v2.6, v3.0, v3.2, v3.4 and v3.6. Future
versions should be supported as well, but aren't actively tested at the moment.
Make sure to open an issue or submit a pull request if you experience any
problems with MongoDB v3.4+.
problems with MongoDB v3.6+.
Installation
============

View File

@ -4,6 +4,7 @@ Changelog
Development
===========
- Add support for MongoDB 3.6 and Python3.7 in travis
- Fix querying on List(EmbeddedDocument) subclasses fields #1961 #1492
- Fix querying on (Generic)EmbeddedDocument subclasses fields #475
- expose `mongoengine.connection.disconnect` and `mongoengine.connection.disconnect_all`

View File

@ -184,9 +184,6 @@ class DocumentMetaclass(type):
if issubclass(new_class, EmbeddedDocument):
raise InvalidDocumentError('CachedReferenceFields is not '
'allowed in EmbeddedDocuments')
if not f.document_type:
raise InvalidDocumentError(
'Document is not available to sync')
if f.auto_sync:
f.start_listener()

View File

@ -31,7 +31,6 @@ def _import_class(cls_name):
field_classes = _field_list_cache
queryset_classes = ('OperationError',)
deref_classes = ('DeReference',)
if cls_name == 'BaseDocument':
@ -43,14 +42,11 @@ def _import_class(cls_name):
elif cls_name in field_classes:
from mongoengine import fields as module
import_classes = field_classes
elif cls_name in queryset_classes:
from mongoengine import queryset as module
import_classes = queryset_classes
elif cls_name in deref_classes:
from mongoengine import dereference as module
import_classes = deref_classes
else:
raise ValueError('No import set for: ' % cls_name)
raise ValueError('No import set for: %s' % cls_name)
for cls in import_classes:
_class_registry_cache[cls] = getattr(module, cls)

View File

@ -110,9 +110,6 @@ class ValidationError(AssertionError):
def build_dict(source):
errors_dict = {}
if not source:
return errors_dict
if isinstance(source, dict):
for field_name, error in iteritems(source):
errors_dict[field_name] = build_dict(error)

View File

@ -152,12 +152,10 @@ class URLField(StringField):
scheme = value.split('://')[0].lower()
if scheme not in self.schemes:
self.error(u'Invalid scheme {} in URL: {}'.format(scheme, value))
return
# Then check full URL
if not self.url_regex.match(value):
self.error(u'Invalid URL: {}'.format(value))
return
class EmailField(StringField):
@ -259,10 +257,10 @@ class EmailField(StringField):
try:
domain_part = domain_part.encode('idna').decode('ascii')
except UnicodeError:
self.error(self.error_msg % value)
self.error("%s %s" % (self.error_msg % value, "(domain failed IDN encoding)"))
else:
if not self.validate_domain_part(domain_part):
self.error(self.error_msg % value)
self.error("%s %s" % (self.error_msg % value, "(domain validation failed)"))
class IntField(BaseField):

View File

@ -6,6 +6,7 @@ from mongoengine.connection import get_connection
# Constant that can be used to compare the version retrieved with
# get_mongodb_version()
MONGODB_36 = (3, 6)
MONGODB_34 = (3, 4)
MONGODB_32 = (3, 2)
MONGODB_3 = (3, 0)

View File

@ -194,7 +194,7 @@ class BaseQuerySet(object):
only_fields=self.only_fields
)
raise AttributeError('Provide a slice or an integer index')
raise TypeError('Provide a slice or an integer index')
def __iter__(self):
raise NotImplementedError

View File

@ -86,14 +86,6 @@ def query(_doc_cls=None, **kwargs):
singular_ops = [None, 'ne', 'gt', 'gte', 'lt', 'lte', 'not']
singular_ops += STRING_OPERATORS
if op in singular_ops:
if isinstance(field, six.string_types):
if (op in STRING_OPERATORS and
isinstance(value, six.string_types)):
StringField = _import_class('StringField')
value = StringField.prepare_query_value(op, value)
else:
value = field
else:
value = field.prepare_query_value(op, value)
if isinstance(field, CachedReferenceField) and value:
@ -277,7 +269,7 @@ def update(_doc_cls=None, **update):
if op == 'pull':
if field.required or value is not None:
if match == 'in' and not isinstance(value, dict):
if match in ('in', 'nin') and not isinstance(value, dict):
value = _prepare_query_for_iterable(field, op, value)
else:
value = field.prepare_query_value(op, value)
@ -304,10 +296,6 @@ def update(_doc_cls=None, **update):
key = '.'.join(parts)
if not op:
raise InvalidQueryError('Updates must supply an operation '
'eg: set__FIELD=value')
if 'pull' in op and '.' in key:
# Dot operators don't work on pull operations
# unless they point to a list field

View File

@ -593,8 +593,9 @@ class IndexesTest(unittest.TestCase):
# Two posts with the same slug is not allowed
post2 = BlogPost(title='test2', slug='test')
self.assertRaises(NotUniqueError, post2.save)
self.assertRaises(NotUniqueError, BlogPost.objects.insert, post2)
# Ensure backwards compatibilty for errors
# Ensure backwards compatibility for errors
self.assertRaises(OperationError, post2.save)
@requires_mongodb_gte_34
@ -826,6 +827,18 @@ class IndexesTest(unittest.TestCase):
self.assertEqual(3600,
info['created_1']['expireAfterSeconds'])
def test_index_drop_dups_silently_ignored(self):
class Customer(Document):
cust_id = IntField(unique=True, required=True)
meta = {
'indexes': ['cust_id'],
'index_drop_dups': True,
'allow_inheritance': False,
}
Customer.drop_collection()
Customer.objects.first()
def test_unique_and_indexes(self):
"""Ensure that 'unique' constraints aren't overridden by
meta.indexes.
@ -842,11 +855,16 @@ class IndexesTest(unittest.TestCase):
cust.save()
cust_dupe = Customer(cust_id=1)
try:
with self.assertRaises(NotUniqueError):
cust_dupe.save()
raise AssertionError("We saved a dupe!")
except NotUniqueError:
pass
cust = Customer(cust_id=2)
cust.save()
# duplicate key on update
with self.assertRaises(NotUniqueError):
cust.cust_id = 1
cust.save()
def test_primary_save_duplicate_update_existing_object(self):
"""If you set a field as primary, then unexpected behaviour can occur.

View File

@ -420,6 +420,12 @@ class InstanceTest(MongoDBTestCase):
person.save()
person.to_dbref()
def test_key_like_attribute_access(self):
person = self.Person(age=30)
self.assertEqual(person['age'], 30)
with self.assertRaises(KeyError):
person['unknown_attr']
def test_save_abstract_document(self):
"""Saving an abstract document should fail."""
class Doc(Document):

View File

@ -40,6 +40,11 @@ class GeoFieldTest(unittest.TestCase):
expected = "Both values (%s) in point must be float or int" % repr(coord)
self._test_for_expected_error(Location, coord, expected)
invalid_coords = [21, 4, 'a']
for coord in invalid_coords:
expected = "GeoPointField can only accept tuples or lists of (x, y)"
self._test_for_expected_error(Location, coord, expected)
def test_point_validation(self):
class Location(Document):
loc = PointField()

View File

@ -208,10 +208,7 @@ class TestCachedReferenceField(MongoDBTestCase):
('pj', "PJ")
)
name = StringField()
tp = StringField(
choices=TYPES
)
tp = StringField(choices=TYPES)
father = CachedReferenceField('self', fields=('tp',))
Person.drop_collection()
@ -222,6 +219,9 @@ class TestCachedReferenceField(MongoDBTestCase):
a2 = Person(name='Wilson Junior', tp='pf', father=a1)
a2.save()
a2 = Person.objects.with_id(a2.id)
self.assertEqual(a2.father.tp, a1.tp)
self.assertEqual(dict(a2.to_mongo()), {
"_id": a2.pk,
"name": u"Wilson Junior",
@ -374,6 +374,9 @@ class TestCachedReferenceField(MongoDBTestCase):
self.assertEqual(o.to_mongo()['animal']['tag'], 'heavy')
self.assertEqual(o.to_mongo()['animal']['owner']['t'], 'u')
# Check to_mongo with fields
self.assertNotIn('animal', o.to_mongo(fields=['person']))
# counts
Ocorrence(person="teste 2").save()
Ocorrence(person="teste 3").save()

View File

@ -172,6 +172,9 @@ class TestDateTimeField(MongoDBTestCase):
log.time = datetime.datetime.now().isoformat(' ')
log.validate()
log.time = '2019-05-16 21:42:57.897847'
log.validate()
if dateutil:
log.time = datetime.datetime.now().isoformat('T')
log.validate()
@ -180,6 +183,12 @@ class TestDateTimeField(MongoDBTestCase):
self.assertRaises(ValidationError, log.validate)
log.time = 'ABC'
self.assertRaises(ValidationError, log.validate)
log.time = '2019-05-16 21:GARBAGE:12'
self.assertRaises(ValidationError, log.validate)
log.time = '2019-05-16 21:42:57.GARBAGE'
self.assertRaises(ValidationError, log.validate)
log.time = '2019-05-16 21:42:57.123.456'
self.assertRaises(ValidationError, log.validate)
class TestDateTimeTzAware(MongoDBTestCase):

View File

@ -75,6 +75,16 @@ class TestEmailField(MongoDBTestCase):
user = User(email='me@localhost')
user.validate()
def test_email_domain_validation_fails_if_invalid_idn(self):
class User(Document):
email = EmailField()
invalid_idn = '.google.com'
user = User(email='me@%s' % invalid_idn)
with self.assertRaises(ValidationError) as ctx_err:
user.validate()
self.assertIn("domain failed IDN encoding", str(ctx_err.exception))
def test_email_field_ip_domain(self):
class User(Document):
email = EmailField()

View File

@ -13,6 +13,35 @@ class TestLazyReferenceField(MongoDBTestCase):
# with a document class name.
self.assertRaises(ValidationError, LazyReferenceField, EmbeddedDocument)
def test___repr__(self):
class Animal(Document):
pass
class Ocurrence(Document):
animal = LazyReferenceField(Animal)
Animal.drop_collection()
Ocurrence.drop_collection()
animal = Animal()
oc = Ocurrence(animal=animal)
self.assertIn('LazyReference', repr(oc.animal))
def test___getattr___unknown_attr_raises_attribute_error(self):
class Animal(Document):
pass
class Ocurrence(Document):
animal = LazyReferenceField(Animal)
Animal.drop_collection()
Ocurrence.drop_collection()
animal = Animal().save()
oc = Ocurrence(animal=animal)
with self.assertRaises(AttributeError):
oc.animal.not_exist
def test_lazy_reference_simple(self):
class Animal(Document):
name = StringField()
@ -479,6 +508,23 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
p = Ocurrence.objects.get()
self.assertIs(p.animal, None)
def test_generic_lazy_reference_accepts_string_instead_of_class(self):
class Animal(Document):
name = StringField()
tag = StringField()
class Ocurrence(Document):
person = StringField()
animal = GenericLazyReferenceField('Animal')
Animal.drop_collection()
Ocurrence.drop_collection()
animal = Animal().save()
Ocurrence(animal=animal).save()
p = Ocurrence.objects.get()
self.assertEqual(p.animal, animal)
def test_generic_lazy_reference_embedded(self):
class Animal(Document):
name = StringField()

View File

@ -39,9 +39,9 @@ class TestLongField(MongoDBTestCase):
doc.value = -1
self.assertRaises(ValidationError, doc.validate)
doc.age = 120
doc.value = 120
self.assertRaises(ValidationError, doc.validate)
doc.age = 'ten'
doc.value = 'ten'
self.assertRaises(ValidationError, doc.validate)
def test_long_ne_operator(self):

View File

@ -6,7 +6,6 @@ import uuid
from decimal import Decimal
from bson import DBRef, ObjectId
from nose.plugins.skip import SkipTest
import pymongo
from pymongo.errors import ConfigurationError
from pymongo.read_preferences import ReadPreference
@ -18,7 +17,7 @@ from mongoengine import *
from mongoengine.connection import get_connection, get_db
from mongoengine.context_managers import query_counter, switch_db
from mongoengine.errors import InvalidQueryError
from mongoengine.mongodb_support import get_mongodb_version, MONGODB_32
from mongoengine.mongodb_support import get_mongodb_version, MONGODB_32, MONGODB_36
from mongoengine.queryset import (DoesNotExist, MultipleObjectsReturned,
QuerySet, QuerySetManager, queryset_manager)
from tests.utils import requires_mongodb_gte_26
@ -32,6 +31,12 @@ class db_ops_tracker(query_counter):
return list(self.db.system.profile.find(ignore_query))
def get_key_compat(mongo_ver):
ORDER_BY_KEY = 'sort' if mongo_ver >= MONGODB_32 else '$orderby'
CMD_QUERY_KEY = 'command' if mongo_ver >= MONGODB_36 else 'query'
return ORDER_BY_KEY, CMD_QUERY_KEY
class QuerySetTest(unittest.TestCase):
def setUp(self):
@ -157,6 +162,11 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(person.name, 'User B')
self.assertEqual(person.age, None)
def test___getitem___invalid_index(self):
"""Ensure slicing a queryset works as expected."""
with self.assertRaises(TypeError):
self.Person.objects()['a']
def test_slice(self):
"""Ensure slicing a queryset works as expected."""
user_a = self.Person.objects.create(name='User A', age=20)
@ -985,6 +995,29 @@ class QuerySetTest(unittest.TestCase):
inserted_comment_id = Comment.objects.insert(comment, load_bulk=False)
self.assertEqual(comment.id, inserted_comment_id)
def test_bulk_insert_accepts_doc_with_ids(self):
class Comment(Document):
id = IntField(primary_key=True)
Comment.drop_collection()
com1 = Comment(id=0)
com2 = Comment(id=1)
Comment.objects.insert([com1, com2])
def test_insert_raise_if_duplicate_in_constraint(self):
class Comment(Document):
id = IntField(primary_key=True)
Comment.drop_collection()
com1 = Comment(id=0)
Comment.objects.insert(com1)
with self.assertRaises(NotUniqueError):
Comment.objects.insert(com1)
def test_get_changed_fields_query_count(self):
"""Make sure we don't perform unnecessary db operations when
none of document's fields were updated.
@ -1280,8 +1313,7 @@ class QuerySetTest(unittest.TestCase):
"""Ensure that the default ordering can be cleared by calling
order_by() w/o any arguments.
"""
MONGO_VER = self.mongodb_version
ORDER_BY_KEY = 'sort' if MONGO_VER >= MONGODB_32 else '$orderby'
ORDER_BY_KEY, CMD_QUERY_KEY = get_key_compat(self.mongodb_version)
class BlogPost(Document):
title = StringField()
@ -1298,7 +1330,7 @@ class QuerySetTest(unittest.TestCase):
BlogPost.objects.filter(title='whatever').first()
self.assertEqual(len(q.get_ops()), 1)
self.assertEqual(
q.get_ops()[0]['query'][ORDER_BY_KEY],
q.get_ops()[0][CMD_QUERY_KEY][ORDER_BY_KEY],
{'published_date': -1}
)
@ -1306,14 +1338,14 @@ class QuerySetTest(unittest.TestCase):
with db_ops_tracker() as q:
BlogPost.objects.filter(title='whatever').order_by().first()
self.assertEqual(len(q.get_ops()), 1)
self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0]['query'])
self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0][CMD_QUERY_KEY])
# calling an explicit order_by should use a specified sort
with db_ops_tracker() as q:
BlogPost.objects.filter(title='whatever').order_by('published_date').first()
self.assertEqual(len(q.get_ops()), 1)
self.assertEqual(
q.get_ops()[0]['query'][ORDER_BY_KEY],
q.get_ops()[0][CMD_QUERY_KEY][ORDER_BY_KEY],
{'published_date': 1}
)
@ -1322,13 +1354,12 @@ class QuerySetTest(unittest.TestCase):
qs = BlogPost.objects.filter(title='whatever').order_by('published_date')
qs.order_by().first()
self.assertEqual(len(q.get_ops()), 1)
self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0]['query'])
self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0][CMD_QUERY_KEY])
def test_no_ordering_for_get(self):
""" Ensure that Doc.objects.get doesn't use any ordering.
"""
MONGO_VER = self.mongodb_version
ORDER_BY_KEY = 'sort' if MONGO_VER == MONGODB_32 else '$orderby'
ORDER_BY_KEY, CMD_QUERY_KEY = get_key_compat(self.mongodb_version)
class BlogPost(Document):
title = StringField()
@ -1344,13 +1375,13 @@ class QuerySetTest(unittest.TestCase):
with db_ops_tracker() as q:
BlogPost.objects.get(title='whatever')
self.assertEqual(len(q.get_ops()), 1)
self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0]['query'])
self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0][CMD_QUERY_KEY])
# Ordering should be ignored for .get even if we set it explicitly
with db_ops_tracker() as q:
BlogPost.objects.order_by('-title').get(title='whatever')
self.assertEqual(len(q.get_ops()), 1)
self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0]['query'])
self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0][CMD_QUERY_KEY])
def test_find_embedded(self):
"""Ensure that an embedded document is properly returned from
@ -2150,6 +2181,40 @@ class QuerySetTest(unittest.TestCase):
Site.objects(id=s.id).update_one(
pull_all__collaborators__helpful__name=['Ross'])
def test_pull_from_nested_embedded_using_in_nin(self):
"""Ensure that the 'pull' update operation works on embedded documents using 'in' and 'nin' operators.
"""
class User(EmbeddedDocument):
name = StringField()
def __unicode__(self):
return '%s' % self.name
class Collaborator(EmbeddedDocument):
helpful = ListField(EmbeddedDocumentField(User))
unhelpful = ListField(EmbeddedDocumentField(User))
class Site(Document):
name = StringField(max_length=75, unique=True, required=True)
collaborators = EmbeddedDocumentField(Collaborator)
Site.drop_collection()
a = User(name='Esteban')
b = User(name='Frank')
x = User(name='Harry')
y = User(name='John')
s = Site(name="test", collaborators=Collaborator(
helpful=[a, b], unhelpful=[x, y])).save()
Site.objects(id=s.id).update_one(pull__collaborators__helpful__name__in=['Esteban']) # Pull a
self.assertEqual(Site.objects.first().collaborators['helpful'], [b])
Site.objects(id=s.id).update_one(pull__collaborators__unhelpful__name__nin=['John']) # Pull x
self.assertEqual(Site.objects.first().collaborators['unhelpful'], [y])
def test_pull_from_nested_mapfield(self):
class Collaborator(EmbeddedDocument):
@ -2489,6 +2554,7 @@ class QuerySetTest(unittest.TestCase):
def test_comment(self):
"""Make sure adding a comment to the query gets added to the query"""
MONGO_VER = self.mongodb_version
_, CMD_QUERY_KEY = get_key_compat(MONGO_VER)
QUERY_KEY = 'filter' if MONGO_VER >= MONGODB_32 else '$query'
COMMENT_KEY = 'comment' if MONGO_VER >= MONGODB_32 else '$comment'
@ -2507,8 +2573,8 @@ class QuerySetTest(unittest.TestCase):
ops = q.get_ops()
self.assertEqual(len(ops), 2)
for op in ops:
self.assertEqual(op['query'][QUERY_KEY], {'age': {'$gte': 18}})
self.assertEqual(op['query'][COMMENT_KEY], 'looking for an adult')
self.assertEqual(op[CMD_QUERY_KEY][QUERY_KEY], {'age': {'$gte': 18}})
self.assertEqual(op[CMD_QUERY_KEY][COMMENT_KEY], 'looking for an adult')
def test_map_reduce(self):
"""Ensure map/reduce is both mapping and reducing.
@ -3524,6 +3590,11 @@ class QuerySetTest(unittest.TestCase):
opts = {"deleted": False}
return qryset(**opts)
@queryset_manager
def objects_1_arg(qryset):
opts = {"deleted": False}
return qryset(**opts)
@queryset_manager
def music_posts(doc_cls, queryset, deleted=False):
return queryset(tags='music',
@ -3538,6 +3609,8 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual([p.id for p in BlogPost.objects()],
[post1.id, post2.id, post3.id])
self.assertEqual([p.id for p in BlogPost.objects_1_arg()],
[post1.id, post2.id, post3.id])
self.assertEqual([p.id for p in BlogPost.music_posts()],
[post1.id, post2.id])
@ -4914,6 +4987,38 @@ class QuerySetTest(unittest.TestCase):
people.count()
self.assertEqual(q, 3)
def test_no_cached_queryset__repr__(self):
class Person(Document):
name = StringField()
Person.drop_collection()
qs = Person.objects.no_cache()
self.assertEqual(repr(qs), '[]')
def test_no_cached_on_a_cached_queryset_raise_error(self):
class Person(Document):
name = StringField()
Person.drop_collection()
Person(name='a').save()
qs = Person.objects()
_ = list(qs)
with self.assertRaises(OperationError) as ctx_err:
qs.no_cache()
self.assertEqual("QuerySet already cached", str(ctx_err.exception))
def test_no_cached_queryset_no_cache_back_to_cache(self):
class Person(Document):
name = StringField()
Person.drop_collection()
qs = Person.objects()
self.assertIsInstance(qs, QuerySet)
qs = qs.no_cache()
self.assertIsInstance(qs, QuerySetNoCache)
qs = qs.cache()
self.assertIsInstance(qs, QuerySet)
def test_cache_not_cloned(self):
class User(Document):
@ -5186,8 +5291,7 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(op['nreturned'], 1)
def test_bool_with_ordering(self):
MONGO_VER = self.mongodb_version
ORDER_BY_KEY = 'sort' if MONGO_VER >= MONGODB_32 else '$orderby'
ORDER_BY_KEY, CMD_QUERY_KEY = get_key_compat(self.mongodb_version)
class Person(Document):
name = StringField()
@ -5206,21 +5310,22 @@ class QuerySetTest(unittest.TestCase):
op = q.db.system.profile.find({"ns":
{"$ne": "%s.system.indexes" % q.db.name}})[0]
self.assertNotIn(ORDER_BY_KEY, op['query'])
self.assertNotIn(ORDER_BY_KEY, op[CMD_QUERY_KEY])
# Check that normal query uses orderby
qs2 = Person.objects.order_by('name')
with query_counter() as p:
with query_counter() as q:
for x in qs2:
pass
op = p.db.system.profile.find({"ns":
op = q.db.system.profile.find({"ns":
{"$ne": "%s.system.indexes" % q.db.name}})[0]
self.assertIn(ORDER_BY_KEY, op['query'])
self.assertIn(ORDER_BY_KEY, op[CMD_QUERY_KEY])
def test_bool_with_ordering_from_meta_dict(self):
ORDER_BY_KEY, CMD_QUERY_KEY = get_key_compat(self.mongodb_version)
class Person(Document):
name = StringField()
@ -5242,7 +5347,7 @@ class QuerySetTest(unittest.TestCase):
op = q.db.system.profile.find({"ns":
{"$ne": "%s.system.indexes" % q.db.name}})[0]
self.assertNotIn('$orderby', op['query'],
self.assertNotIn('$orderby', op[CMD_QUERY_KEY],
'BaseQuerySet must remove orderby from meta in boolen test')
self.assertEqual(Person.objects.first().name, 'A')

View File

@ -71,6 +71,14 @@ class TransformTest(unittest.TestCase):
update = transform.update(BlogPost, push_all__tags=['mongo', 'db'])
self.assertEqual(update, {'$push': {'tags': {'$each': ['mongo', 'db']}}})
def test_transform_update_no_operator_default_to_set(self):
"""Ensure the differences in behvaior between 'push' and 'push_all'"""
class BlogPost(Document):
tags = ListField(StringField())
update = transform.update(BlogPost, tags=['mongo', 'db'])
self.assertEqual(update, {'$set': {'tags': ['mongo', 'db']}})
def test_query_field_name(self):
"""Ensure that the correct field name is used when querying.
"""
@ -283,6 +291,11 @@ class TransformTest(unittest.TestCase):
update = transform.update(MainDoc, pull__content__heading='xyz')
self.assertEqual(update, {'$pull': {'content.heading': 'xyz'}})
update = transform.update(MainDoc, pull__content__text__word__in=['foo', 'bar'])
self.assertEqual(update, {'$pull': {'content.text': {'word': {'$in': ['foo', 'bar']}}}})
update = transform.update(MainDoc, pull__content__text__word__nin=['foo', 'bar'])
self.assertEqual(update, {'$pull': {'content.text': {'word': {'$nin': ['foo', 'bar']}}}})
if __name__ == '__main__':
unittest.main()

15
tests/test_common.py Normal file
View File

@ -0,0 +1,15 @@
import unittest
from mongoengine.common import _import_class
from mongoengine import Document
class TestCommon(unittest.TestCase):
def test__import_class(self):
doc_cls = _import_class("Document")
self.assertIs(doc_cls, Document)
def test__import_class_raise_if_not_known(self):
with self.assertRaises(ValueError):
_import_class("UnknownClass")

View File

@ -270,6 +270,14 @@ class ContextManagersTest(unittest.TestCase):
counter += 1
self.assertEqual(q, counter)
self.assertEqual(int(q), counter) # test __int__
self.assertEqual(repr(q), str(int(q))) # test __repr__
self.assertGreater(q, -1) # test __gt__
self.assertGreaterEqual(q, int(q)) # test __gte__
self.assertNotEqual(q, -1)
self.assertLess(q, 1000)
self.assertLessEqual(q, int(q))
def test_query_counter_counts_getmore_queries(self):
connect('mongoenginetest')
db = get_db()

View File

@ -1,4 +1,5 @@
import unittest
from six import iterkeys
from mongoengine import Document
from mongoengine.base.datastructures import StrictDict, BaseList, BaseDict
@ -368,6 +369,20 @@ class TestStrictDict(unittest.TestCase):
d = self.dtype(a=1, b=1, c=1)
self.assertEqual((d.a, d.b, d.c), (1, 1, 1))
def test_iterkeys(self):
d = self.dtype(a=1)
self.assertEqual(list(iterkeys(d)), ['a'])
def test_len(self):
d = self.dtype(a=1)
self.assertEqual(len(d), 1)
def test_pop(self):
d = self.dtype(a=1)
self.assertIn('a', d)
d.pop('a')
self.assertNotIn('a', d)
def test_repr(self):
d = self.dtype(a=1, b=2, c=3)
self.assertEqual(repr(d), '{"a": 1, "b": 2, "c": 3}')