Compare commits

..

67 Commits

Author SHA1 Message Date
Stefan Wojcik
60571ce1de document breaking change where we dont allow outdated "from mongoengine.base import ErrorClass" imports 2016-12-11 18:02:53 -05:00
Stefan Wojcik
953123b3dc minor compat tweak 2016-12-11 17:25:37 -05:00
Stefan Wojcik
fdc1d94f47 slightly simpler condition in _clear_changed_fields 2016-12-11 17:22:38 -05:00
Stefan Wojcik
828d5d6d29 *finally* a working .landscape.yml 2016-12-11 16:38:33 -05:00
Stefan Wojcik
501f6f11ba try another landscape approach 2016-12-11 16:01:31 -05:00
Stefan Wojcik
1199f0d649 another attempt at the right landscape config 2016-12-11 16:00:07 -05:00
Stefan Wojcik
af6601a2e1 update changelog and upgrade docs 2016-12-11 15:56:04 -05:00
Stefan Wojcik
d51788050f Merge branch 'master' of github.com:MongoEngine/mongoengine into improve-health-2 2016-12-11 15:38:29 -05:00
Stefan Wojcik
93d8d97fbd fix .landscape.yml 2016-12-11 15:24:25 -05:00
Stefan Wojcik
dc15195dd8 merge master into improve-health-2 2016-12-11 15:02:51 -05:00
Stefan Wojcik
688ea4f0f2 add long to built-ins in landscape 2016-12-11 15:01:51 -05:00
Stefan Wojcik
a12abe2da4 add xrange as a valid built-in in landscape 2016-12-11 14:56:34 -05:00
Stefan Wojcik
7ffaace4dd update setup.py classifiers 2016-12-11 14:33:53 -05:00
Stefan Wojcik
3ebe3748fa use with self.assertRaises for readability 2016-12-10 22:33:39 -05:00
Stefan Wojcik
a8884391c2 restore cover-package in setup.cfg 2016-12-10 21:04:04 -05:00
Stefan Wojcik
eb903987eb remove a print statement 2016-12-10 20:59:56 -05:00
Stefan Wojcik
b32cd19266 setup.cfg cleanup + only run coveralls on py27 2016-12-10 20:57:43 -05:00
Stefan Wojcik
500b182d17 deprecate explain's format param 2016-12-10 20:09:19 -05:00
Stefan Wojcik
5b70a451c4 fix improper syntax for datetimes 2016-12-10 19:59:15 -05:00
Stefan Wojcik
05fea58d6a remove a print statement 2016-12-10 19:58:41 -05:00
Stefan Wojcik
a9c205bffe remove ridiculous verify_exists option from URLField 2016-12-10 19:56:26 -05:00
Stefan Wojcik
fa9ca2555a remove more python 2.6 code + upgrade coverage + cleaner setup.py 2016-12-10 19:02:07 -05:00
Stefan Wojcik
d89cdff90a remove unused import and dont override built-in "id" 2016-12-10 14:29:48 -05:00
Stefan Wojcik
1e9a120f7e drop unused imports 2016-12-10 14:06:40 -05:00
Stefan Wojcik
94870d7377 merge master into improve-health-2 2016-12-10 13:53:13 -05:00
Stefan Wojcik
30ebe7c11e make the delete rules nicer and safer in BaseQuerySet.delete 2016-12-10 13:50:52 -05:00
Stefan Wojcik
566e8ee801 readd accidentally dropped line in setup.cfg 2016-12-10 13:25:22 -05:00
Stefan Wojcik
5778cb4b51 merge master into improve-health-2 2016-12-10 13:10:19 -05:00
Stefan Wojcik
37c86350f2 drop Python v2.6 support and use dict comprehensions 2016-12-10 12:57:54 -05:00
Stefan Wojcik
cb1eda480b remove outdated migration tests 2016-12-09 16:07:02 -05:00
Stefan Wojcik
e50b23f047 remove xrange and unused variables 2016-12-09 00:08:59 -05:00
Stefan Wojcik
6eb470a821 remove unnecessary usage of the "global" keyword 2016-12-08 23:50:36 -05:00
Stefan Wojcik
756d8b2ac5 remove ridiculous try-finally clause from BaseQuerySet.distinct 2016-12-08 23:44:36 -05:00
Stefan Wojcik
b99985eaf8 slightly cleaner and more performant BaseQuerySet.delete 2016-12-08 23:36:11 -05:00
Stefan Wojcik
4e1145d890 improve documentation regarding allow_inheritance 2016-12-08 23:06:17 -05:00
Stefan Wojcik
5b7b65a750 Prefer ' over " + minor docstring tweaks 2016-12-08 22:44:02 -05:00
Stefan Wojcik
76219901db nicer merge_index_specs (thanks @gukoff!) 2016-12-08 21:49:25 -05:00
Stefan Wojcik
44b86e29c6 more cleanup 2016-12-08 19:27:57 -05:00
Stefan Wojcik
f1f999a570 cleanup + nicer EmbeddedDocumentList.__match_all and __only_matches 2016-12-08 18:59:25 -05:00
Stefan Wojcik
9a32ff4c42 document and slightly simplify BaseDocument._lookup_field 2016-12-08 17:41:40 -05:00
Stefan Wojcik
4b024409ba fix benchmark.py + ignore it in landscape 2016-12-08 16:31:44 -05:00
Stefan Wojcik
b2825119ce remove unnecessary parentheses 2016-12-08 15:36:54 -05:00
Stefan Wojcik
b02904ee75 BREAKING CHANGE rename ConnectionError to MongoEngineConnectionError to avoid conflicts with PY3's built-in ConnectionError 2016-12-08 15:18:17 -05:00
Stefan Wojcik
c86155e571 cleaner connection code 2016-12-08 15:10:10 -05:00
Stefan Wojcik
fa6949eca2 no need to redefine PY3 - six already has it 2016-12-08 12:53:01 -05:00
Stefan Wojcik
18a91cc794 drop an unnecessary ALLOW_INHERITANCE 2016-12-08 11:22:51 -05:00
Stefan Wojcik
ae777e45b2 better comment about overriding allow_inheritance 2016-12-08 11:15:35 -05:00
Stefan Wojcik
0189818f3e clearer .landscape.yml + change self.__class__._meta to self._meta 2016-12-08 10:45:24 -05:00
Stefan Wojcik
205a975781 fix flake8 2016-12-08 10:34:09 -05:00
Stefan Wojcik
bb81652ffe nicer imports 2016-12-08 10:33:15 -05:00
Stefan Wojcik
edbecb4df0 minimize cyclic import warnings
remaining ones are wrongly attributed to mongoengine.common which only does inline imports
2016-12-08 10:12:46 -05:00
Stefan Wojcik
4373ea98cf more import fixes 2016-12-08 08:42:32 -05:00
Stefan Wojcik
8f657e0f7d cleaner code + prefer top-level import over _import_class 2016-12-08 08:18:33 -05:00
Stefan Wojcik
f6b8899bba fix broken inheritance for Document and EmbeddedDocument 2016-12-07 22:48:06 -05:00
Stefan Wojcik
c43a5fe760 add .landscape.yml 2016-12-07 00:13:32 -05:00
Stefan Wojcik
c1993de524 remove one last unicode + safer default param 2016-12-06 23:36:57 -05:00
Stefan Wojcik
bc83ba6a24 minor tweaks to Document._build_index_specs 2016-12-06 23:28:21 -05:00
Stefan Wojcik
0fc44efbcc minor tweak to python_support 2016-12-06 23:09:55 -05:00
Stefan Wojcik
1b36ca00e5 minor health tweak to benchmark.py 2016-12-06 23:04:38 -05:00
Stefan Wojcik
7dd4639037 pk as a property with a setter + get rid of basestring 2016-12-06 23:02:08 -05:00
Stefan Wojcik
557f9bd971 flake8 tweaks 2016-12-06 16:20:41 -05:00
Stefan Wojcik
59cac2b75c remove last few uses of "unicode" 2016-12-06 16:17:15 -05:00
Stefan Wojcik
548c7438b0 dont re-implement six 2016-12-06 16:14:53 -05:00
Stefan Wojcik
50df653768 imports working in py2 and py3 in mongoengine/__init__.py 2016-12-06 13:53:12 -05:00
Stefan Wojcik
bc6c84c408 remove unnecessary sys.path manipulation in tests 2016-12-06 13:27:10 -05:00
Stefan Wojcik
6b2cebb07b healthier, cleaner imports 2016-12-06 13:17:40 -05:00
Stefan Wojcik
db673a9033 ditch the old "except Exception, e" syntax 2016-12-05 23:23:38 -05:00
20 changed files with 45 additions and 413 deletions

View File

@@ -20,7 +20,7 @@ post to the `user group <http://groups.google.com/group/mongoengine-users>`
Supported Interpreters
----------------------
MongoEngine supports CPython 2.7 and newer. Language
MongoEngine supports CPython 2.6 and newer. Language
features not supported by all interpreters can not be used.
Please also ensure that your code is properly converted by
`2to3 <http://docs.python.org/library/2to3.html>`_ for Python 3 support.

View File

@@ -4,7 +4,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: Stefan Wójcik (http://github.com/wojcikstefan)
:Maintainer: Ross Lawley (http://github.com/rozza)
.. image:: https://travis-ci.org/MongoEngine/mongoengine.svg?branch=master
:target: https://travis-ci.org/MongoEngine/mongoengine

View File

@@ -5,8 +5,6 @@ Changelog
Development
===========
- (Fill this out as you fix issues and develop you features).
- Fixed connecting to a replica set with PyMongo 2.x #1436
- Fixed an obscure error message when filtering by `field__in=non_iterable`. #1237
Changes in 0.11.0
=================

View File

@@ -479,8 +479,6 @@ operators. To use a :class:`~mongoengine.queryset.Q` object, pass it in as the
first positional argument to :attr:`Document.objects` when you filter it by
calling it with keyword arguments::
from mongoengine.queryset.visitor import Q
# Get published posts
Post.objects(Q(published=True) | Q(publish_date__lte=datetime.now()))

View File

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

View File

@@ -138,7 +138,10 @@ class BaseList(list):
return super(BaseList, self).__setitem__(key, value)
def __delitem__(self, key, *args, **kwargs):
self._mark_as_changed()
if isinstance(key, slice):
self._mark_as_changed()
else:
self._mark_as_changed(key)
return super(BaseList, self).__delitem__(key)
def __setslice__(self, *args, **kwargs):

View File

@@ -675,9 +675,6 @@ class BaseDocument(object):
if not only_fields:
only_fields = []
if son and not isinstance(son, dict):
raise ValueError("The source SON object needs to be of type 'dict'")
# Get the class name from the document, falling back to the given
# class if unavailable
class_name = son.get('_cls', cls._class_name)

View File

@@ -23,6 +23,7 @@ class BaseField(object):
.. versionchanged:: 0.5 - added verbose and help text
"""
name = None
_geo_index = False
_auto_gen = False # Call `generate` to generate a value

View File

@@ -34,10 +34,7 @@ def _import_class(cls_name):
queryset_classes = ('OperationError',)
deref_classes = ('DeReference',)
if cls_name == 'BaseDocument':
from mongoengine.base import document as module
import_classes = ['BaseDocument']
elif cls_name in doc_classes:
if cls_name in doc_classes:
from mongoengine import document as module
import_classes = doc_classes
elif cls_name in field_classes:

View File

@@ -66,9 +66,9 @@ def register_connection(alias, name=None, host=None, port=None,
'authentication_mechanism': authentication_mechanism
}
# Handle uri style connections
conn_host = conn_settings['host']
# Host can be a list or a string, so if string, force to a list.
# host can be a list or a string, so if string, force to a list
if isinstance(conn_host, six.string_types):
conn_host = [conn_host]
@@ -96,7 +96,7 @@ def register_connection(alias, name=None, host=None, port=None,
uri_options = uri_dict['options']
if 'replicaset' in uri_options:
conn_settings['replicaSet'] = uri_options['replicaset']
conn_settings['replicaSet'] = True
if 'authsource' in uri_options:
conn_settings['authentication_source'] = uri_options['authsource']
if 'authmechanism' in uri_options:
@@ -170,22 +170,23 @@ def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
else:
connection_class = MongoClient
# For replica set connections with PyMongo 2.x, use
# MongoReplicaSetClient.
# TODO remove this once we stop supporting PyMongo 2.x.
if 'replicaSet' in conn_settings and not IS_PYMONGO_3:
connection_class = MongoReplicaSetClient
conn_settings['hosts_or_uri'] = conn_settings.pop('host', None)
# hosts_or_uri has to be a string, so if 'host' was provided
# as a list, join its parts and separate them by ','
if isinstance(conn_settings['hosts_or_uri'], list):
conn_settings['hosts_or_uri'] = ','.join(
conn_settings['hosts_or_uri'])
# Handle replica set connections
if 'replicaSet' in conn_settings:
# Discard port since it can't be used on MongoReplicaSetClient
conn_settings.pop('port', None)
# Discard replicaSet if it's not a string
if not isinstance(conn_settings['replicaSet'], six.string_types):
del conn_settings['replicaSet']
# For replica set connections with PyMongo 2.x, use
# MongoReplicaSetClient.
# TODO remove this once we stop supporting PyMongo 2.x.
if not IS_PYMONGO_3:
connection_class = MongoReplicaSetClient
conn_settings['hosts_or_uri'] = conn_settings.pop('host', None)
# Iterate over all of the connection settings and if a connection with
# the same parameters is already established, use it instead of creating
# a new one.

View File

@@ -313,9 +313,6 @@ class Document(BaseDocument):
.. versionchanged:: 0.10.7
Add signal_kwargs argument
"""
if self._meta.get('abstract'):
raise InvalidDocumentError('Cannot save an abstract document.')
signal_kwargs = signal_kwargs or {}
signals.pre_save.send(self.__class__, document=self, **signal_kwargs)
@@ -831,6 +828,7 @@ class Document(BaseDocument):
""" Lists all of the indexes that should be created for given
collection. It includes all the indexes from super- and sub-classes.
"""
if cls._meta.get('abstract'):
return []

View File

@@ -28,7 +28,7 @@ from mongoengine.base import (BaseDocument, BaseField, ComplexBaseField,
GeoJsonBaseField, ObjectIdField, get_document)
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
from mongoengine.document import Document, EmbeddedDocument
from mongoengine.errors import DoesNotExist, InvalidQueryError, ValidationError
from mongoengine.errors import DoesNotExist, ValidationError
from mongoengine.python_support import StringIO
from mongoengine.queryset import DO_NOTHING, QuerySet
@@ -566,11 +566,7 @@ class EmbeddedDocumentField(BaseField):
def prepare_query_value(self, op, value):
if value is not None and not isinstance(value, self.document_type):
try:
value = self.document_type._from_son(value)
except ValueError:
raise InvalidQueryError("Querying the embedded document '%s' failed, due to an invalid query value" %
(self.document_type._class_name,))
value = self.document_type._from_son(value)
super(EmbeddedDocumentField, self).prepare_query_value(op, value)
return self.to_mongo(value)

View File

@@ -900,24 +900,18 @@ class BaseQuerySet(object):
return self.fields(**fields)
def fields(self, _only_called=False, **kwargs):
"""Manipulate how you load this document's fields. Used by `.only()`
and `.exclude()` to manipulate which fields to retrieve. If called
directly, use a set of kwargs similar to the MongoDB projection
document. For example:
"""Manipulate how you load this document's fields. Used by `.only()`
and `.exclude()` to manipulate which fields to retrieve. Fields also
allows for a greater level of control for example:
Include only a subset of fields:
Retrieving a Subrange of Array Elements:
posts = BlogPost.objects(...).fields(author=1, title=1)
You can use the $slice operator to retrieve a subrange of elements in
an array. For example to get the first 5 comments::
Exclude a specific field:
post = BlogPost.objects(...).fields(slice__comments=5)
posts = BlogPost.objects(...).fields(comments=0)
To retrieve a subrange of array elements:
posts = BlogPost.objects(...).fields(slice__comments=5)
:param kwargs: A set keywors arguments identifying what to include.
:param kwargs: A dictionary identifying what to include
.. versionadded:: 0.5
"""
@@ -933,20 +927,7 @@ class BaseQuerySet(object):
key = '.'.join(parts)
cleaned_fields.append((key, value))
# Sort fields by their values, explicitly excluded fields first, then
# explicitly included, and then more complicated operators such as
# $slice.
def _sort_key(field_tuple):
key, value = field_tuple
if isinstance(value, (int)):
return value # 0 for exclusion, 1 for inclusion
else:
return 2 # so that complex values appear last
fields = sorted(cleaned_fields, key=_sort_key)
# Clone the queryset, group all fields by their value, convert
# each of them to db_fields, and set the queryset's _loaded_fields
fields = sorted(cleaned_fields, key=operator.itemgetter(1))
queryset = self.clone()
for value, group in itertools.groupby(fields, lambda x: x[1]):
fields = [field for field, value in group]

View File

@@ -101,21 +101,8 @@ def query(_doc_cls=None, **kwargs):
value = value['_id']
elif op in ('in', 'nin', 'all', 'near') and not isinstance(value, dict):
# Raise an error if the in/nin/all/near param is not iterable. We need a
# special check for BaseDocument, because - although it's iterable - using
# it as such in the context of this method is most definitely a mistake.
BaseDocument = _import_class('BaseDocument')
if isinstance(value, BaseDocument):
raise TypeError("When using the `in`, `nin`, `all`, or "
"`near`-operators you can\'t use a "
"`Document`, you must wrap your object "
"in a list (object -> [object]).")
elif not hasattr(value, '__iter__'):
raise TypeError("The `in`, `nin`, `all`, or "
"`near`-operators must be applied to an "
"iterable (e.g. a list).")
else:
value = [field.prepare_query_value(op, v) for v in value]
# 'in', 'nin' and 'all' require a list of values
value = [field.prepare_query_value(op, v) for v in value]
# If we're querying a GenericReferenceField, we need to alter the
# key depending on the value:
@@ -233,7 +220,8 @@ def update(_doc_cls=None, **update):
# Support decrement by flipping a positive value's sign
# and using 'inc'
op = 'inc'
value = -value
if value > 0:
value = -value
elif op == 'add_to_set':
op = 'addToSet'
elif op == 'set_on_insert':

View File

@@ -7,5 +7,5 @@ cover-package=mongoengine
[flake8]
ignore=E501,F401,F403,F405,I201
exclude=build,dist,docs,venv,venv3,.tox,.eggs,tests
max-complexity=47
max-complexity=45
application-import-names=mongoengine,tests

View File

@@ -435,15 +435,6 @@ class InstanceTest(unittest.TestCase):
person.to_dbref()
def test_save_abstract_document(self):
"""Saving an abstract document should fail."""
class Doc(Document):
name = StringField()
meta = {'abstract': True}
with self.assertRaises(InvalidDocumentError):
Doc(name='aaa').save()
def test_reload(self):
"""Ensure that attributes may be reloaded.
"""
@@ -1869,10 +1860,6 @@ class InstanceTest(unittest.TestCase):
'occurs': {"hello": None}
})
# Tests for issue #1438: https://github.com/MongoEngine/mongoengine/issues/1438
with self.assertRaises(ValueError):
Word._from_son('this is not a valid SON dict')
def test_reverse_delete_rule_cascade_and_nullify(self):
"""Ensure that a referenced document is also deleted upon deletion.
"""

View File

@@ -1042,7 +1042,6 @@ class FieldTest(unittest.TestCase):
self.assertEqual(
BlogPost.objects.filter(info__100__test__exact='test').count(), 0)
# test queries by list
post = BlogPost()
post.info = ['1', '2']
post.save()
@@ -1054,248 +1053,6 @@ class FieldTest(unittest.TestCase):
post.info *= 2
post.save()
self.assertEqual(BlogPost.objects(info=['1', '2', '3', '4', '1', '2', '3', '4']).count(), 1)
BlogPost.drop_collection()
def test_list_field_manipulative_operators(self):
"""Ensure that ListField works with standard list operators that manipulate the list.
"""
class BlogPost(Document):
ref = StringField()
info = ListField(StringField())
BlogPost.drop_collection()
post = BlogPost()
post.ref = "1234"
post.info = ['0', '1', '2', '3', '4', '5']
post.save()
def reset_post():
post.info = ['0', '1', '2', '3', '4', '5']
post.save()
# '__add__(listB)'
# listA+listB
# operator.add(listA, listB)
reset_post()
temp = ['a', 'b']
post.info = post.info + temp
self.assertEqual(post.info, ['0', '1', '2', '3', '4', '5', 'a', 'b'])
post.save()
post.reload()
self.assertEqual(post.info, ['0', '1', '2', '3', '4', '5', 'a', 'b'])
# '__delitem__(index)'
# aka 'del list[index]'
# aka 'operator.delitem(list, index)'
reset_post()
del post.info[2] # del from middle ('2')
self.assertEqual(post.info, ['0', '1', '3', '4', '5'])
post.save()
post.reload()
self.assertEqual(post.info, ['0', '1', '3', '4', '5'])
# '__delitem__(slice(i, j))'
# aka 'del list[i:j]'
# aka 'operator.delitem(list, slice(i,j))'
reset_post()
del post.info[1:3] # removes '1', '2'
self.assertEqual(post.info, ['0', '3', '4', '5'])
post.save()
post.reload()
self.assertEqual(post.info, ['0', '3', '4', '5'])
# '__iadd__'
# aka 'list += list'
reset_post()
temp = ['a', 'b']
post.info += temp
self.assertEqual(post.info, ['0', '1', '2', '3', '4', '5', 'a', 'b'])
post.save()
post.reload()
self.assertEqual(post.info, ['0', '1', '2', '3', '4', '5', 'a', 'b'])
# '__imul__'
# aka 'list *= number'
reset_post()
post.info *= 2
self.assertEqual(post.info, ['0', '1', '2', '3', '4', '5', '0', '1', '2', '3', '4', '5'])
post.save()
post.reload()
self.assertEqual(post.info, ['0', '1', '2', '3', '4', '5', '0', '1', '2', '3', '4', '5'])
# '__mul__'
# aka 'listA*listB'
reset_post()
post.info = post.info * 2
self.assertEqual(post.info, ['0', '1', '2', '3', '4', '5', '0', '1', '2', '3', '4', '5'])
post.save()
post.reload()
self.assertEqual(post.info, ['0', '1', '2', '3', '4', '5', '0', '1', '2', '3', '4', '5'])
# '__rmul__'
# aka 'listB*listA'
reset_post()
post.info = 2 * post.info
self.assertEqual(post.info, ['0', '1', '2', '3', '4', '5', '0', '1', '2', '3', '4', '5'])
post.save()
post.reload()
self.assertEqual(post.info, ['0', '1', '2', '3', '4', '5', '0', '1', '2', '3', '4', '5'])
# '__setitem__(index, value)'
# aka 'list[index]=value'
# aka 'setitem(list, value)'
reset_post()
post.info[4] = '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)'
reset_post()
post.info[1: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'])
# 'append'
reset_post()
post.info.append('h')
self.assertEqual(post.info, ['0', '1', '2', '3', '4', '5', 'h'])
post.save()
post.reload()
self.assertEqual(post.info, ['0', '1', '2', '3', '4', '5', 'h'])
# 'extend'
reset_post()
post.info.extend(['h', 'e', 'l', 'l', 'o'])
self.assertEqual(post.info, ['0', '1', '2', '3', '4', '5', 'h', 'e', 'l', 'l', 'o'])
post.save()
post.reload()
self.assertEqual(post.info, ['0', '1', '2', '3', '4', '5', 'h', 'e', 'l', 'l', 'o'])
# 'insert'
# 'pop'
reset_post()
x = post.info.pop(2)
y = post.info.pop()
self.assertEqual(post.info, ['0', '1', '3', '4'])
self.assertEqual(x, '2')
self.assertEqual(y, '5')
post.save()
post.reload()
self.assertEqual(post.info, ['0', '1', '3', '4'])
# 'remove'
reset_post()
post.info.remove('2')
self.assertEqual(post.info, ['0', '1', '3', '4', '5'])
post.save()
post.reload()
self.assertEqual(post.info, ['0', '1', '3', '4', '5'])
# 'reverse'
reset_post()
post.info.reverse()
self.assertEqual(post.info, ['5', '4', '3', '2', '1', '0'])
post.save()
post.reload()
self.assertEqual(post.info, ['5', '4', '3', '2', '1', '0'])
# 'sort': though this operator method does manipulate the list, it is tested in
# the 'test_list_field_lexicograpic_operators' function
BlogPost.drop_collection()
def test_list_field_invalid_operators(self):
class BlogPost(Document):
ref = StringField()
info = ListField(StringField())
post = BlogPost()
post.ref = "1234"
post.info = ['0', '1', '2', '3', '4', '5']
# '__hash__'
# aka 'hash(list)'
# # assert TypeError
self.assertRaises(TypeError, lambda: hash(post.info))
def test_list_field_lexicographic_operators(self):
"""Ensure that ListField works with standard list operators that do lexigraphic ordering.
"""
class BlogPost(Document):
ref = StringField()
text_info = ListField(StringField())
oid_info = ListField(ObjectIdField())
bool_info = ListField(BooleanField())
BlogPost.drop_collection()
blogSmall = BlogPost(ref="small")
blogSmall.text_info = ["a", "a", "a"]
blogSmall.bool_info = [False, False]
blogSmall.save()
blogSmall.reload()
blogLargeA = BlogPost(ref="big")
blogLargeA.text_info = ["a", "z", "j"]
blogLargeA.bool_info = [False, True]
blogLargeA.save()
blogLargeA.reload()
blogLargeB = BlogPost(ref="big2")
blogLargeB.text_info = ["a", "z", "j"]
blogLargeB.oid_info = [
"54495ad94c934721ede76f90",
"54495ad94c934721ede76d23",
"54495ad94c934721ede76d00"
]
blogLargeB.bool_info = [False, True]
blogLargeB.save()
blogLargeB.reload()
# '__eq__' aka '=='
self.assertEqual(blogLargeA.text_info, blogLargeB.text_info)
self.assertEqual(blogLargeA.bool_info, blogLargeB.bool_info)
# '__ge__' aka '>='
self.assertGreaterEqual(blogLargeA.text_info, blogSmall.text_info)
self.assertGreaterEqual(blogLargeA.text_info, blogLargeB.text_info)
self.assertGreaterEqual(blogLargeA.bool_info, blogSmall.bool_info)
self.assertGreaterEqual(blogLargeA.bool_info, blogLargeB.bool_info)
# '__gt__' aka '>'
self.assertGreaterEqual(blogLargeA.text_info, blogSmall.text_info)
self.assertGreaterEqual(blogLargeA.bool_info, blogSmall.bool_info)
# '__le__' aka '<='
self.assertLessEqual(blogSmall.text_info, blogLargeB.text_info)
self.assertLessEqual(blogLargeA.text_info, blogLargeB.text_info)
self.assertLessEqual(blogSmall.bool_info, blogLargeB.bool_info)
self.assertLessEqual(blogLargeA.bool_info, blogLargeB.bool_info)
# '__lt__' aka '<'
self.assertLess(blogSmall.text_info, blogLargeB.text_info)
self.assertLess(blogSmall.bool_info, blogLargeB.bool_info)
# '__ne__' aka '!='
self.assertNotEqual(blogSmall.text_info, blogLargeB.text_info)
self.assertNotEqual(blogSmall.bool_info, blogLargeB.bool_info)
# 'sort'
blogLargeB.bool_info = [True, False, True, False]
blogLargeB.text_info.sort()
blogLargeB.oid_info.sort()
blogLargeB.bool_info.sort()
sorted_target_list = [
ObjectId("54495ad94c934721ede76d00"),
ObjectId("54495ad94c934721ede76d23"),
ObjectId("54495ad94c934721ede76f90")
]
self.assertEqual(blogLargeB.text_info, ["a", "j", "z"])
self.assertEqual(blogLargeB.oid_info, sorted_target_list)
self.assertEqual(blogLargeB.bool_info, [False, False, True, True])
blogLargeB.save()
blogLargeB.reload()
self.assertEqual(blogLargeB.text_info, ["a", "j", "z"])
self.assertEqual(blogLargeB.oid_info, sorted_target_list)
self.assertEqual(blogLargeB.bool_info, [False, False, True, True])
BlogPost.drop_collection()
def test_list_assignment(self):
@@ -1345,6 +1102,7 @@ class FieldTest(unittest.TestCase):
post.reload()
self.assertEqual(post.info, [1, 2, 3, 4, 'n5'])
def test_list_field_passed_in_value(self):
class Foo(Document):
bars = ListField(ReferenceField("Bar"))

View File

@@ -141,16 +141,6 @@ class OnlyExcludeAllTest(unittest.TestCase):
self.assertEqual(qs._loaded_fields.as_dict(),
{'b': {'$slice': 5}})
def test_mix_slice_with_other_fields(self):
class MyDoc(Document):
a = ListField()
b = ListField()
c = ListField()
qs = MyDoc.objects.fields(a=1, b=0, slice__c=2)
self.assertEqual(qs._loaded_fields.as_dict(),
{'c': {'$slice': 2}, 'a': 1})
def test_only(self):
"""Ensure that QuerySet.only only returns the requested fields.
"""

View File

@@ -1266,7 +1266,7 @@ class QuerySetTest(unittest.TestCase):
def test_find_embedded(self):
"""Ensure that an embedded document is properly returned from
different manners of querying.
a query.
"""
class User(EmbeddedDocument):
name = StringField()
@@ -1277,9 +1277,8 @@ class QuerySetTest(unittest.TestCase):
BlogPost.drop_collection()
user = User(name='Test User')
BlogPost.objects.create(
author=user,
author=User(name='Test User'),
content='Had a good coffee today...'
)
@@ -1287,19 +1286,6 @@ class QuerySetTest(unittest.TestCase):
self.assertTrue(isinstance(result.author, User))
self.assertEqual(result.author.name, 'Test User')
result = BlogPost.objects.get(author__name=user.name)
self.assertTrue(isinstance(result.author, User))
self.assertEqual(result.author.name, 'Test User')
result = BlogPost.objects.get(author={'name': user.name})
self.assertTrue(isinstance(result.author, User))
self.assertEqual(result.author.name, 'Test User')
# Fails, since the string is not a type that is able to represent the
# author's document structure (should be dict)
with self.assertRaises(InvalidQueryError):
BlogPost.objects.get(author=user.name)
def test_find_empty_embedded(self):
"""Ensure that you can save and find an empty embedded document."""
class User(EmbeddedDocument):
@@ -1826,11 +1812,6 @@ class QuerySetTest(unittest.TestCase):
post.reload()
self.assertEqual(post.hits, 10)
# Negative dec operator is equal to a positive inc operator
BlogPost.objects.update_one(dec__hits=-1)
post.reload()
self.assertEqual(post.hits, 11)
BlogPost.objects.update(push__tags='mongo')
post.reload()
self.assertTrue('mongo' in post.tags)
@@ -4982,35 +4963,6 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(i, 249)
self.assertEqual(j, 249)
def test_in_operator_on_non_iterable(self):
"""Ensure that using the `__in` operator on a non-iterable raises an
error.
"""
class User(Document):
name = StringField()
class BlogPost(Document):
content = StringField()
authors = ListField(ReferenceField(User))
User.drop_collection()
BlogPost.drop_collection()
author = User.objects.create(name='Test User')
post = BlogPost.objects.create(content='Had a good coffee today...',
authors=[author])
# Make sure using `__in` with a list works
blog_posts = BlogPost.objects(authors__in=[author])
self.assertEqual(list(blog_posts), [post])
# Using `__in` with a non-iterable should raise a TypeError
self.assertRaises(TypeError, BlogPost.objects(authors__in=author.pk).count)
# Using `__in` with a `Document` (which is seemingly iterable but not
# in a way we'd expect) should raise a TypeError, too
self.assertRaises(TypeError, BlogPost.objects(authors__in=author).count)
if __name__ == '__main__':
unittest.main()

View File

@@ -200,19 +200,6 @@ class ConnectionTest(unittest.TestCase):
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertEqual(db.name, 'test')
def test_connect_uri_with_replicaset(self):
"""Ensure connect() works when specifying a replicaSet."""
if IS_PYMONGO_3:
c = connect(host='mongodb://localhost/test?replicaSet=local-rs')
db = get_db()
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertEqual(db.name, 'test')
else:
# PyMongo < v3.x raises an exception:
# "localhost:27017 is not a member of replica set local-rs"
with self.assertRaises(MongoEngineConnectionError):
c = connect(host='mongodb://localhost/test?replicaSet=local-rs')
def test_uri_without_credentials_doesnt_override_conn_settings(self):
"""Ensure connect() uses the username & password params if the URI
doesn't explicitly specify them.