Compare commits
73 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
88c4aa2d87 | ||
|
fb8c0d8fe3 | ||
|
1a863725d1 | ||
|
7b4245c91c | ||
|
9bd0d6b99d | ||
|
b640c766db | ||
|
50ffa8014e | ||
|
7ef688b256 | ||
|
b4fe0b35e4 | ||
|
a2cbbdf819 | ||
|
35b7efe3f4 | ||
|
7cea2a768f | ||
|
7247b9b68e | ||
|
dca837b843 | ||
|
c60c2ee8d0 | ||
|
3cdb5b5db2 | ||
|
b9cc8a4ca9 | ||
|
28606e9985 | ||
|
5bbe782812 | ||
|
d65861cdf7 | ||
|
c8df3fd2a7 | ||
|
6cfe6652a3 | ||
|
6b711da69d | ||
|
595cb99b2d | ||
|
f0a3445250 | ||
|
6d353dae1e | ||
|
57a38282a9 | ||
|
db47604865 | ||
|
2a121fe202 | ||
|
36baff0d7f | ||
|
201f3008b1 | ||
|
f4873fee18 | ||
|
e02261be6d | ||
|
2919e6765c | ||
|
b8fc4d0079 | ||
|
4a46f5f095 | ||
|
3484ceabb8 | ||
|
cab659dce6 | ||
|
a657f29439 | ||
|
4c054bf316 | ||
|
dc7922c38b | ||
|
c6c68abfcc | ||
|
6aacb0c898 | ||
|
e7000db491 | ||
|
fce994ea7f | ||
|
6c6446765e | ||
|
69a99c70c6 | ||
|
56d9f7a8af | ||
|
363aefe399 | ||
|
7fd4f792ba | ||
|
6fbdde63d8 | ||
|
b04dc90cdf | ||
|
b525c91bd3 | ||
|
a32c893078 | ||
|
2c6a744848 | ||
|
4492874d08 | ||
|
d3a592e5bf | ||
|
cab21b1b21 | ||
|
1319e422ea | ||
|
c88ea40b57 | ||
|
3194a37fcb | ||
|
72ebaa52e9 | ||
|
0e00695fc7 | ||
|
48a691e722 | ||
|
cf54d6d6f8 | ||
|
a03fe234d0 | ||
|
d88d40cc08 | ||
|
d3b4af116e | ||
|
352b23331b | ||
|
0fa6610fdb | ||
|
7ddbea697e | ||
|
d72daf5f39 | ||
|
54d8c64ad5 |
@@ -19,8 +19,14 @@ elif [ "$MONGODB" = "3.2" ]; then
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install mongodb-org-server=3.2.20
|
sudo apt-get install mongodb-org-server=3.2.20
|
||||||
# service should be started automatically
|
# service should be started automatically
|
||||||
|
elif [ "$MONGODB" = "3.4" ]; then
|
||||||
|
sudo apt-key adv --keyserver keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6
|
||||||
|
echo "deb http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.4.list
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install mongodb-org-server=3.4.17
|
||||||
|
# service should be started automatically
|
||||||
else
|
else
|
||||||
echo "Invalid MongoDB version, expected 2.6, 3.0, or 3.2"
|
echo "Invalid MongoDB version, expected 2.6, 3.0, 3.2 or 3.4."
|
||||||
exit 1
|
exit 1
|
||||||
fi;
|
fi;
|
||||||
|
|
||||||
|
@@ -6,6 +6,7 @@
|
|||||||
# v3.5, v3.6, PyPy, and PyMongo v3.x.
|
# v3.5, v3.6, PyPy, and PyMongo v3.x.
|
||||||
# * MongoDB v3.0 & v3.2 are tested against Python v2.7, v3.5 & v3.6
|
# * MongoDB v3.0 & v3.2 are tested against Python v2.7, v3.5 & v3.6
|
||||||
# and Pymongo v3.5 & v3.x
|
# and Pymongo v3.5 & v3.x
|
||||||
|
# * MongoDB v3.4 is tested against v3.6 and Pymongo v3.x
|
||||||
# Reminder: Update README.rst if you change MongoDB versions we test.
|
# Reminder: Update README.rst if you change MongoDB versions we test.
|
||||||
|
|
||||||
language: python
|
language: python
|
||||||
@@ -26,16 +27,14 @@ matrix:
|
|||||||
include:
|
include:
|
||||||
- python: 2.7
|
- python: 2.7
|
||||||
env: MONGODB=3.0 PYMONGO=3.5
|
env: MONGODB=3.0 PYMONGO=3.5
|
||||||
- python: 2.7
|
|
||||||
env: MONGODB=3.2 PYMONGO=3.x
|
|
||||||
- python: 3.5
|
|
||||||
env: MONGODB=3.0 PYMONGO=3.5
|
|
||||||
- python: 3.5
|
- python: 3.5
|
||||||
env: MONGODB=3.2 PYMONGO=3.x
|
env: MONGODB=3.2 PYMONGO=3.x
|
||||||
- python: 3.6
|
- python: 3.6
|
||||||
env: MONGODB=3.0 PYMONGO=3.5
|
env: MONGODB=3.0 PYMONGO=3.5
|
||||||
- python: 3.6
|
- python: 3.6
|
||||||
env: MONGODB=3.2 PYMONGO=3.x
|
env: MONGODB=3.2 PYMONGO=3.x
|
||||||
|
- python: 3.6
|
||||||
|
env: MONGODB=3.4 PYMONGO=3.x
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- bash .install_mongodb_on_travis.sh
|
- bash .install_mongodb_on_travis.sh
|
||||||
|
2
AUTHORS
2
AUTHORS
@@ -247,3 +247,5 @@ that much better:
|
|||||||
* Erdenezul Batmunkh (https://github.com/erdenezul)
|
* Erdenezul Batmunkh (https://github.com/erdenezul)
|
||||||
* Andy Yankovsky (https://github.com/werat)
|
* Andy Yankovsky (https://github.com/werat)
|
||||||
* Bastien Gérard (https://github.com/bagerard)
|
* Bastien Gérard (https://github.com/bagerard)
|
||||||
|
* Trevor Hall (https://github.com/tjhall13)
|
||||||
|
* Gleb Voropaev (https://github.com/buggyspace)
|
@@ -26,7 +26,7 @@ an `API reference <https://mongoengine-odm.readthedocs.io/apireference.html>`_.
|
|||||||
|
|
||||||
Supported MongoDB Versions
|
Supported MongoDB Versions
|
||||||
==========================
|
==========================
|
||||||
MongoEngine is currently tested against MongoDB v2.6, v3.0 and v3.2. Future
|
MongoEngine is currently tested against MongoDB v2.6, v3.0, v3.2 and v3.4. Future
|
||||||
versions should be supported as well, but aren't actively tested at the moment.
|
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
|
Make sure to open an issue or submit a pull request if you experience any
|
||||||
problems with MongoDB v3.4+.
|
problems with MongoDB v3.4+.
|
||||||
@@ -36,7 +36,7 @@ Installation
|
|||||||
We recommend the use of `virtualenv <https://virtualenv.pypa.io/>`_ and of
|
We recommend the use of `virtualenv <https://virtualenv.pypa.io/>`_ and of
|
||||||
`pip <https://pip.pypa.io/>`_. You can then use ``pip install -U mongoengine``.
|
`pip <https://pip.pypa.io/>`_. You can then use ``pip install -U mongoengine``.
|
||||||
You may also have `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_
|
You may also have `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_
|
||||||
and thus you can use ``easy_install -U mongoengine``. Another option is
|
and thus you can use ``easy_install -U mongoengine``. Another option is
|
||||||
`pipenv <https://docs.pipenv.org/>`_. You can then use ``pipenv install mongoengine``
|
`pipenv <https://docs.pipenv.org/>`_. You can then use ``pipenv install mongoengine``
|
||||||
to both create the virtual environment and install the package. Otherwise, you can
|
to both create the virtual environment and install the package. Otherwise, you can
|
||||||
download the source from `GitHub <http://github.com/MongoEngine/mongoengine>`_ and
|
download the source from `GitHub <http://github.com/MongoEngine/mongoengine>`_ and
|
||||||
|
@@ -6,6 +6,21 @@ Development
|
|||||||
===========
|
===========
|
||||||
- (Fill this out as you fix issues and develop your features).
|
- (Fill this out as you fix issues and develop your features).
|
||||||
|
|
||||||
|
Changes in 0.17.0
|
||||||
|
=================
|
||||||
|
- Fix .only() working improperly after using .count() of the same instance of QuerySet
|
||||||
|
- Fix batch_size that was not copied when cloning a queryset object #2011
|
||||||
|
- POTENTIAL BREAKING CHANGE: All result fields are now passed, including internal fields (_cls, _id) when using `QuerySet.as_pymongo` #1976
|
||||||
|
- Document a BREAKING CHANGE introduced in 0.15.3 and not reported at that time (#1995)
|
||||||
|
- Fix InvalidStringData error when using modify on a BinaryField #1127
|
||||||
|
- DEPRECATION: `EmbeddedDocument.save` & `.reload` are marked as deprecated and will be removed in a next version of mongoengine #1552
|
||||||
|
- Fix test suite and CI to support MongoDB 3.4 #1445
|
||||||
|
|
||||||
|
=================
|
||||||
|
Changes in 0.16.3
|
||||||
|
=================
|
||||||
|
- Fix $push with $position operator not working with lists in embedded document #1965
|
||||||
|
|
||||||
=================
|
=================
|
||||||
Changes in 0.16.2
|
Changes in 0.16.2
|
||||||
=================
|
=================
|
||||||
@@ -57,6 +72,7 @@ Changes in 0.16.0
|
|||||||
|
|
||||||
Changes in 0.15.3
|
Changes in 0.15.3
|
||||||
=================
|
=================
|
||||||
|
- BREAKING CHANGES: `Queryset.update/update_one` methods now returns an UpdateResult when `full_result=True` is provided and no longer a dict (relates to #1491)
|
||||||
- Subfield resolve error in generic_emdedded_document query #1651 #1652
|
- Subfield resolve error in generic_emdedded_document query #1651 #1652
|
||||||
- use each modifier only with $position #1673 #1675
|
- use each modifier only with $position #1673 #1675
|
||||||
- Improve LazyReferenceField and GenericLazyReferenceField with nested fields #1704
|
- Improve LazyReferenceField and GenericLazyReferenceField with nested fields #1704
|
||||||
|
@@ -85,6 +85,7 @@ are as follows:
|
|||||||
* :class:`~mongoengine.fields.ImageField`
|
* :class:`~mongoengine.fields.ImageField`
|
||||||
* :class:`~mongoengine.fields.IntField`
|
* :class:`~mongoengine.fields.IntField`
|
||||||
* :class:`~mongoengine.fields.ListField`
|
* :class:`~mongoengine.fields.ListField`
|
||||||
|
* :class:`~mongoengine.fields.LongField`
|
||||||
* :class:`~mongoengine.fields.MapField`
|
* :class:`~mongoengine.fields.MapField`
|
||||||
* :class:`~mongoengine.fields.ObjectIdField`
|
* :class:`~mongoengine.fields.ObjectIdField`
|
||||||
* :class:`~mongoengine.fields.ReferenceField`
|
* :class:`~mongoengine.fields.ReferenceField`
|
||||||
@@ -155,7 +156,7 @@ arguments can be set on all fields:
|
|||||||
An iterable (e.g. list, tuple or set) of choices to which the value of this
|
An iterable (e.g. list, tuple or set) of choices to which the value of this
|
||||||
field should be limited.
|
field should be limited.
|
||||||
|
|
||||||
Can be either be a nested tuples of value (stored in mongo) and a
|
Can either be nested tuples of value (stored in mongo) and a
|
||||||
human readable key ::
|
human readable key ::
|
||||||
|
|
||||||
SIZE = (('S', 'Small'),
|
SIZE = (('S', 'Small'),
|
||||||
|
@@ -23,7 +23,7 @@ __all__ = (list(document.__all__) + list(fields.__all__) +
|
|||||||
list(signals.__all__) + list(errors.__all__))
|
list(signals.__all__) + list(errors.__all__))
|
||||||
|
|
||||||
|
|
||||||
VERSION = (0, 16, 2)
|
VERSION = (0, 17, 0)
|
||||||
|
|
||||||
|
|
||||||
def get_version():
|
def get_version():
|
||||||
|
@@ -2,11 +2,30 @@ import weakref
|
|||||||
|
|
||||||
from bson import DBRef
|
from bson import DBRef
|
||||||
import six
|
import six
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
from mongoengine.common import _import_class
|
from mongoengine.common import _import_class
|
||||||
from mongoengine.errors import DoesNotExist, MultipleObjectsReturned
|
from mongoengine.errors import DoesNotExist, MultipleObjectsReturned
|
||||||
|
|
||||||
__all__ = ('BaseDict', 'BaseList', 'EmbeddedDocumentList', 'LazyReference')
|
__all__ = ('BaseDict', 'StrictDict', 'BaseList', 'EmbeddedDocumentList', 'LazyReference')
|
||||||
|
|
||||||
|
|
||||||
|
def mark_as_changed_wrapper(parent_method):
|
||||||
|
"""Decorators 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
|
||||||
|
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"""
|
||||||
|
def wrapper(self, key, *args, **kwargs):
|
||||||
|
result = parent_method(self, key, *args, **kwargs) # Can't use super() in the decorator
|
||||||
|
self._mark_as_changed(key)
|
||||||
|
return result
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
class BaseDict(dict):
|
class BaseDict(dict):
|
||||||
@@ -24,7 +43,14 @@ class BaseDict(dict):
|
|||||||
self._name = name
|
self._name = name
|
||||||
super(BaseDict, self).__init__(dict_items)
|
super(BaseDict, self).__init__(dict_items)
|
||||||
|
|
||||||
def __getitem__(self, key, *args, **kwargs):
|
def get(self, key, default=None):
|
||||||
|
# get does not use __getitem__ by default so we must override it as well
|
||||||
|
try:
|
||||||
|
return self.__getitem__(key)
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
value = super(BaseDict, self).__getitem__(key)
|
value = super(BaseDict, self).__getitem__(key)
|
||||||
|
|
||||||
EmbeddedDocument = _import_class('EmbeddedDocument')
|
EmbeddedDocument = _import_class('EmbeddedDocument')
|
||||||
@@ -40,22 +66,6 @@ class BaseDict(dict):
|
|||||||
value._instance = self._instance
|
value._instance = self._instance
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def __setitem__(self, key, value, *args, **kwargs):
|
|
||||||
self._mark_as_changed(key)
|
|
||||||
return super(BaseDict, self).__setitem__(key, value)
|
|
||||||
|
|
||||||
def __delete__(self, *args, **kwargs):
|
|
||||||
self._mark_as_changed()
|
|
||||||
return super(BaseDict, self).__delete__(*args, **kwargs)
|
|
||||||
|
|
||||||
def __delitem__(self, key, *args, **kwargs):
|
|
||||||
self._mark_as_changed(key)
|
|
||||||
return super(BaseDict, self).__delitem__(key)
|
|
||||||
|
|
||||||
def __delattr__(self, key, *args, **kwargs):
|
|
||||||
self._mark_as_changed(key)
|
|
||||||
return super(BaseDict, self).__delattr__(key)
|
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
self.instance = None
|
self.instance = None
|
||||||
self._dereferenced = False
|
self._dereferenced = False
|
||||||
@@ -65,25 +75,14 @@ class BaseDict(dict):
|
|||||||
self = state
|
self = state
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def clear(self, *args, **kwargs):
|
__setitem__ = mark_key_as_changed_wrapper(dict.__setitem__)
|
||||||
self._mark_as_changed()
|
__delattr__ = mark_key_as_changed_wrapper(dict.__delattr__)
|
||||||
return super(BaseDict, self).clear()
|
__delitem__ = mark_key_as_changed_wrapper(dict.__delitem__)
|
||||||
|
pop = mark_as_changed_wrapper(dict.pop)
|
||||||
def pop(self, *args, **kwargs):
|
clear = mark_as_changed_wrapper(dict.clear)
|
||||||
self._mark_as_changed()
|
update = mark_as_changed_wrapper(dict.update)
|
||||||
return super(BaseDict, self).pop(*args, **kwargs)
|
popitem = mark_as_changed_wrapper(dict.popitem)
|
||||||
|
setdefault = mark_as_changed_wrapper(dict.setdefault)
|
||||||
def popitem(self, *args, **kwargs):
|
|
||||||
self._mark_as_changed()
|
|
||||||
return super(BaseDict, self).popitem()
|
|
||||||
|
|
||||||
def setdefault(self, *args, **kwargs):
|
|
||||||
self._mark_as_changed()
|
|
||||||
return super(BaseDict, self).setdefault(*args, **kwargs)
|
|
||||||
|
|
||||||
def update(self, *args, **kwargs):
|
|
||||||
self._mark_as_changed()
|
|
||||||
return super(BaseDict, self).update(*args, **kwargs)
|
|
||||||
|
|
||||||
def _mark_as_changed(self, key=None):
|
def _mark_as_changed(self, key=None):
|
||||||
if hasattr(self._instance, '_mark_as_changed'):
|
if hasattr(self._instance, '_mark_as_changed'):
|
||||||
@@ -108,17 +107,24 @@ class BaseList(list):
|
|||||||
self._name = name
|
self._name = name
|
||||||
super(BaseList, self).__init__(list_items)
|
super(BaseList, self).__init__(list_items)
|
||||||
|
|
||||||
def __getitem__(self, key, *args, **kwargs):
|
def __getitem__(self, key):
|
||||||
value = super(BaseList, self).__getitem__(key)
|
value = super(BaseList, self).__getitem__(key)
|
||||||
|
|
||||||
|
if isinstance(key, slice):
|
||||||
|
# When receiving a slice operator, we don't convert the structure and bind
|
||||||
|
# to parent's instance. This is buggy for now but would require more work to be handled properly
|
||||||
|
return value
|
||||||
|
|
||||||
EmbeddedDocument = _import_class('EmbeddedDocument')
|
EmbeddedDocument = _import_class('EmbeddedDocument')
|
||||||
if isinstance(value, EmbeddedDocument) and value._instance is None:
|
if isinstance(value, EmbeddedDocument) and value._instance is None:
|
||||||
value._instance = self._instance
|
value._instance = self._instance
|
||||||
elif isinstance(value, dict) and not isinstance(value, BaseDict):
|
elif isinstance(value, dict) and not isinstance(value, BaseDict):
|
||||||
|
# Replace dict by BaseDict
|
||||||
value = BaseDict(value, None, '%s.%s' % (self._name, key))
|
value = BaseDict(value, None, '%s.%s' % (self._name, key))
|
||||||
super(BaseList, self).__setitem__(key, value)
|
super(BaseList, self).__setitem__(key, value)
|
||||||
value._instance = self._instance
|
value._instance = self._instance
|
||||||
elif isinstance(value, list) and not isinstance(value, BaseList):
|
elif isinstance(value, list) and not isinstance(value, BaseList):
|
||||||
|
# Replace list by BaseList
|
||||||
value = BaseList(value, None, '%s.%s' % (self._name, key))
|
value = BaseList(value, None, '%s.%s' % (self._name, key))
|
||||||
super(BaseList, self).__setitem__(key, value)
|
super(BaseList, self).__setitem__(key, value)
|
||||||
value._instance = self._instance
|
value._instance = self._instance
|
||||||
@@ -128,25 +134,6 @@ class BaseList(list):
|
|||||||
for v in super(BaseList, self).__iter__():
|
for v in super(BaseList, self).__iter__():
|
||||||
yield v
|
yield v
|
||||||
|
|
||||||
def __setitem__(self, key, value, *args, **kwargs):
|
|
||||||
if isinstance(key, slice):
|
|
||||||
self._mark_as_changed()
|
|
||||||
else:
|
|
||||||
self._mark_as_changed(key)
|
|
||||||
return super(BaseList, self).__setitem__(key, value)
|
|
||||||
|
|
||||||
def __delitem__(self, key):
|
|
||||||
self._mark_as_changed()
|
|
||||||
return super(BaseList, self).__delitem__(key)
|
|
||||||
|
|
||||||
def __setslice__(self, *args, **kwargs):
|
|
||||||
self._mark_as_changed()
|
|
||||||
return super(BaseList, self).__setslice__(*args, **kwargs)
|
|
||||||
|
|
||||||
def __delslice__(self, *args, **kwargs):
|
|
||||||
self._mark_as_changed()
|
|
||||||
return super(BaseList, self).__delslice__(*args, **kwargs)
|
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
self.instance = None
|
self.instance = None
|
||||||
self._dereferenced = False
|
self._dereferenced = False
|
||||||
@@ -156,41 +143,40 @@ class BaseList(list):
|
|||||||
self = state
|
self = state
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __iadd__(self, other):
|
def __setitem__(self, key, value):
|
||||||
self._mark_as_changed()
|
changed_key = key
|
||||||
return super(BaseList, self).__iadd__(other)
|
if isinstance(key, slice):
|
||||||
|
# In case of slice, we don't bother to identify the exact elements being updated
|
||||||
|
# instead, we simply marks the whole list as changed
|
||||||
|
changed_key = None
|
||||||
|
|
||||||
def __imul__(self, other):
|
result = super(BaseList, self).__setitem__(key, value)
|
||||||
self._mark_as_changed()
|
self._mark_as_changed(changed_key)
|
||||||
return super(BaseList, self).__imul__(other)
|
return result
|
||||||
|
|
||||||
def append(self, *args, **kwargs):
|
append = mark_as_changed_wrapper(list.append)
|
||||||
self._mark_as_changed()
|
extend = mark_as_changed_wrapper(list.extend)
|
||||||
return super(BaseList, self).append(*args, **kwargs)
|
insert = mark_as_changed_wrapper(list.insert)
|
||||||
|
pop = mark_as_changed_wrapper(list.pop)
|
||||||
|
remove = mark_as_changed_wrapper(list.remove)
|
||||||
|
reverse = mark_as_changed_wrapper(list.reverse)
|
||||||
|
sort = mark_as_changed_wrapper(list.sort)
|
||||||
|
__delitem__ = mark_as_changed_wrapper(list.__delitem__)
|
||||||
|
__iadd__ = mark_as_changed_wrapper(list.__iadd__)
|
||||||
|
__imul__ = mark_as_changed_wrapper(list.__imul__)
|
||||||
|
|
||||||
def extend(self, *args, **kwargs):
|
if six.PY2:
|
||||||
self._mark_as_changed()
|
# Under py3 __setslice__, __delslice__ and __getslice__
|
||||||
return super(BaseList, self).extend(*args, **kwargs)
|
# are replaced by __setitem__, __delitem__ and __getitem__ with a slice as parameter
|
||||||
|
# so we mimic this under python 2
|
||||||
|
def __setslice__(self, i, j, sequence):
|
||||||
|
return self.__setitem__(slice(i, j), sequence)
|
||||||
|
|
||||||
def insert(self, *args, **kwargs):
|
def __delslice__(self, i, j):
|
||||||
self._mark_as_changed()
|
return self.__delitem__(slice(i, j))
|
||||||
return super(BaseList, self).insert(*args, **kwargs)
|
|
||||||
|
|
||||||
def pop(self, *args, **kwargs):
|
def __getslice__(self, i, j):
|
||||||
self._mark_as_changed()
|
return self.__getitem__(slice(i, j))
|
||||||
return super(BaseList, self).pop(*args, **kwargs)
|
|
||||||
|
|
||||||
def remove(self, *args, **kwargs):
|
|
||||||
self._mark_as_changed()
|
|
||||||
return super(BaseList, self).remove(*args, **kwargs)
|
|
||||||
|
|
||||||
def reverse(self):
|
|
||||||
self._mark_as_changed()
|
|
||||||
return super(BaseList, self).reverse()
|
|
||||||
|
|
||||||
def sort(self, *args, **kwargs):
|
|
||||||
self._mark_as_changed()
|
|
||||||
return super(BaseList, self).sort(*args, **kwargs)
|
|
||||||
|
|
||||||
def _mark_as_changed(self, key=None):
|
def _mark_as_changed(self, key=None):
|
||||||
if hasattr(self._instance, '_mark_as_changed'):
|
if hasattr(self._instance, '_mark_as_changed'):
|
||||||
@@ -204,6 +190,10 @@ class BaseList(list):
|
|||||||
|
|
||||||
class EmbeddedDocumentList(BaseList):
|
class EmbeddedDocumentList(BaseList):
|
||||||
|
|
||||||
|
def __init__(self, list_items, instance, name):
|
||||||
|
super(EmbeddedDocumentList, self).__init__(list_items, instance, name)
|
||||||
|
self._instance = instance
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __match_all(cls, embedded_doc, kwargs):
|
def __match_all(cls, embedded_doc, kwargs):
|
||||||
"""Return True if a given embedded doc matches all the filter
|
"""Return True if a given embedded doc matches all the filter
|
||||||
@@ -222,10 +212,6 @@ class EmbeddedDocumentList(BaseList):
|
|||||||
return embedded_docs
|
return embedded_docs
|
||||||
return [doc for doc in embedded_docs if cls.__match_all(doc, kwargs)]
|
return [doc for doc in embedded_docs if cls.__match_all(doc, kwargs)]
|
||||||
|
|
||||||
def __init__(self, list_items, instance, name):
|
|
||||||
super(EmbeddedDocumentList, self).__init__(list_items, instance, name)
|
|
||||||
self._instance = instance
|
|
||||||
|
|
||||||
def filter(self, **kwargs):
|
def filter(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Filters the list by only including embedded documents with the
|
Filters the list by only including embedded documents with the
|
||||||
@@ -378,7 +364,7 @@ class StrictDict(object):
|
|||||||
_classes = {}
|
_classes = {}
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
for k, v in kwargs.iteritems():
|
for k, v in iteritems(kwargs):
|
||||||
setattr(self, k, v)
|
setattr(self, k, v)
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
@@ -426,7 +412,7 @@ class StrictDict(object):
|
|||||||
return (key for key in self.__slots__ if hasattr(self, key))
|
return (key for key in self.__slots__ if hasattr(self, key))
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(list(self.iteritems()))
|
return len(list(iteritems(self)))
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.items() == other.items()
|
return self.items() == other.items()
|
||||||
|
@@ -2,11 +2,10 @@ import copy
|
|||||||
import numbers
|
import numbers
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from bson import ObjectId, json_util
|
from bson import DBRef, ObjectId, SON, json_util
|
||||||
from bson.dbref import DBRef
|
|
||||||
from bson.son import SON
|
|
||||||
import pymongo
|
import pymongo
|
||||||
import six
|
import six
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
from mongoengine import signals
|
from mongoengine import signals
|
||||||
from mongoengine.base.common import get_document
|
from mongoengine.base.common import get_document
|
||||||
@@ -85,7 +84,7 @@ class BaseDocument(object):
|
|||||||
self._dynamic_fields = SON()
|
self._dynamic_fields = SON()
|
||||||
|
|
||||||
# Assign default values to instance
|
# Assign default values to instance
|
||||||
for key, field in self._fields.iteritems():
|
for key, field in iteritems(self._fields):
|
||||||
if self._db_field_map.get(key, key) in __only_fields:
|
if self._db_field_map.get(key, key) in __only_fields:
|
||||||
continue
|
continue
|
||||||
value = getattr(self, key, None)
|
value = getattr(self, key, None)
|
||||||
@@ -97,14 +96,14 @@ class BaseDocument(object):
|
|||||||
# Set passed values after initialisation
|
# Set passed values after initialisation
|
||||||
if self._dynamic:
|
if self._dynamic:
|
||||||
dynamic_data = {}
|
dynamic_data = {}
|
||||||
for key, value in values.iteritems():
|
for key, value in iteritems(values):
|
||||||
if key in self._fields or key == '_id':
|
if key in self._fields or key == '_id':
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
else:
|
else:
|
||||||
dynamic_data[key] = value
|
dynamic_data[key] = value
|
||||||
else:
|
else:
|
||||||
FileField = _import_class('FileField')
|
FileField = _import_class('FileField')
|
||||||
for key, value in values.iteritems():
|
for key, value in iteritems(values):
|
||||||
key = self._reverse_db_field_map.get(key, key)
|
key = self._reverse_db_field_map.get(key, key)
|
||||||
if key in self._fields or key in ('id', 'pk', '_cls'):
|
if key in self._fields or key in ('id', 'pk', '_cls'):
|
||||||
if __auto_convert and value is not None:
|
if __auto_convert and value is not None:
|
||||||
@@ -120,7 +119,7 @@ class BaseDocument(object):
|
|||||||
|
|
||||||
if self._dynamic:
|
if self._dynamic:
|
||||||
self._dynamic_lock = False
|
self._dynamic_lock = False
|
||||||
for key, value in dynamic_data.iteritems():
|
for key, value in iteritems(dynamic_data):
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
# Flag initialised
|
# Flag initialised
|
||||||
@@ -515,7 +514,7 @@ class BaseDocument(object):
|
|||||||
if not hasattr(data, 'items'):
|
if not hasattr(data, 'items'):
|
||||||
iterator = enumerate(data)
|
iterator = enumerate(data)
|
||||||
else:
|
else:
|
||||||
iterator = data.iteritems()
|
iterator = iteritems(data)
|
||||||
|
|
||||||
for index_or_key, value in iterator:
|
for index_or_key, value in iterator:
|
||||||
item_key = '%s%s.' % (base_key, index_or_key)
|
item_key = '%s%s.' % (base_key, index_or_key)
|
||||||
@@ -680,7 +679,7 @@ class BaseDocument(object):
|
|||||||
# Convert SON to a data dict, making sure each key is a string and
|
# Convert SON to a data dict, making sure each key is a string and
|
||||||
# corresponds to the right db field.
|
# corresponds to the right db field.
|
||||||
data = {}
|
data = {}
|
||||||
for key, value in son.iteritems():
|
for key, value in iteritems(son):
|
||||||
key = str(key)
|
key = str(key)
|
||||||
key = cls._db_field_map.get(key, key)
|
key = cls._db_field_map.get(key, key)
|
||||||
data[key] = value
|
data[key] = value
|
||||||
@@ -696,7 +695,7 @@ class BaseDocument(object):
|
|||||||
if not _auto_dereference:
|
if not _auto_dereference:
|
||||||
fields = copy.deepcopy(fields)
|
fields = copy.deepcopy(fields)
|
||||||
|
|
||||||
for field_name, field in fields.iteritems():
|
for field_name, field in iteritems(fields):
|
||||||
field._auto_dereference = _auto_dereference
|
field._auto_dereference = _auto_dereference
|
||||||
if field.db_field in data:
|
if field.db_field in data:
|
||||||
value = data[field.db_field]
|
value = data[field.db_field]
|
||||||
@@ -717,7 +716,7 @@ class BaseDocument(object):
|
|||||||
|
|
||||||
# In STRICT documents, remove any keys that aren't in cls._fields
|
# In STRICT documents, remove any keys that aren't in cls._fields
|
||||||
if cls.STRICT:
|
if cls.STRICT:
|
||||||
data = {k: v for k, v in data.iteritems() if k in cls._fields}
|
data = {k: v for k, v in iteritems(data) if k in cls._fields}
|
||||||
|
|
||||||
obj = cls(__auto_convert=False, _created=created, __only_fields=only_fields, **data)
|
obj = cls(__auto_convert=False, _created=created, __only_fields=only_fields, **data)
|
||||||
obj._changed_fields = changed_fields
|
obj._changed_fields = changed_fields
|
||||||
|
@@ -5,6 +5,7 @@ import weakref
|
|||||||
from bson import DBRef, ObjectId, SON
|
from bson import DBRef, ObjectId, SON
|
||||||
import pymongo
|
import pymongo
|
||||||
import six
|
import six
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
from mongoengine.base.common import UPDATE_OPERATORS
|
from mongoengine.base.common import UPDATE_OPERATORS
|
||||||
from mongoengine.base.datastructures import (BaseDict, BaseList,
|
from mongoengine.base.datastructures import (BaseDict, BaseList,
|
||||||
@@ -382,11 +383,11 @@ class ComplexBaseField(BaseField):
|
|||||||
if self.field:
|
if self.field:
|
||||||
value_dict = {
|
value_dict = {
|
||||||
key: self.field._to_mongo_safe_call(item, use_db_field, fields)
|
key: self.field._to_mongo_safe_call(item, use_db_field, fields)
|
||||||
for key, item in value.iteritems()
|
for key, item in iteritems(value)
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
value_dict = {}
|
value_dict = {}
|
||||||
for k, v in value.iteritems():
|
for k, v in iteritems(value):
|
||||||
if isinstance(v, Document):
|
if isinstance(v, Document):
|
||||||
# We need the id from the saved object to create the DBRef
|
# We need the id from the saved object to create the DBRef
|
||||||
if v.pk is None:
|
if v.pk is None:
|
||||||
@@ -423,7 +424,7 @@ class ComplexBaseField(BaseField):
|
|||||||
errors = {}
|
errors = {}
|
||||||
if self.field:
|
if self.field:
|
||||||
if hasattr(value, 'iteritems') or hasattr(value, 'items'):
|
if hasattr(value, 'iteritems') or hasattr(value, 'items'):
|
||||||
sequence = value.iteritems()
|
sequence = iteritems(value)
|
||||||
else:
|
else:
|
||||||
sequence = enumerate(value)
|
sequence = enumerate(value)
|
||||||
for k, v in sequence:
|
for k, v in sequence:
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
from six import iteritems, itervalues
|
||||||
|
|
||||||
from mongoengine.base.common import _document_registry
|
from mongoengine.base.common import _document_registry
|
||||||
from mongoengine.base.fields import BaseField, ComplexBaseField, ObjectIdField
|
from mongoengine.base.fields import BaseField, ComplexBaseField, ObjectIdField
|
||||||
@@ -62,7 +63,7 @@ class DocumentMetaclass(type):
|
|||||||
# Standard object mixin - merge in any Fields
|
# Standard object mixin - merge in any Fields
|
||||||
if not hasattr(base, '_meta'):
|
if not hasattr(base, '_meta'):
|
||||||
base_fields = {}
|
base_fields = {}
|
||||||
for attr_name, attr_value in base.__dict__.iteritems():
|
for attr_name, attr_value in iteritems(base.__dict__):
|
||||||
if not isinstance(attr_value, BaseField):
|
if not isinstance(attr_value, BaseField):
|
||||||
continue
|
continue
|
||||||
attr_value.name = attr_name
|
attr_value.name = attr_name
|
||||||
@@ -74,7 +75,7 @@ class DocumentMetaclass(type):
|
|||||||
|
|
||||||
# Discover any document fields
|
# Discover any document fields
|
||||||
field_names = {}
|
field_names = {}
|
||||||
for attr_name, attr_value in attrs.iteritems():
|
for attr_name, attr_value in iteritems(attrs):
|
||||||
if not isinstance(attr_value, BaseField):
|
if not isinstance(attr_value, BaseField):
|
||||||
continue
|
continue
|
||||||
attr_value.name = attr_name
|
attr_value.name = attr_name
|
||||||
@@ -103,7 +104,7 @@ class DocumentMetaclass(type):
|
|||||||
|
|
||||||
attrs['_fields_ordered'] = tuple(i[1] for i in sorted(
|
attrs['_fields_ordered'] = tuple(i[1] for i in sorted(
|
||||||
(v.creation_counter, v.name)
|
(v.creation_counter, v.name)
|
||||||
for v in doc_fields.itervalues()))
|
for v in itervalues(doc_fields)))
|
||||||
|
|
||||||
#
|
#
|
||||||
# Set document hierarchy
|
# Set document hierarchy
|
||||||
@@ -173,7 +174,7 @@ class DocumentMetaclass(type):
|
|||||||
f.__dict__.update({'im_self': getattr(f, '__self__')})
|
f.__dict__.update({'im_self': getattr(f, '__self__')})
|
||||||
|
|
||||||
# Handle delete rules
|
# Handle delete rules
|
||||||
for field in new_class._fields.itervalues():
|
for field in itervalues(new_class._fields):
|
||||||
f = field
|
f = field
|
||||||
if f.owner_document is None:
|
if f.owner_document is None:
|
||||||
f.owner_document = new_class
|
f.owner_document = new_class
|
||||||
@@ -375,7 +376,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
|
|||||||
new_class.objects = QuerySetManager()
|
new_class.objects = QuerySetManager()
|
||||||
|
|
||||||
# Validate the fields and set primary key if needed
|
# Validate the fields and set primary key if needed
|
||||||
for field_name, field in new_class._fields.iteritems():
|
for field_name, field in iteritems(new_class._fields):
|
||||||
if field.primary_key:
|
if field.primary_key:
|
||||||
# Ensure only one primary key is set
|
# Ensure only one primary key is set
|
||||||
current_pk = new_class._meta.get('id_field')
|
current_pk = new_class._meta.get('id_field')
|
||||||
@@ -438,7 +439,7 @@ class MetaDict(dict):
|
|||||||
_merge_options = ('indexes',)
|
_merge_options = ('indexes',)
|
||||||
|
|
||||||
def merge(self, new_options):
|
def merge(self, new_options):
|
||||||
for k, v in new_options.iteritems():
|
for k, v in iteritems(new_options):
|
||||||
if k in self._merge_options:
|
if k in self._merge_options:
|
||||||
self[k] = self.get(k, []) + v
|
self[k] = self.get(k, []) + v
|
||||||
else:
|
else:
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
from pymongo import MongoClient, ReadPreference, uri_parser
|
from pymongo import MongoClient, ReadPreference, uri_parser
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from mongoengine.python_support import IS_PYMONGO_3
|
from mongoengine.pymongo_support import IS_PYMONGO_3
|
||||||
|
|
||||||
__all__ = ['MongoEngineConnectionError', 'connect', 'register_connection',
|
__all__ = ['MongoEngineConnectionError', 'connect', 'register_connection',
|
||||||
'DEFAULT_CONNECTION_NAME']
|
'DEFAULT_CONNECTION_NAME', 'get_db']
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_CONNECTION_NAME = 'default'
|
DEFAULT_CONNECTION_NAME = 'default'
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
from pymongo.write_concern import WriteConcern
|
from pymongo.write_concern import WriteConcern
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
from mongoengine.common import _import_class
|
from mongoengine.common import _import_class
|
||||||
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
|
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
|
||||||
|
from mongoengine.pymongo_support import count_documents
|
||||||
|
|
||||||
__all__ = ('switch_db', 'switch_collection', 'no_dereference',
|
__all__ = ('switch_db', 'switch_collection', 'no_dereference',
|
||||||
'no_sub_classes', 'query_counter', 'set_write_concern')
|
'no_sub_classes', 'query_counter', 'set_write_concern')
|
||||||
@@ -112,7 +115,7 @@ class no_dereference(object):
|
|||||||
GenericReferenceField = _import_class('GenericReferenceField')
|
GenericReferenceField = _import_class('GenericReferenceField')
|
||||||
ComplexBaseField = _import_class('ComplexBaseField')
|
ComplexBaseField = _import_class('ComplexBaseField')
|
||||||
|
|
||||||
self.deref_fields = [k for k, v in self.cls._fields.iteritems()
|
self.deref_fields = [k for k, v in iteritems(self.cls._fields)
|
||||||
if isinstance(v, (ReferenceField,
|
if isinstance(v, (ReferenceField,
|
||||||
GenericReferenceField,
|
GenericReferenceField,
|
||||||
ComplexBaseField))]
|
ComplexBaseField))]
|
||||||
@@ -235,7 +238,7 @@ class query_counter(object):
|
|||||||
and substracting the queries issued by this context. In fact everytime this is called, 1 query is
|
and substracting the queries issued by this context. In fact everytime this is called, 1 query is
|
||||||
issued so we need to balance that
|
issued so we need to balance that
|
||||||
"""
|
"""
|
||||||
count = self.db.system.profile.find(self._ignored_query).count() - self._ctx_query_counter
|
count = count_documents(self.db.system.profile, self._ignored_query) - self._ctx_query_counter
|
||||||
self._ctx_query_counter += 1 # Account for the query we just issued to gather the information
|
self._ctx_query_counter += 1 # Account for the query we just issued to gather the information
|
||||||
return count
|
return count
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
from bson import DBRef, SON
|
from bson import DBRef, SON
|
||||||
import six
|
import six
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
from mongoengine.base import (BaseDict, BaseList, EmbeddedDocumentList,
|
from mongoengine.base import (BaseDict, BaseList, EmbeddedDocumentList,
|
||||||
TopLevelDocumentMetaclass, get_document)
|
TopLevelDocumentMetaclass, get_document)
|
||||||
@@ -52,26 +53,40 @@ class DeReference(object):
|
|||||||
[i.__class__ == doc_type for i in items.values()]):
|
[i.__class__ == doc_type for i in items.values()]):
|
||||||
return items
|
return items
|
||||||
elif not field.dbref:
|
elif not field.dbref:
|
||||||
|
# We must turn the ObjectIds into DBRefs
|
||||||
|
|
||||||
|
# Recursively dig into the sub items of a list/dict
|
||||||
|
# to turn the ObjectIds into DBRefs
|
||||||
|
def _get_items_from_list(items):
|
||||||
|
new_items = []
|
||||||
|
for v in items:
|
||||||
|
value = v
|
||||||
|
if isinstance(v, dict):
|
||||||
|
value = _get_items_from_dict(v)
|
||||||
|
elif isinstance(v, list):
|
||||||
|
value = _get_items_from_list(v)
|
||||||
|
elif not isinstance(v, (DBRef, Document)):
|
||||||
|
value = field.to_python(v)
|
||||||
|
new_items.append(value)
|
||||||
|
return new_items
|
||||||
|
|
||||||
|
def _get_items_from_dict(items):
|
||||||
|
new_items = {}
|
||||||
|
for k, v in iteritems(items):
|
||||||
|
value = v
|
||||||
|
if isinstance(v, list):
|
||||||
|
value = _get_items_from_list(v)
|
||||||
|
elif isinstance(v, dict):
|
||||||
|
value = _get_items_from_dict(v)
|
||||||
|
elif not isinstance(v, (DBRef, Document)):
|
||||||
|
value = field.to_python(v)
|
||||||
|
new_items[k] = value
|
||||||
|
return new_items
|
||||||
|
|
||||||
if not hasattr(items, 'items'):
|
if not hasattr(items, 'items'):
|
||||||
|
items = _get_items_from_list(items)
|
||||||
def _get_items(items):
|
|
||||||
new_items = []
|
|
||||||
for v in items:
|
|
||||||
if isinstance(v, list):
|
|
||||||
new_items.append(_get_items(v))
|
|
||||||
elif not isinstance(v, (DBRef, Document)):
|
|
||||||
new_items.append(field.to_python(v))
|
|
||||||
else:
|
|
||||||
new_items.append(v)
|
|
||||||
return new_items
|
|
||||||
|
|
||||||
items = _get_items(items)
|
|
||||||
else:
|
else:
|
||||||
items = {
|
items = _get_items_from_dict(items)
|
||||||
k: (v if isinstance(v, (DBRef, Document))
|
|
||||||
else field.to_python(v))
|
|
||||||
for k, v in items.iteritems()
|
|
||||||
}
|
|
||||||
|
|
||||||
self.reference_map = self._find_references(items)
|
self.reference_map = self._find_references(items)
|
||||||
self.object_map = self._fetch_objects(doc_type=doc_type)
|
self.object_map = self._fetch_objects(doc_type=doc_type)
|
||||||
@@ -98,7 +113,7 @@ class DeReference(object):
|
|||||||
depth += 1
|
depth += 1
|
||||||
for item in iterator:
|
for item in iterator:
|
||||||
if isinstance(item, (Document, EmbeddedDocument)):
|
if isinstance(item, (Document, EmbeddedDocument)):
|
||||||
for field_name, field in item._fields.iteritems():
|
for field_name, field in iteritems(item._fields):
|
||||||
v = item._data.get(field_name, None)
|
v = item._data.get(field_name, None)
|
||||||
if isinstance(v, LazyReference):
|
if isinstance(v, LazyReference):
|
||||||
# LazyReference inherits DBRef but should not be dereferenced here !
|
# LazyReference inherits DBRef but should not be dereferenced here !
|
||||||
@@ -110,7 +125,7 @@ class DeReference(object):
|
|||||||
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
|
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
|
||||||
field_cls = getattr(getattr(field, 'field', None), 'document_type', None)
|
field_cls = getattr(getattr(field, 'field', None), 'document_type', None)
|
||||||
references = self._find_references(v, depth)
|
references = self._find_references(v, depth)
|
||||||
for key, refs in references.iteritems():
|
for key, refs in iteritems(references):
|
||||||
if isinstance(field_cls, (Document, TopLevelDocumentMetaclass)):
|
if isinstance(field_cls, (Document, TopLevelDocumentMetaclass)):
|
||||||
key = field_cls
|
key = field_cls
|
||||||
reference_map.setdefault(key, set()).update(refs)
|
reference_map.setdefault(key, set()).update(refs)
|
||||||
@@ -123,7 +138,7 @@ class DeReference(object):
|
|||||||
reference_map.setdefault(get_document(item['_cls']), set()).add(item['_ref'].id)
|
reference_map.setdefault(get_document(item['_cls']), set()).add(item['_ref'].id)
|
||||||
elif isinstance(item, (dict, list, tuple)) and depth - 1 <= self.max_depth:
|
elif isinstance(item, (dict, list, tuple)) and depth - 1 <= self.max_depth:
|
||||||
references = self._find_references(item, depth - 1)
|
references = self._find_references(item, depth - 1)
|
||||||
for key, refs in references.iteritems():
|
for key, refs in iteritems(references):
|
||||||
reference_map.setdefault(key, set()).update(refs)
|
reference_map.setdefault(key, set()).update(refs)
|
||||||
|
|
||||||
return reference_map
|
return reference_map
|
||||||
@@ -132,7 +147,7 @@ class DeReference(object):
|
|||||||
"""Fetch all references and convert to their document objects
|
"""Fetch all references and convert to their document objects
|
||||||
"""
|
"""
|
||||||
object_map = {}
|
object_map = {}
|
||||||
for collection, dbrefs in self.reference_map.iteritems():
|
for collection, dbrefs in iteritems(self.reference_map):
|
||||||
|
|
||||||
# we use getattr instead of hasattr because hasattr swallows any exception under python2
|
# we use getattr instead of hasattr because hasattr swallows any exception under python2
|
||||||
# so it could hide nasty things without raising exceptions (cfr bug #1688))
|
# so it could hide nasty things without raising exceptions (cfr bug #1688))
|
||||||
@@ -143,7 +158,7 @@ class DeReference(object):
|
|||||||
refs = [dbref for dbref in dbrefs
|
refs = [dbref for dbref in dbrefs
|
||||||
if (col_name, dbref) not in object_map]
|
if (col_name, dbref) not in object_map]
|
||||||
references = collection.objects.in_bulk(refs)
|
references = collection.objects.in_bulk(refs)
|
||||||
for key, doc in references.iteritems():
|
for key, doc in iteritems(references):
|
||||||
object_map[(col_name, key)] = doc
|
object_map[(col_name, key)] = doc
|
||||||
else: # Generic reference: use the refs data to convert to document
|
else: # Generic reference: use the refs data to convert to document
|
||||||
if isinstance(doc_type, (ListField, DictField, MapField)):
|
if isinstance(doc_type, (ListField, DictField, MapField)):
|
||||||
@@ -215,7 +230,7 @@ class DeReference(object):
|
|||||||
data = []
|
data = []
|
||||||
else:
|
else:
|
||||||
is_list = False
|
is_list = False
|
||||||
iterator = items.iteritems()
|
iterator = iteritems(items)
|
||||||
data = {}
|
data = {}
|
||||||
|
|
||||||
depth += 1
|
depth += 1
|
||||||
|
@@ -5,6 +5,7 @@ from bson.dbref import DBRef
|
|||||||
import pymongo
|
import pymongo
|
||||||
from pymongo.read_preferences import ReadPreference
|
from pymongo.read_preferences import ReadPreference
|
||||||
import six
|
import six
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
from mongoengine import signals
|
from mongoengine import signals
|
||||||
from mongoengine.base import (BaseDict, BaseDocument, BaseList,
|
from mongoengine.base import (BaseDict, BaseDocument, BaseList,
|
||||||
@@ -17,7 +18,7 @@ from mongoengine.context_managers import (set_write_concern,
|
|||||||
switch_db)
|
switch_db)
|
||||||
from mongoengine.errors import (InvalidDocumentError, InvalidQueryError,
|
from mongoengine.errors import (InvalidDocumentError, InvalidQueryError,
|
||||||
SaveConditionError)
|
SaveConditionError)
|
||||||
from mongoengine.python_support import IS_PYMONGO_3
|
from mongoengine.pymongo_support import IS_PYMONGO_3, list_collection_names
|
||||||
from mongoengine.queryset import (NotUniqueError, OperationError,
|
from mongoengine.queryset import (NotUniqueError, OperationError,
|
||||||
QuerySet, transform)
|
QuerySet, transform)
|
||||||
|
|
||||||
@@ -90,9 +91,15 @@ class EmbeddedDocument(six.with_metaclass(DocumentMetaclass, BaseDocument)):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
warnings.warn("EmbeddedDocument.save is deprecated and will be removed in a next version of mongoengine."
|
||||||
|
"Use the parent document's .save() or ._instance.save()",
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
self._instance.save(*args, **kwargs)
|
self._instance.save(*args, **kwargs)
|
||||||
|
|
||||||
def reload(self, *args, **kwargs):
|
def reload(self, *args, **kwargs):
|
||||||
|
warnings.warn("EmbeddedDocument.reload is deprecated and will be removed in a next version of mongoengine."
|
||||||
|
"Use the parent document's .reload() or ._instance.reload()",
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
self._instance.reload(*args, **kwargs)
|
self._instance.reload(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@@ -221,7 +228,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
|||||||
|
|
||||||
# If the collection already exists and has different options
|
# If the collection already exists and has different options
|
||||||
# (i.e. isn't capped or has different max/size), raise an error.
|
# (i.e. isn't capped or has different max/size), raise an error.
|
||||||
if collection_name in db.collection_names():
|
if collection_name in list_collection_names(db, include_system_collections=True):
|
||||||
collection = db[collection_name]
|
collection = db[collection_name]
|
||||||
options = collection.options()
|
options = collection.options()
|
||||||
if (
|
if (
|
||||||
@@ -607,7 +614,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
|||||||
|
|
||||||
# Delete FileFields separately
|
# Delete FileFields separately
|
||||||
FileField = _import_class('FileField')
|
FileField = _import_class('FileField')
|
||||||
for name, field in self._fields.iteritems():
|
for name, field in iteritems(self._fields):
|
||||||
if isinstance(field, FileField):
|
if isinstance(field, FileField):
|
||||||
getattr(self, name).delete()
|
getattr(self, name).delete()
|
||||||
|
|
||||||
@@ -1016,7 +1023,7 @@ class DynamicDocument(six.with_metaclass(TopLevelDocumentMetaclass, Document)):
|
|||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
There is one caveat on Dynamic Documents: fields cannot start with `_`
|
There is one caveat on Dynamic Documents: undeclared fields cannot start with `_`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# The __metaclass__ attribute is removed by 2to3 when running with Python3
|
# The __metaclass__ attribute is removed by 2to3 when running with Python3
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
__all__ = ('NotRegistered', 'InvalidDocumentError', 'LookUpError',
|
__all__ = ('NotRegistered', 'InvalidDocumentError', 'LookUpError',
|
||||||
'DoesNotExist', 'MultipleObjectsReturned', 'InvalidQueryError',
|
'DoesNotExist', 'MultipleObjectsReturned', 'InvalidQueryError',
|
||||||
@@ -113,7 +114,7 @@ class ValidationError(AssertionError):
|
|||||||
return errors_dict
|
return errors_dict
|
||||||
|
|
||||||
if isinstance(source, dict):
|
if isinstance(source, dict):
|
||||||
for field_name, error in source.iteritems():
|
for field_name, error in iteritems(source):
|
||||||
errors_dict[field_name] = build_dict(error)
|
errors_dict[field_name] = build_dict(error)
|
||||||
elif isinstance(source, ValidationError) and source.errors:
|
elif isinstance(source, ValidationError) and source.errors:
|
||||||
return build_dict(source.errors)
|
return build_dict(source.errors)
|
||||||
@@ -135,12 +136,12 @@ class ValidationError(AssertionError):
|
|||||||
value = ' '.join([generate_key(k) for k in value])
|
value = ' '.join([generate_key(k) for k in value])
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
value = ' '.join(
|
value = ' '.join(
|
||||||
[generate_key(v, k) for k, v in value.iteritems()])
|
[generate_key(v, k) for k, v in iteritems(value)])
|
||||||
|
|
||||||
results = '%s.%s' % (prefix, value) if prefix else value
|
results = '%s.%s' % (prefix, value) if prefix else value
|
||||||
return results
|
return results
|
||||||
|
|
||||||
error_dict = defaultdict(list)
|
error_dict = defaultdict(list)
|
||||||
for k, v in self.to_dict().iteritems():
|
for k, v in iteritems(self.to_dict()):
|
||||||
error_dict[generate_key(v)].append(k)
|
error_dict[generate_key(v)].append(k)
|
||||||
return ' '.join(['%s: %s' % (k, v) for k, v in error_dict.iteritems()])
|
return ' '.join(['%s: %s' % (k, v) for k, v in iteritems(error_dict)])
|
||||||
|
@@ -11,6 +11,7 @@ from bson import Binary, DBRef, ObjectId, SON
|
|||||||
import gridfs
|
import gridfs
|
||||||
import pymongo
|
import pymongo
|
||||||
import six
|
import six
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import dateutil
|
import dateutil
|
||||||
@@ -794,12 +795,12 @@ class DynamicField(BaseField):
|
|||||||
value = {k: v for k, v in enumerate(value)}
|
value = {k: v for k, v in enumerate(value)}
|
||||||
|
|
||||||
data = {}
|
data = {}
|
||||||
for k, v in value.iteritems():
|
for k, v in iteritems(value):
|
||||||
data[k] = self.to_mongo(v, use_db_field, fields)
|
data[k] = self.to_mongo(v, use_db_field, fields)
|
||||||
|
|
||||||
value = data
|
value = data
|
||||||
if is_list: # Convert back to a list
|
if is_list: # Convert back to a list
|
||||||
value = [v for k, v in sorted(data.iteritems(), key=itemgetter(0))]
|
value = [v for k, v in sorted(iteritems(data), key=itemgetter(0))]
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
@@ -944,7 +945,7 @@ def key_has_dot_or_dollar(d):
|
|||||||
dictionary contains a dot or a dollar sign.
|
dictionary contains a dot or a dollar sign.
|
||||||
"""
|
"""
|
||||||
for k, v in d.items():
|
for k, v in d.items():
|
||||||
if ('.' in k or '$' in k) or (isinstance(v, dict) and key_has_dot_or_dollar(v)):
|
if ('.' in k or k.startswith('$')) or (isinstance(v, dict) and key_has_dot_or_dollar(v)):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@@ -977,7 +978,7 @@ class DictField(ComplexBaseField):
|
|||||||
self.error(msg)
|
self.error(msg)
|
||||||
if key_has_dot_or_dollar(value):
|
if key_has_dot_or_dollar(value):
|
||||||
self.error('Invalid dictionary key name - keys may not contain "."'
|
self.error('Invalid dictionary key name - keys may not contain "."'
|
||||||
' or "$" characters')
|
' or startswith "$" characters')
|
||||||
super(DictField, self).validate(value)
|
super(DictField, self).validate(value)
|
||||||
|
|
||||||
def lookup_member(self, member_name):
|
def lookup_member(self, member_name):
|
||||||
@@ -1483,6 +1484,12 @@ class BinaryField(BaseField):
|
|||||||
if self.max_bytes is not None and len(value) > self.max_bytes:
|
if self.max_bytes is not None and len(value) > self.max_bytes:
|
||||||
self.error('Binary value is too long')
|
self.error('Binary value is too long')
|
||||||
|
|
||||||
|
def prepare_query_value(self, op, value):
|
||||||
|
if value is None:
|
||||||
|
return value
|
||||||
|
return super(BinaryField, self).prepare_query_value(
|
||||||
|
op, self.to_mongo(value))
|
||||||
|
|
||||||
|
|
||||||
class GridFSError(Exception):
|
class GridFSError(Exception):
|
||||||
pass
|
pass
|
||||||
@@ -2251,9 +2258,9 @@ class LazyReferenceField(BaseField):
|
|||||||
:param reverse_delete_rule: Determines what to do when the referring
|
:param reverse_delete_rule: Determines what to do when the referring
|
||||||
object is deleted
|
object is deleted
|
||||||
:param passthrough: When trying to access unknown fields, the
|
:param passthrough: When trying to access unknown fields, the
|
||||||
:class:`~mongoengine.base.datastructure.LazyReference` instance will
|
:class:`~mongoengine.base.datastructure.LazyReference` instance will
|
||||||
automatically call `fetch()` and try to retrive the field on the fetched
|
automatically call `fetch()` and try to retrive the field on the fetched
|
||||||
document. Note this only work getting field (not setting or deleting).
|
document. Note this only work getting field (not setting or deleting).
|
||||||
"""
|
"""
|
||||||
# XXX ValidationError raised outside of the "validate" method.
|
# XXX ValidationError raised outside of the "validate" method.
|
||||||
if (
|
if (
|
||||||
|
21
mongoengine/mongodb_support.py
Normal file
21
mongoengine/mongodb_support.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
"""
|
||||||
|
Helper functions, constants, and types to aid with MongoDB version support
|
||||||
|
"""
|
||||||
|
from mongoengine.connection import get_connection
|
||||||
|
|
||||||
|
|
||||||
|
# Constant that can be used to compare the version retrieved with
|
||||||
|
# get_mongodb_version()
|
||||||
|
MONGODB_34 = (3, 4)
|
||||||
|
MONGODB_32 = (3, 2)
|
||||||
|
MONGODB_3 = (3, 0)
|
||||||
|
MONGODB_26 = (2, 6)
|
||||||
|
|
||||||
|
|
||||||
|
def get_mongodb_version():
|
||||||
|
"""Return the version of the connected mongoDB (first 2 digits)
|
||||||
|
|
||||||
|
:return: tuple(int, int)
|
||||||
|
"""
|
||||||
|
version_list = get_connection().server_info()['versionArray'][:2] # e.g: (3, 2)
|
||||||
|
return tuple(version_list)
|
33
mongoengine/pymongo_support.py
Normal file
33
mongoengine/pymongo_support.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
"""
|
||||||
|
Helper functions, constants, and types to aid with PyMongo v2.7 - v3.x support.
|
||||||
|
"""
|
||||||
|
import pymongo
|
||||||
|
|
||||||
|
_PYMONGO_37 = (3, 7)
|
||||||
|
|
||||||
|
PYMONGO_VERSION = tuple(pymongo.version_tuple[:2])
|
||||||
|
|
||||||
|
IS_PYMONGO_3 = PYMONGO_VERSION[0] >= 3
|
||||||
|
IS_PYMONGO_GTE_37 = PYMONGO_VERSION >= _PYMONGO_37
|
||||||
|
|
||||||
|
|
||||||
|
def count_documents(collection, filter):
|
||||||
|
"""Pymongo>3.7 deprecates count in favour of count_documents"""
|
||||||
|
if IS_PYMONGO_GTE_37:
|
||||||
|
return collection.count_documents(filter)
|
||||||
|
else:
|
||||||
|
count = collection.find(filter).count()
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
|
def list_collection_names(db, include_system_collections=False):
|
||||||
|
"""Pymongo>3.7 deprecates collection_names in favour of list_collection_names"""
|
||||||
|
if IS_PYMONGO_GTE_37:
|
||||||
|
collections = db.list_collection_names()
|
||||||
|
else:
|
||||||
|
collections = db.collection_names()
|
||||||
|
|
||||||
|
if not include_system_collections:
|
||||||
|
collections = [c for c in collections if not c.startswith('system.')]
|
||||||
|
|
||||||
|
return collections
|
@@ -1,13 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
Helper functions, constants, and types to aid with Python v2.7 - v3.x and
|
Helper functions, constants, and types to aid with Python v2.7 - v3.x support
|
||||||
PyMongo v2.7 - v3.x support.
|
|
||||||
"""
|
"""
|
||||||
import pymongo
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
|
||||||
IS_PYMONGO_3 = pymongo.version_tuple[0] >= 3
|
|
||||||
|
|
||||||
# six.BytesIO resolves to StringIO.StringIO in Py2 and io.BytesIO in Py3.
|
# six.BytesIO resolves to StringIO.StringIO in Py2 and io.BytesIO in Py3.
|
||||||
StringIO = six.BytesIO
|
StringIO = six.BytesIO
|
||||||
|
|
||||||
|
@@ -12,6 +12,7 @@ import pymongo
|
|||||||
import pymongo.errors
|
import pymongo.errors
|
||||||
from pymongo.common import validate_read_preference
|
from pymongo.common import validate_read_preference
|
||||||
import six
|
import six
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
from mongoengine import signals
|
from mongoengine import signals
|
||||||
from mongoengine.base import get_document
|
from mongoengine.base import get_document
|
||||||
@@ -20,7 +21,7 @@ from mongoengine.connection import get_db
|
|||||||
from mongoengine.context_managers import set_write_concern, switch_db
|
from mongoengine.context_managers import set_write_concern, switch_db
|
||||||
from mongoengine.errors import (InvalidQueryError, LookUpError,
|
from mongoengine.errors import (InvalidQueryError, LookUpError,
|
||||||
NotUniqueError, OperationError)
|
NotUniqueError, OperationError)
|
||||||
from mongoengine.python_support import IS_PYMONGO_3
|
from mongoengine.pymongo_support import IS_PYMONGO_3
|
||||||
from mongoengine.queryset import transform
|
from mongoengine.queryset import transform
|
||||||
from mongoengine.queryset.field_list import QueryFieldList
|
from mongoengine.queryset.field_list import QueryFieldList
|
||||||
from mongoengine.queryset.visitor import Q, QNode
|
from mongoengine.queryset.visitor import Q, QNode
|
||||||
@@ -188,7 +189,7 @@ class BaseQuerySet(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if queryset._as_pymongo:
|
if queryset._as_pymongo:
|
||||||
return queryset._get_as_pymongo(queryset._cursor[key])
|
return queryset._cursor[key]
|
||||||
|
|
||||||
return queryset._document._from_son(
|
return queryset._document._from_son(
|
||||||
queryset._cursor[key],
|
queryset._cursor[key],
|
||||||
@@ -215,7 +216,7 @@ class BaseQuerySet(object):
|
|||||||
# Core functions
|
# Core functions
|
||||||
|
|
||||||
def all(self):
|
def all(self):
|
||||||
"""Returns all documents."""
|
"""Returns a copy of the current QuerySet."""
|
||||||
return self.__call__()
|
return self.__call__()
|
||||||
|
|
||||||
def filter(self, *q_objs, **query):
|
def filter(self, *q_objs, **query):
|
||||||
@@ -396,7 +397,9 @@ class BaseQuerySet(object):
|
|||||||
"""
|
"""
|
||||||
if self._limit == 0 and with_limit_and_skip is False or self._none:
|
if self._limit == 0 and with_limit_and_skip is False or self._none:
|
||||||
return 0
|
return 0
|
||||||
return self._cursor.count(with_limit_and_skip=with_limit_and_skip)
|
count = self._cursor.count(with_limit_and_skip=with_limit_and_skip)
|
||||||
|
self._cursor_obj = None
|
||||||
|
return count
|
||||||
|
|
||||||
def delete(self, write_concern=None, _from_doc_delete=False,
|
def delete(self, write_concern=None, _from_doc_delete=False,
|
||||||
cascade_refs=None):
|
cascade_refs=None):
|
||||||
@@ -496,11 +499,12 @@ class BaseQuerySet(object):
|
|||||||
``save(..., write_concern={w: 2, fsync: True}, ...)`` will
|
``save(..., write_concern={w: 2, fsync: True}, ...)`` will
|
||||||
wait until at least two servers have recorded the write and
|
wait until at least two servers have recorded the write and
|
||||||
will force an fsync on the primary server.
|
will force an fsync on the primary server.
|
||||||
:param full_result: Return the full result dictionary rather than just the number
|
:param full_result: Return the associated ``pymongo.UpdateResult`` rather than just the number
|
||||||
updated, e.g. return
|
updated items
|
||||||
``{'n': 2, 'nModified': 2, 'ok': 1.0, 'updatedExisting': True}``.
|
|
||||||
:param update: Django-style update keyword arguments
|
:param update: Django-style update keyword arguments
|
||||||
|
|
||||||
|
:returns the number of updated documents (unless ``full_result`` is True)
|
||||||
|
|
||||||
.. versionadded:: 0.2
|
.. versionadded:: 0.2
|
||||||
"""
|
"""
|
||||||
if not update and not upsert:
|
if not update and not upsert:
|
||||||
@@ -564,7 +568,7 @@ class BaseQuerySet(object):
|
|||||||
document = self._document.objects.with_id(atomic_update.upserted_id)
|
document = self._document.objects.with_id(atomic_update.upserted_id)
|
||||||
return document
|
return document
|
||||||
|
|
||||||
def update_one(self, upsert=False, write_concern=None, **update):
|
def update_one(self, upsert=False, write_concern=None, full_result=False, **update):
|
||||||
"""Perform an atomic update on the fields of the first document
|
"""Perform an atomic update on the fields of the first document
|
||||||
matched by the query.
|
matched by the query.
|
||||||
|
|
||||||
@@ -575,12 +579,19 @@ class BaseQuerySet(object):
|
|||||||
``save(..., write_concern={w: 2, fsync: True}, ...)`` will
|
``save(..., write_concern={w: 2, fsync: True}, ...)`` will
|
||||||
wait until at least two servers have recorded the write and
|
wait until at least two servers have recorded the write and
|
||||||
will force an fsync on the primary server.
|
will force an fsync on the primary server.
|
||||||
|
:param full_result: Return the associated ``pymongo.UpdateResult`` rather than just the number
|
||||||
|
updated items
|
||||||
:param update: Django-style update keyword arguments
|
:param update: Django-style update keyword arguments
|
||||||
|
full_result
|
||||||
|
:returns the number of updated documents (unless ``full_result`` is True)
|
||||||
.. versionadded:: 0.2
|
.. versionadded:: 0.2
|
||||||
"""
|
"""
|
||||||
return self.update(
|
return self.update(
|
||||||
upsert=upsert, multi=False, write_concern=write_concern, **update)
|
upsert=upsert,
|
||||||
|
multi=False,
|
||||||
|
write_concern=write_concern,
|
||||||
|
full_result=full_result,
|
||||||
|
**update)
|
||||||
|
|
||||||
def modify(self, upsert=False, full_response=False, remove=False, new=False, **update):
|
def modify(self, upsert=False, full_response=False, remove=False, new=False, **update):
|
||||||
"""Update and return the updated document.
|
"""Update and return the updated document.
|
||||||
@@ -688,7 +699,7 @@ class BaseQuerySet(object):
|
|||||||
self._document._from_son(doc, only_fields=self.only_fields))
|
self._document._from_son(doc, only_fields=self.only_fields))
|
||||||
elif self._as_pymongo:
|
elif self._as_pymongo:
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
doc_map[doc['_id']] = self._get_as_pymongo(doc)
|
doc_map[doc['_id']] = doc
|
||||||
else:
|
else:
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
doc_map[doc['_id']] = self._document._from_son(
|
doc_map[doc['_id']] = self._document._from_son(
|
||||||
@@ -746,7 +757,7 @@ class BaseQuerySet(object):
|
|||||||
'_read_preference', '_iter', '_scalar', '_as_pymongo',
|
'_read_preference', '_iter', '_scalar', '_as_pymongo',
|
||||||
'_limit', '_skip', '_hint', '_auto_dereference',
|
'_limit', '_skip', '_hint', '_auto_dereference',
|
||||||
'_search_text', 'only_fields', '_max_time_ms',
|
'_search_text', 'only_fields', '_max_time_ms',
|
||||||
'_comment')
|
'_comment', '_batch_size')
|
||||||
|
|
||||||
for prop in copy_props:
|
for prop in copy_props:
|
||||||
val = getattr(self, prop)
|
val = getattr(self, prop)
|
||||||
@@ -1484,7 +1495,7 @@ class BaseQuerySet(object):
|
|||||||
raw_doc = six.next(self._cursor)
|
raw_doc = six.next(self._cursor)
|
||||||
|
|
||||||
if self._as_pymongo:
|
if self._as_pymongo:
|
||||||
return self._get_as_pymongo(raw_doc)
|
return raw_doc
|
||||||
|
|
||||||
doc = self._document._from_son(
|
doc = self._document._from_son(
|
||||||
raw_doc, _auto_dereference=self._auto_dereference,
|
raw_doc, _auto_dereference=self._auto_dereference,
|
||||||
@@ -1729,13 +1740,13 @@ class BaseQuerySet(object):
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
total, data, types = self.exec_js(freq_func, field)
|
total, data, types = self.exec_js(freq_func, field)
|
||||||
values = {types.get(k): int(v) for k, v in data.iteritems()}
|
values = {types.get(k): int(v) for k, v in iteritems(data)}
|
||||||
|
|
||||||
if normalize:
|
if normalize:
|
||||||
values = {k: float(v) / total for k, v in values.items()}
|
values = {k: float(v) / total for k, v in values.items()}
|
||||||
|
|
||||||
frequencies = {}
|
frequencies = {}
|
||||||
for k, v in values.iteritems():
|
for k, v in iteritems(values):
|
||||||
if isinstance(k, float):
|
if isinstance(k, float):
|
||||||
if int(k) == k:
|
if int(k) == k:
|
||||||
k = int(k)
|
k = int(k)
|
||||||
@@ -1831,26 +1842,6 @@ class BaseQuerySet(object):
|
|||||||
|
|
||||||
return tuple(data)
|
return tuple(data)
|
||||||
|
|
||||||
def _get_as_pymongo(self, doc):
|
|
||||||
"""Clean up a PyMongo doc, removing fields that were only fetched
|
|
||||||
for the sake of MongoEngine's implementation, and return it.
|
|
||||||
"""
|
|
||||||
# Always remove _cls as a MongoEngine's implementation detail.
|
|
||||||
if '_cls' in doc:
|
|
||||||
del doc['_cls']
|
|
||||||
|
|
||||||
# If the _id was not included in a .only or was excluded in a .exclude,
|
|
||||||
# remove it from the doc (we always fetch it so that we can properly
|
|
||||||
# construct documents).
|
|
||||||
fields = self._loaded_fields
|
|
||||||
if fields and '_id' in doc and (
|
|
||||||
(fields.value == QueryFieldList.ONLY and '_id' not in fields.fields) or
|
|
||||||
(fields.value == QueryFieldList.EXCLUDE and '_id' in fields.fields)
|
|
||||||
):
|
|
||||||
del doc['_id']
|
|
||||||
|
|
||||||
return doc
|
|
||||||
|
|
||||||
def _sub_js_fields(self, code):
|
def _sub_js_fields(self, code):
|
||||||
"""When fields are specified with [~fieldname] syntax, where
|
"""When fields are specified with [~fieldname] syntax, where
|
||||||
*fieldname* is the Python name of a field, *fieldname* will be
|
*fieldname* is the Python name of a field, *fieldname* will be
|
||||||
|
@@ -4,12 +4,13 @@ from bson import ObjectId, SON
|
|||||||
from bson.dbref import DBRef
|
from bson.dbref import DBRef
|
||||||
import pymongo
|
import pymongo
|
||||||
import six
|
import six
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
from mongoengine.base import UPDATE_OPERATORS
|
from mongoengine.base import UPDATE_OPERATORS
|
||||||
from mongoengine.common import _import_class
|
from mongoengine.common import _import_class
|
||||||
from mongoengine.connection import get_connection
|
from mongoengine.connection import get_connection
|
||||||
from mongoengine.errors import InvalidQueryError
|
from mongoengine.errors import InvalidQueryError
|
||||||
from mongoengine.python_support import IS_PYMONGO_3
|
from mongoengine.pymongo_support import IS_PYMONGO_3
|
||||||
|
|
||||||
__all__ = ('query', 'update')
|
__all__ = ('query', 'update')
|
||||||
|
|
||||||
@@ -154,7 +155,7 @@ def query(_doc_cls=None, **kwargs):
|
|||||||
if ('$maxDistance' in value_dict or '$minDistance' in value_dict) and \
|
if ('$maxDistance' in value_dict or '$minDistance' in value_dict) and \
|
||||||
('$near' in value_dict or '$nearSphere' in value_dict):
|
('$near' in value_dict or '$nearSphere' in value_dict):
|
||||||
value_son = SON()
|
value_son = SON()
|
||||||
for k, v in value_dict.iteritems():
|
for k, v in iteritems(value_dict):
|
||||||
if k == '$maxDistance' or k == '$minDistance':
|
if k == '$maxDistance' or k == '$minDistance':
|
||||||
continue
|
continue
|
||||||
value_son[k] = v
|
value_son[k] = v
|
||||||
@@ -345,7 +346,7 @@ def update(_doc_cls=None, **update):
|
|||||||
value = {key: {'$each': value}}
|
value = {key: {'$each': value}}
|
||||||
elif op in ('push', 'pushAll'):
|
elif op in ('push', 'pushAll'):
|
||||||
if parts[-1].isdigit():
|
if parts[-1].isdigit():
|
||||||
key = parts[0]
|
key = '.'.join(parts[0:-1])
|
||||||
position = int(parts[-1])
|
position = int(parts[-1])
|
||||||
# $position expects an iterable. If pushing a single value,
|
# $position expects an iterable. If pushing a single value,
|
||||||
# wrap it in a list.
|
# wrap it in a list.
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
|
from mongoengine.pymongo_support import list_collection_names
|
||||||
|
|
||||||
from mongoengine.queryset import NULLIFY, PULL
|
from mongoengine.queryset import NULLIFY, PULL
|
||||||
from mongoengine.connection import get_db
|
from mongoengine.connection import get_db
|
||||||
@@ -27,9 +28,7 @@ class ClassMethodsTest(unittest.TestCase):
|
|||||||
self.Person = Person
|
self.Person = Person
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
for collection in self.db.collection_names():
|
for collection in list_collection_names(self.db):
|
||||||
if 'system.' in collection:
|
|
||||||
continue
|
|
||||||
self.db.drop_collection(collection)
|
self.db.drop_collection(collection)
|
||||||
|
|
||||||
def test_definition(self):
|
def test_definition(self):
|
||||||
@@ -66,10 +65,10 @@ class ClassMethodsTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
collection_name = 'person'
|
collection_name = 'person'
|
||||||
self.Person(name='Test').save()
|
self.Person(name='Test').save()
|
||||||
self.assertIn(collection_name, self.db.collection_names())
|
self.assertIn(collection_name, list_collection_names(self.db))
|
||||||
|
|
||||||
self.Person.drop_collection()
|
self.Person.drop_collection()
|
||||||
self.assertNotIn(collection_name, self.db.collection_names())
|
self.assertNotIn(collection_name, list_collection_names(self.db))
|
||||||
|
|
||||||
def test_register_delete_rule(self):
|
def test_register_delete_rule(self):
|
||||||
"""Ensure that register delete rule adds a delete rule to the document
|
"""Ensure that register delete rule adds a delete rule to the document
|
||||||
@@ -102,16 +101,16 @@ class ClassMethodsTest(unittest.TestCase):
|
|||||||
BlogPost.drop_collection()
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
BlogPost.ensure_indexes()
|
BlogPost.ensure_indexes()
|
||||||
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
|
self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': []})
|
||||||
|
|
||||||
BlogPost.ensure_index(['author', 'description'])
|
BlogPost.ensure_index(['author', 'description'])
|
||||||
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [[('author', 1), ('description', 1)]] })
|
self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': [[('author', 1), ('description', 1)]]})
|
||||||
|
|
||||||
BlogPost._get_collection().drop_index('author_1_description_1')
|
BlogPost._get_collection().drop_index('author_1_description_1')
|
||||||
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
|
self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': []})
|
||||||
|
|
||||||
BlogPost._get_collection().drop_index('author_1_title_1')
|
BlogPost._get_collection().drop_index('author_1_title_1')
|
||||||
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [[('author', 1), ('title', 1)]], 'extra': [] })
|
self.assertEqual(BlogPost.compare_indexes(), {'missing': [[('author', 1), ('title', 1)]], 'extra': []})
|
||||||
|
|
||||||
def test_compare_indexes_inheritance(self):
|
def test_compare_indexes_inheritance(self):
|
||||||
""" Ensure that the indexes are properly created and that
|
""" Ensure that the indexes are properly created and that
|
||||||
@@ -140,16 +139,16 @@ class ClassMethodsTest(unittest.TestCase):
|
|||||||
|
|
||||||
BlogPost.ensure_indexes()
|
BlogPost.ensure_indexes()
|
||||||
BlogPostWithTags.ensure_indexes()
|
BlogPostWithTags.ensure_indexes()
|
||||||
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
|
self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': []})
|
||||||
|
|
||||||
BlogPostWithTags.ensure_index(['author', 'tag_list'])
|
BlogPostWithTags.ensure_index(['author', 'tag_list'])
|
||||||
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [[('_cls', 1), ('author', 1), ('tag_list', 1)]] })
|
self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': [[('_cls', 1), ('author', 1), ('tag_list', 1)]]})
|
||||||
|
|
||||||
BlogPostWithTags._get_collection().drop_index('_cls_1_author_1_tag_list_1')
|
BlogPostWithTags._get_collection().drop_index('_cls_1_author_1_tag_list_1')
|
||||||
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
|
self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': []})
|
||||||
|
|
||||||
BlogPostWithTags._get_collection().drop_index('_cls_1_author_1_tags_1')
|
BlogPostWithTags._get_collection().drop_index('_cls_1_author_1_tags_1')
|
||||||
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [[('_cls', 1), ('author', 1), ('tags', 1)]], 'extra': [] })
|
self.assertEqual(BlogPost.compare_indexes(), {'missing': [[('_cls', 1), ('author', 1), ('tags', 1)]], 'extra': []})
|
||||||
|
|
||||||
def test_compare_indexes_multiple_subclasses(self):
|
def test_compare_indexes_multiple_subclasses(self):
|
||||||
""" Ensure that compare_indexes behaves correctly if called from a
|
""" Ensure that compare_indexes behaves correctly if called from a
|
||||||
@@ -184,9 +183,9 @@ class ClassMethodsTest(unittest.TestCase):
|
|||||||
BlogPostWithTags.ensure_indexes()
|
BlogPostWithTags.ensure_indexes()
|
||||||
BlogPostWithCustomField.ensure_indexes()
|
BlogPostWithCustomField.ensure_indexes()
|
||||||
|
|
||||||
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
|
self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': []})
|
||||||
self.assertEqual(BlogPostWithTags.compare_indexes(), { 'missing': [], 'extra': [] })
|
self.assertEqual(BlogPostWithTags.compare_indexes(), {'missing': [], 'extra': []})
|
||||||
self.assertEqual(BlogPostWithCustomField.compare_indexes(), { 'missing': [], 'extra': [] })
|
self.assertEqual(BlogPostWithCustomField.compare_indexes(), {'missing': [], 'extra': []})
|
||||||
|
|
||||||
@requires_mongodb_gte_26
|
@requires_mongodb_gte_26
|
||||||
def test_compare_indexes_for_text_indexes(self):
|
def test_compare_indexes_for_text_indexes(self):
|
||||||
@@ -340,7 +339,7 @@ class ClassMethodsTest(unittest.TestCase):
|
|||||||
meta = {'collection': collection_name}
|
meta = {'collection': collection_name}
|
||||||
|
|
||||||
Person(name="Test User").save()
|
Person(name="Test User").save()
|
||||||
self.assertIn(collection_name, self.db.collection_names())
|
self.assertIn(collection_name, list_collection_names(self.db))
|
||||||
|
|
||||||
user_obj = self.db[collection_name].find_one()
|
user_obj = self.db[collection_name].find_one()
|
||||||
self.assertEqual(user_obj['name'], "Test User")
|
self.assertEqual(user_obj['name'], "Test User")
|
||||||
@@ -349,7 +348,7 @@ class ClassMethodsTest(unittest.TestCase):
|
|||||||
self.assertEqual(user_obj.name, "Test User")
|
self.assertEqual(user_obj.name, "Test User")
|
||||||
|
|
||||||
Person.drop_collection()
|
Person.drop_collection()
|
||||||
self.assertNotIn(collection_name, self.db.collection_names())
|
self.assertNotIn(collection_name, list_collection_names(self.db))
|
||||||
|
|
||||||
def test_collection_name_and_primary(self):
|
def test_collection_name_and_primary(self):
|
||||||
"""Ensure that a collection with a specified name may be used.
|
"""Ensure that a collection with a specified name may be used.
|
||||||
|
@@ -3,16 +3,14 @@ import unittest
|
|||||||
|
|
||||||
from bson import SON
|
from bson import SON
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
from mongoengine.connection import get_db
|
from mongoengine.pymongo_support import list_collection_names
|
||||||
|
from tests.utils import MongoDBTestCase
|
||||||
__all__ = ("DeltaTest",)
|
|
||||||
|
|
||||||
|
|
||||||
class DeltaTest(unittest.TestCase):
|
class DeltaTest(MongoDBTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
connect(db='mongoenginetest')
|
super(DeltaTest, self).setUp()
|
||||||
self.db = get_db()
|
|
||||||
|
|
||||||
class Person(Document):
|
class Person(Document):
|
||||||
name = StringField()
|
name = StringField()
|
||||||
@@ -25,9 +23,7 @@ class DeltaTest(unittest.TestCase):
|
|||||||
self.Person = Person
|
self.Person = Person
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
for collection in self.db.collection_names():
|
for collection in list_collection_names(self.db):
|
||||||
if 'system.' in collection:
|
|
||||||
continue
|
|
||||||
self.db.drop_collection(collection)
|
self.db.drop_collection(collection)
|
||||||
|
|
||||||
def test_delta(self):
|
def test_delta(self):
|
||||||
@@ -863,5 +859,6 @@ class DeltaTest(unittest.TestCase):
|
|||||||
self.assertEqual('oops', delta[0]["users.007.rolist"][0]["type"])
|
self.assertEqual('oops', delta[0]["users.007.rolist"][0]["type"])
|
||||||
self.assertEqual(uinfo.id, delta[0]["users.007.info"])
|
self.assertEqual(uinfo.id, delta[0]["users.007.info"])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -1,16 +1,15 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
from mongoengine.connection import get_db
|
from tests.utils import MongoDBTestCase
|
||||||
|
|
||||||
__all__ = ("DynamicTest", )
|
__all__ = ("TestDynamicDocument", )
|
||||||
|
|
||||||
|
|
||||||
class DynamicTest(unittest.TestCase):
|
class TestDynamicDocument(MongoDBTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
connect(db='mongoenginetest')
|
super(TestDynamicDocument, self).setUp()
|
||||||
self.db = get_db()
|
|
||||||
|
|
||||||
class Person(DynamicDocument):
|
class Person(DynamicDocument):
|
||||||
name = StringField()
|
name = StringField()
|
||||||
@@ -98,6 +97,72 @@ class DynamicTest(unittest.TestCase):
|
|||||||
self.assertEqual(len(p._data), 4)
|
self.assertEqual(len(p._data), 4)
|
||||||
self.assertEqual(sorted(p._data.keys()), ['_cls', 'age', 'id', 'name'])
|
self.assertEqual(sorted(p._data.keys()), ['_cls', 'age', 'id', 'name'])
|
||||||
|
|
||||||
|
def test_fields_without_underscore(self):
|
||||||
|
"""Ensure we can query dynamic fields"""
|
||||||
|
Person = self.Person
|
||||||
|
|
||||||
|
p = self.Person(name='Dean')
|
||||||
|
p.save()
|
||||||
|
|
||||||
|
raw_p = Person.objects.as_pymongo().get(id=p.id)
|
||||||
|
self.assertEqual(
|
||||||
|
raw_p,
|
||||||
|
{
|
||||||
|
'_cls': u'Person',
|
||||||
|
'_id': p.id,
|
||||||
|
'name': u'Dean'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
p.name = 'OldDean'
|
||||||
|
p.newattr = 'garbage'
|
||||||
|
p.save()
|
||||||
|
raw_p = Person.objects.as_pymongo().get(id=p.id)
|
||||||
|
self.assertEqual(
|
||||||
|
raw_p,
|
||||||
|
{
|
||||||
|
'_cls': u'Person',
|
||||||
|
'_id': p.id,
|
||||||
|
'name': 'OldDean',
|
||||||
|
'newattr': u'garbage'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fields_containing_underscore(self):
|
||||||
|
"""Ensure we can query dynamic fields"""
|
||||||
|
class WeirdPerson(DynamicDocument):
|
||||||
|
name = StringField()
|
||||||
|
_name = StringField()
|
||||||
|
|
||||||
|
WeirdPerson.drop_collection()
|
||||||
|
|
||||||
|
p = WeirdPerson(name='Dean', _name='Dean')
|
||||||
|
p.save()
|
||||||
|
|
||||||
|
raw_p = WeirdPerson.objects.as_pymongo().get(id=p.id)
|
||||||
|
self.assertEqual(
|
||||||
|
raw_p,
|
||||||
|
{
|
||||||
|
'_id': p.id,
|
||||||
|
'_name': u'Dean',
|
||||||
|
'name': u'Dean'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
p.name = 'OldDean'
|
||||||
|
p._name = 'NewDean'
|
||||||
|
p._newattr1 = 'garbage' # Unknown fields won't be added
|
||||||
|
p.save()
|
||||||
|
raw_p = WeirdPerson.objects.as_pymongo().get(id=p.id)
|
||||||
|
self.assertEqual(
|
||||||
|
raw_p,
|
||||||
|
{
|
||||||
|
'_id': p.id,
|
||||||
|
'_name': u'NewDean',
|
||||||
|
'name': u'OldDean',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def test_dynamic_document_queries(self):
|
def test_dynamic_document_queries(self):
|
||||||
"""Ensure we can query dynamic fields"""
|
"""Ensure we can query dynamic fields"""
|
||||||
p = self.Person()
|
p = self.Person()
|
||||||
@@ -369,5 +434,6 @@ class DynamicTest(unittest.TestCase):
|
|||||||
person.save()
|
person.save()
|
||||||
self.assertEqual(Person.objects.first().age, 35)
|
self.assertEqual(Person.objects.first().age, 35)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -5,10 +5,12 @@ from datetime import datetime
|
|||||||
from nose.plugins.skip import SkipTest
|
from nose.plugins.skip import SkipTest
|
||||||
from pymongo.errors import OperationFailure
|
from pymongo.errors import OperationFailure
|
||||||
import pymongo
|
import pymongo
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
from mongoengine.connection import get_db
|
from mongoengine.connection import get_db
|
||||||
from tests.utils import get_mongodb_version, requires_mongodb_gte_26, MONGODB_32, MONGODB_3
|
from mongoengine.mongodb_support import get_mongodb_version, MONGODB_32, MONGODB_3
|
||||||
|
from tests.utils import requires_mongodb_gte_26, requires_mongodb_lte_32, requires_mongodb_gte_34
|
||||||
|
|
||||||
__all__ = ("IndexesTest", )
|
__all__ = ("IndexesTest", )
|
||||||
|
|
||||||
@@ -68,7 +70,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
info = BlogPost.objects._collection.index_information()
|
info = BlogPost.objects._collection.index_information()
|
||||||
# _id, '-date', 'tags', ('cat', 'date')
|
# _id, '-date', 'tags', ('cat', 'date')
|
||||||
self.assertEqual(len(info), 4)
|
self.assertEqual(len(info), 4)
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in iteritems(info)]
|
||||||
for expected in expected_specs:
|
for expected in expected_specs:
|
||||||
self.assertIn(expected['fields'], info)
|
self.assertIn(expected['fields'], info)
|
||||||
|
|
||||||
@@ -100,7 +102,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
# the indices on -date and tags will both contain
|
# the indices on -date and tags will both contain
|
||||||
# _cls as first element in the key
|
# _cls as first element in the key
|
||||||
self.assertEqual(len(info), 4)
|
self.assertEqual(len(info), 4)
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in iteritems(info)]
|
||||||
for expected in expected_specs:
|
for expected in expected_specs:
|
||||||
self.assertIn(expected['fields'], info)
|
self.assertIn(expected['fields'], info)
|
||||||
|
|
||||||
@@ -115,7 +117,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
|
|
||||||
ExtendedBlogPost.ensure_indexes()
|
ExtendedBlogPost.ensure_indexes()
|
||||||
info = ExtendedBlogPost.objects._collection.index_information()
|
info = ExtendedBlogPost.objects._collection.index_information()
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in iteritems(info)]
|
||||||
for expected in expected_specs:
|
for expected in expected_specs:
|
||||||
self.assertIn(expected['fields'], info)
|
self.assertIn(expected['fields'], info)
|
||||||
|
|
||||||
@@ -225,7 +227,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
# Indexes are lazy so use list() to perform query
|
# Indexes are lazy so use list() to perform query
|
||||||
list(Person.objects)
|
list(Person.objects)
|
||||||
info = Person.objects._collection.index_information()
|
info = Person.objects._collection.index_information()
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in iteritems(info)]
|
||||||
self.assertIn([('rank.title', 1)], info)
|
self.assertIn([('rank.title', 1)], info)
|
||||||
|
|
||||||
def test_explicit_geo2d_index(self):
|
def test_explicit_geo2d_index(self):
|
||||||
@@ -245,7 +247,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
|
|
||||||
Place.ensure_indexes()
|
Place.ensure_indexes()
|
||||||
info = Place._get_collection().index_information()
|
info = Place._get_collection().index_information()
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in iteritems(info)]
|
||||||
self.assertIn([('location.point', '2d')], info)
|
self.assertIn([('location.point', '2d')], info)
|
||||||
|
|
||||||
def test_explicit_geo2d_index_embedded(self):
|
def test_explicit_geo2d_index_embedded(self):
|
||||||
@@ -268,7 +270,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
|
|
||||||
Place.ensure_indexes()
|
Place.ensure_indexes()
|
||||||
info = Place._get_collection().index_information()
|
info = Place._get_collection().index_information()
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in iteritems(info)]
|
||||||
self.assertIn([('current.location.point', '2d')], info)
|
self.assertIn([('current.location.point', '2d')], info)
|
||||||
|
|
||||||
def test_explicit_geosphere_index(self):
|
def test_explicit_geosphere_index(self):
|
||||||
@@ -288,7 +290,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
|
|
||||||
Place.ensure_indexes()
|
Place.ensure_indexes()
|
||||||
info = Place._get_collection().index_information()
|
info = Place._get_collection().index_information()
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in iteritems(info)]
|
||||||
self.assertIn([('location.point', '2dsphere')], info)
|
self.assertIn([('location.point', '2dsphere')], info)
|
||||||
|
|
||||||
def test_explicit_geohaystack_index(self):
|
def test_explicit_geohaystack_index(self):
|
||||||
@@ -310,7 +312,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
|
|
||||||
Place.ensure_indexes()
|
Place.ensure_indexes()
|
||||||
info = Place._get_collection().index_information()
|
info = Place._get_collection().index_information()
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in iteritems(info)]
|
||||||
self.assertIn([('location.point', 'geoHaystack')], info)
|
self.assertIn([('location.point', 'geoHaystack')], info)
|
||||||
|
|
||||||
def test_create_geohaystack_index(self):
|
def test_create_geohaystack_index(self):
|
||||||
@@ -322,7 +324,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
|
|
||||||
Place.create_index({'fields': (')location.point', 'name')}, bucketSize=10)
|
Place.create_index({'fields': (')location.point', 'name')}, bucketSize=10)
|
||||||
info = Place._get_collection().index_information()
|
info = Place._get_collection().index_information()
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in iteritems(info)]
|
||||||
self.assertIn([('location.point', 'geoHaystack'), ('name', 1)], info)
|
self.assertIn([('location.point', 'geoHaystack'), ('name', 1)], info)
|
||||||
|
|
||||||
def test_dictionary_indexes(self):
|
def test_dictionary_indexes(self):
|
||||||
@@ -355,7 +357,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
info = [(value['key'],
|
info = [(value['key'],
|
||||||
value.get('unique', False),
|
value.get('unique', False),
|
||||||
value.get('sparse', False))
|
value.get('sparse', False))
|
||||||
for key, value in info.iteritems()]
|
for key, value in iteritems(info)]
|
||||||
self.assertIn(([('addDate', -1)], True, True), info)
|
self.assertIn(([('addDate', -1)], True, True), info)
|
||||||
|
|
||||||
BlogPost.drop_collection()
|
BlogPost.drop_collection()
|
||||||
@@ -476,6 +478,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
def test_covered_index(self):
|
def test_covered_index(self):
|
||||||
"""Ensure that covered indexes can be used
|
"""Ensure that covered indexes can be used
|
||||||
"""
|
"""
|
||||||
|
IS_MONGODB_3 = get_mongodb_version() >= MONGODB_3
|
||||||
|
|
||||||
class Test(Document):
|
class Test(Document):
|
||||||
a = IntField()
|
a = IntField()
|
||||||
@@ -491,8 +494,6 @@ class IndexesTest(unittest.TestCase):
|
|||||||
obj = Test(a=1)
|
obj = Test(a=1)
|
||||||
obj.save()
|
obj.save()
|
||||||
|
|
||||||
IS_MONGODB_3 = get_mongodb_version() >= MONGODB_3
|
|
||||||
|
|
||||||
# Need to be explicit about covered indexes as mongoDB doesn't know if
|
# Need to be explicit about covered indexes as mongoDB doesn't know if
|
||||||
# the documents returned might have more keys in that here.
|
# the documents returned might have more keys in that here.
|
||||||
query_plan = Test.objects(id=obj.id).exclude('a').explain()
|
query_plan = Test.objects(id=obj.id).exclude('a').explain()
|
||||||
@@ -568,7 +569,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
if pymongo.version != '3.0':
|
if pymongo.version != '3.0':
|
||||||
self.assertEqual(BlogPost.objects.hint([('tags', 1)]).count(), 10)
|
self.assertEqual(BlogPost.objects.hint([('tags', 1)]).count(), 10)
|
||||||
|
|
||||||
if MONGO_VER == MONGODB_32:
|
if MONGO_VER >= MONGODB_32:
|
||||||
# Mongo32 throws an error if an index exists (i.e `tags` in our case)
|
# Mongo32 throws an error if an index exists (i.e `tags` in our case)
|
||||||
# and you use hint on an index name that does not exist
|
# and you use hint on an index name that does not exist
|
||||||
with self.assertRaises(OperationFailure):
|
with self.assertRaises(OperationFailure):
|
||||||
@@ -576,7 +577,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
else:
|
else:
|
||||||
self.assertEqual(BlogPost.objects.hint([('ZZ', 1)]).count(), 10)
|
self.assertEqual(BlogPost.objects.hint([('ZZ', 1)]).count(), 10)
|
||||||
|
|
||||||
self.assertEqual(BlogPost.objects.hint(TAGS_INDEX_NAME ).count(), 10)
|
self.assertEqual(BlogPost.objects.hint(TAGS_INDEX_NAME).count(), 10)
|
||||||
|
|
||||||
with self.assertRaises(Exception):
|
with self.assertRaises(Exception):
|
||||||
BlogPost.objects.hint(('tags', 1)).next()
|
BlogPost.objects.hint(('tags', 1)).next()
|
||||||
@@ -600,6 +601,32 @@ class IndexesTest(unittest.TestCase):
|
|||||||
# Ensure backwards compatibilty for errors
|
# Ensure backwards compatibilty for errors
|
||||||
self.assertRaises(OperationError, post2.save)
|
self.assertRaises(OperationError, post2.save)
|
||||||
|
|
||||||
|
@requires_mongodb_gte_34
|
||||||
|
def test_primary_key_unique_not_working_under_mongo_34(self):
|
||||||
|
"""Relates to #1445"""
|
||||||
|
class Blog(Document):
|
||||||
|
id = StringField(primary_key=True, unique=True)
|
||||||
|
|
||||||
|
Blog.drop_collection()
|
||||||
|
|
||||||
|
with self.assertRaises(OperationFailure) as ctx_err:
|
||||||
|
Blog(id='garbage').save()
|
||||||
|
try:
|
||||||
|
self.assertIn("The field 'unique' is not valid for an _id index specification", str(ctx_err.exception))
|
||||||
|
except AssertionError:
|
||||||
|
# error is slightly different on python 3.6
|
||||||
|
self.assertIn("The field 'background' is not valid for an _id index specification", str(ctx_err.exception))
|
||||||
|
|
||||||
|
@requires_mongodb_lte_32
|
||||||
|
def test_primary_key_unique_working_under_mongo_32(self):
|
||||||
|
"""Relates to #1445"""
|
||||||
|
class Blog(Document):
|
||||||
|
id = StringField(primary_key=True, unique=True)
|
||||||
|
|
||||||
|
Blog.drop_collection()
|
||||||
|
|
||||||
|
Blog(id='garbage').save()
|
||||||
|
|
||||||
def test_unique_with(self):
|
def test_unique_with(self):
|
||||||
"""Ensure that unique_with constraints are applied to fields.
|
"""Ensure that unique_with constraints are applied to fields.
|
||||||
"""
|
"""
|
||||||
@@ -759,7 +786,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
You won't create a duplicate but you will update an existing document.
|
You won't create a duplicate but you will update an existing document.
|
||||||
"""
|
"""
|
||||||
class User(Document):
|
class User(Document):
|
||||||
name = StringField(primary_key=True, unique=True)
|
name = StringField(primary_key=True)
|
||||||
password = StringField()
|
password = StringField()
|
||||||
|
|
||||||
User.drop_collection()
|
User.drop_collection()
|
||||||
@@ -806,7 +833,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
self.fail('Unbound local error at index + pk definition')
|
self.fail('Unbound local error at index + pk definition')
|
||||||
|
|
||||||
info = BlogPost.objects._collection.index_information()
|
info = BlogPost.objects._collection.index_information()
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in iteritems(info)]
|
||||||
index_item = [('_id', 1), ('comments.comment_id', 1)]
|
index_item = [('_id', 1), ('comments.comment_id', 1)]
|
||||||
self.assertIn(index_item, info)
|
self.assertIn(index_item, info)
|
||||||
|
|
||||||
@@ -854,7 +881,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
info = MyDoc.objects._collection.index_information()
|
info = MyDoc.objects._collection.index_information()
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in iteritems(info)]
|
||||||
self.assertIn([('provider_ids.foo', 1)], info)
|
self.assertIn([('provider_ids.foo', 1)], info)
|
||||||
self.assertIn([('provider_ids.bar', 1)], info)
|
self.assertIn([('provider_ids.bar', 1)], info)
|
||||||
|
|
||||||
@@ -936,7 +963,6 @@ class IndexesTest(unittest.TestCase):
|
|||||||
# Drop the temporary database at the end
|
# Drop the temporary database at the end
|
||||||
connection.drop_database('tempdatabase')
|
connection.drop_database('tempdatabase')
|
||||||
|
|
||||||
|
|
||||||
def test_index_dont_send_cls_option(self):
|
def test_index_dont_send_cls_option(self):
|
||||||
"""
|
"""
|
||||||
Ensure that 'cls' option is not sent through ensureIndex. We shouldn't
|
Ensure that 'cls' option is not sent through ensureIndex. We shouldn't
|
||||||
|
@@ -2,25 +2,22 @@
|
|||||||
import unittest
|
import unittest
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
from mongoengine import (BooleanField, Document, EmbeddedDocument,
|
from mongoengine import (BooleanField, Document, EmbeddedDocument,
|
||||||
EmbeddedDocumentField, GenericReferenceField,
|
EmbeddedDocumentField, GenericReferenceField,
|
||||||
IntField, ReferenceField, StringField, connect)
|
IntField, ReferenceField, StringField)
|
||||||
from mongoengine.connection import get_db
|
from mongoengine.pymongo_support import list_collection_names
|
||||||
|
from tests.utils import MongoDBTestCase
|
||||||
from tests.fixtures import Base
|
from tests.fixtures import Base
|
||||||
|
|
||||||
__all__ = ('InheritanceTest', )
|
__all__ = ('InheritanceTest', )
|
||||||
|
|
||||||
|
|
||||||
class InheritanceTest(unittest.TestCase):
|
class InheritanceTest(MongoDBTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
connect(db='mongoenginetest')
|
|
||||||
self.db = get_db()
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
for collection in self.db.collection_names():
|
for collection in list_collection_names(self.db):
|
||||||
if 'system.' in collection:
|
|
||||||
continue
|
|
||||||
self.db.drop_collection(collection)
|
self.db.drop_collection(collection)
|
||||||
|
|
||||||
def test_constructor_cls(self):
|
def test_constructor_cls(self):
|
||||||
@@ -36,12 +33,12 @@ class InheritanceTest(unittest.TestCase):
|
|||||||
meta = {'allow_inheritance': True}
|
meta = {'allow_inheritance': True}
|
||||||
|
|
||||||
test_doc = DataDoc(name='test', embed=EmbedData(data='data'))
|
test_doc = DataDoc(name='test', embed=EmbedData(data='data'))
|
||||||
assert test_doc._cls == 'DataDoc'
|
self.assertEqual(test_doc._cls, 'DataDoc')
|
||||||
assert test_doc.embed._cls == 'EmbedData'
|
self.assertEqual(test_doc.embed._cls, 'EmbedData')
|
||||||
test_doc.save()
|
test_doc.save()
|
||||||
saved_doc = DataDoc.objects.with_id(test_doc.id)
|
saved_doc = DataDoc.objects.with_id(test_doc.id)
|
||||||
assert test_doc._cls == saved_doc._cls
|
self.assertEqual(test_doc._cls, saved_doc._cls)
|
||||||
assert test_doc.embed._cls == saved_doc.embed._cls
|
self.assertEqual(test_doc.embed._cls, saved_doc.embed._cls)
|
||||||
test_doc.delete()
|
test_doc.delete()
|
||||||
|
|
||||||
def test_superclasses(self):
|
def test_superclasses(self):
|
||||||
@@ -485,7 +482,7 @@ class InheritanceTest(unittest.TestCase):
|
|||||||
meta = {'abstract': True}
|
meta = {'abstract': True}
|
||||||
class Human(Mammal): pass
|
class Human(Mammal): pass
|
||||||
|
|
||||||
for k, v in defaults.iteritems():
|
for k, v in iteritems(defaults):
|
||||||
for cls in [Animal, Fish, Guppy]:
|
for cls in [Animal, Fish, Guppy]:
|
||||||
self.assertEqual(cls._meta[k], v)
|
self.assertEqual(cls._meta[k], v)
|
||||||
|
|
||||||
|
@@ -4,12 +4,15 @@ import os
|
|||||||
import pickle
|
import pickle
|
||||||
import unittest
|
import unittest
|
||||||
import uuid
|
import uuid
|
||||||
|
import warnings
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from bson import DBRef, ObjectId
|
from bson import DBRef, ObjectId
|
||||||
from pymongo.errors import DuplicateKeyError
|
from pymongo.errors import DuplicateKeyError
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
|
from mongoengine.pymongo_support import list_collection_names
|
||||||
from tests import fixtures
|
from tests import fixtures
|
||||||
from tests.fixtures import (PickleEmbedded, PickleTest, PickleSignalsTest,
|
from tests.fixtures import (PickleEmbedded, PickleTest, PickleSignalsTest,
|
||||||
PickleDynamicEmbedded, PickleDynamicTest)
|
PickleDynamicEmbedded, PickleDynamicTest)
|
||||||
@@ -53,9 +56,7 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
self.Job = Job
|
self.Job = Job
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
for collection in self.db.collection_names():
|
for collection in list_collection_names(self.db):
|
||||||
if 'system.' in collection:
|
|
||||||
continue
|
|
||||||
self.db.drop_collection(collection)
|
self.db.drop_collection(collection)
|
||||||
|
|
||||||
def assertDbEqual(self, docs):
|
def assertDbEqual(self, docs):
|
||||||
@@ -570,7 +571,7 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
|
|
||||||
Post.drop_collection()
|
Post.drop_collection()
|
||||||
|
|
||||||
Post._get_collection().insert({
|
Post._get_collection().insert_one({
|
||||||
"title": "Items eclipse",
|
"title": "Items eclipse",
|
||||||
"items": ["more lorem", "even more ipsum"]
|
"items": ["more lorem", "even more ipsum"]
|
||||||
})
|
})
|
||||||
@@ -804,7 +805,8 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
doc2 = self.Person(name="jim", age=20).save()
|
doc2 = self.Person(name="jim", age=20).save()
|
||||||
docs = [dict(doc1.to_mongo()), dict(doc2.to_mongo())]
|
docs = [dict(doc1.to_mongo()), dict(doc2.to_mongo())]
|
||||||
|
|
||||||
assert not doc1.modify({'name': doc2.name}, set__age=100)
|
n_modified = doc1.modify({'name': doc2.name}, set__age=100)
|
||||||
|
self.assertEqual(n_modified, 0)
|
||||||
|
|
||||||
self.assertDbEqual(docs)
|
self.assertDbEqual(docs)
|
||||||
|
|
||||||
@@ -813,7 +815,8 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
doc2 = self.Person(id=ObjectId(), name="jim", age=20)
|
doc2 = self.Person(id=ObjectId(), name="jim", age=20)
|
||||||
docs = [dict(doc1.to_mongo())]
|
docs = [dict(doc1.to_mongo())]
|
||||||
|
|
||||||
assert not doc2.modify({'name': doc2.name}, set__age=100)
|
n_modified = doc2.modify({'name': doc2.name}, set__age=100)
|
||||||
|
self.assertEqual(n_modified, 0)
|
||||||
|
|
||||||
self.assertDbEqual(docs)
|
self.assertDbEqual(docs)
|
||||||
|
|
||||||
@@ -829,23 +832,30 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
doc.job.name = "Google"
|
doc.job.name = "Google"
|
||||||
doc.job.years = 3
|
doc.job.years = 3
|
||||||
|
|
||||||
assert doc.modify(
|
n_modified = doc.modify(
|
||||||
set__age=21, set__job__name="MongoDB", unset__job__years=True)
|
set__age=21, set__job__name="MongoDB", unset__job__years=True)
|
||||||
|
self.assertEqual(n_modified, 1)
|
||||||
doc_copy.age = 21
|
doc_copy.age = 21
|
||||||
doc_copy.job.name = "MongoDB"
|
doc_copy.job.name = "MongoDB"
|
||||||
del doc_copy.job.years
|
del doc_copy.job.years
|
||||||
|
|
||||||
assert doc.to_json() == doc_copy.to_json()
|
self.assertEqual(doc.to_json(), doc_copy.to_json())
|
||||||
assert doc._get_changed_fields() == []
|
self.assertEqual(doc._get_changed_fields(), [])
|
||||||
|
|
||||||
self.assertDbEqual([dict(other_doc.to_mongo()), dict(doc.to_mongo())])
|
self.assertDbEqual([dict(other_doc.to_mongo()), dict(doc.to_mongo())])
|
||||||
|
|
||||||
@requires_mongodb_gte_26
|
@requires_mongodb_gte_26
|
||||||
def test_modify_with_positional_push(self):
|
def test_modify_with_positional_push(self):
|
||||||
|
class Content(EmbeddedDocument):
|
||||||
|
keywords = ListField(StringField())
|
||||||
|
|
||||||
class BlogPost(Document):
|
class BlogPost(Document):
|
||||||
tags = ListField(StringField())
|
tags = ListField(StringField())
|
||||||
|
content = EmbeddedDocumentField(Content)
|
||||||
|
|
||||||
|
post = BlogPost.objects.create(
|
||||||
|
tags=['python'], content=Content(keywords=['ipsum']))
|
||||||
|
|
||||||
post = BlogPost.objects.create(tags=['python'])
|
|
||||||
self.assertEqual(post.tags, ['python'])
|
self.assertEqual(post.tags, ['python'])
|
||||||
post.modify(push__tags__0=['code', 'mongo'])
|
post.modify(push__tags__0=['code', 'mongo'])
|
||||||
self.assertEqual(post.tags, ['code', 'mongo', 'python'])
|
self.assertEqual(post.tags, ['code', 'mongo', 'python'])
|
||||||
@@ -856,6 +866,16 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
['code', 'mongo', 'python']
|
['code', 'mongo', 'python']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.assertEqual(post.content.keywords, ['ipsum'])
|
||||||
|
post.modify(push__content__keywords__0=['lorem'])
|
||||||
|
self.assertEqual(post.content.keywords, ['lorem', 'ipsum'])
|
||||||
|
|
||||||
|
# Assert same order of the list items is maintained in the db
|
||||||
|
self.assertEqual(
|
||||||
|
BlogPost._get_collection().find_one({'_id': post.pk})['content']['keywords'],
|
||||||
|
['lorem', 'ipsum']
|
||||||
|
)
|
||||||
|
|
||||||
def test_save(self):
|
def test_save(self):
|
||||||
"""Ensure that a document may be saved in the database."""
|
"""Ensure that a document may be saved in the database."""
|
||||||
|
|
||||||
@@ -1466,7 +1486,7 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
Message.drop_collection()
|
Message.drop_collection()
|
||||||
|
|
||||||
# All objects share the same id, but each in a different collection
|
# All objects share the same id, but each in a different collection
|
||||||
user = User(id=1, name='user-name')#.save()
|
user = User(id=1, name='user-name') # .save()
|
||||||
message = Message(id=1, author=user).save()
|
message = Message(id=1, author=user).save()
|
||||||
|
|
||||||
message.author.name = 'tutu'
|
message.author.name = 'tutu'
|
||||||
@@ -1984,7 +2004,6 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
child_record.delete()
|
child_record.delete()
|
||||||
self.assertEqual(Record.objects(name='parent').get().children, [])
|
self.assertEqual(Record.objects(name='parent').get().children, [])
|
||||||
|
|
||||||
|
|
||||||
def test_reverse_delete_rule_with_custom_id_field(self):
|
def test_reverse_delete_rule_with_custom_id_field(self):
|
||||||
"""Ensure that a referenced document with custom primary key
|
"""Ensure that a referenced document with custom primary key
|
||||||
is also deleted upon deletion.
|
is also deleted upon deletion.
|
||||||
@@ -2741,7 +2760,7 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
|
|
||||||
User.drop_collection()
|
User.drop_collection()
|
||||||
|
|
||||||
User._get_collection().save({
|
User._get_collection().insert_one({
|
||||||
'name': 'John',
|
'name': 'John',
|
||||||
'foo': 'Bar',
|
'foo': 'Bar',
|
||||||
'data': [1, 2, 3]
|
'data': [1, 2, 3]
|
||||||
@@ -2757,7 +2776,7 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
|
|
||||||
User.drop_collection()
|
User.drop_collection()
|
||||||
|
|
||||||
User._get_collection().save({
|
User._get_collection().insert_one({
|
||||||
'name': 'John',
|
'name': 'John',
|
||||||
'foo': 'Bar',
|
'foo': 'Bar',
|
||||||
'data': [1, 2, 3]
|
'data': [1, 2, 3]
|
||||||
@@ -2780,7 +2799,7 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
|
|
||||||
User.drop_collection()
|
User.drop_collection()
|
||||||
|
|
||||||
User._get_collection().save({
|
User._get_collection().insert_one({
|
||||||
'name': 'John',
|
'name': 'John',
|
||||||
'thing': {
|
'thing': {
|
||||||
'name': 'My thing',
|
'name': 'My thing',
|
||||||
@@ -2803,7 +2822,7 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
|
|
||||||
User.drop_collection()
|
User.drop_collection()
|
||||||
|
|
||||||
User._get_collection().save({
|
User._get_collection().insert_one({
|
||||||
'name': 'John',
|
'name': 'John',
|
||||||
'thing': {
|
'thing': {
|
||||||
'name': 'My thing',
|
'name': 'My thing',
|
||||||
@@ -2826,7 +2845,7 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
|
|
||||||
User.drop_collection()
|
User.drop_collection()
|
||||||
|
|
||||||
User._get_collection().save({
|
User._get_collection().insert_one({
|
||||||
'name': 'John',
|
'name': 'John',
|
||||||
'thing': {
|
'thing': {
|
||||||
'name': 'My thing',
|
'name': 'My thing',
|
||||||
@@ -3043,7 +3062,7 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
|
|
||||||
def expand(self):
|
def expand(self):
|
||||||
self.flattened_parameter = {}
|
self.flattened_parameter = {}
|
||||||
for parameter_name, parameter in self.parameters.iteritems():
|
for parameter_name, parameter in iteritems(self.parameters):
|
||||||
parameter.expand()
|
parameter.expand()
|
||||||
|
|
||||||
class NodesSystem(Document):
|
class NodesSystem(Document):
|
||||||
@@ -3051,7 +3070,7 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
nodes = MapField(ReferenceField(Node, dbref=False))
|
nodes = MapField(ReferenceField(Node, dbref=False))
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
for node_name, node in self.nodes.iteritems():
|
for node_name, node in iteritems(self.nodes):
|
||||||
node.expand()
|
node.expand()
|
||||||
node.save(*args, **kwargs)
|
node.save(*args, **kwargs)
|
||||||
super(NodesSystem, self).save(*args, **kwargs)
|
super(NodesSystem, self).save(*args, **kwargs)
|
||||||
@@ -3070,6 +3089,24 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
"UNDEFINED",
|
"UNDEFINED",
|
||||||
system.nodes["node"].parameters["param"].macros["test"].value)
|
system.nodes["node"].parameters["param"].macros["test"].value)
|
||||||
|
|
||||||
|
def test_embedded_document_save_reload_warning(self):
|
||||||
|
"""Relates to #1570"""
|
||||||
|
class Embedded(EmbeddedDocument):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Doc(Document):
|
||||||
|
emb = EmbeddedDocumentField(Embedded)
|
||||||
|
|
||||||
|
doc = Doc(emb=Embedded()).save()
|
||||||
|
doc.emb.save() # Make sure its still working
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("error", DeprecationWarning)
|
||||||
|
with self.assertRaises(DeprecationWarning):
|
||||||
|
doc.emb.save()
|
||||||
|
|
||||||
|
with self.assertRaises(DeprecationWarning):
|
||||||
|
doc.emb.reload()
|
||||||
|
|
||||||
def test_embedded_document_equality(self):
|
def test_embedded_document_equality(self):
|
||||||
class Test(Document):
|
class Test(Document):
|
||||||
field = StringField(required=True)
|
field = StringField(required=True)
|
||||||
@@ -3179,8 +3216,7 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
coll = Person._get_collection()
|
coll = Person._get_collection()
|
||||||
for person in Person.objects.as_pymongo():
|
for person in Person.objects.as_pymongo():
|
||||||
if 'height' not in person:
|
if 'height' not in person:
|
||||||
person['height'] = 189
|
coll.update_one({'_id': person['_id']}, {'$set': {'height': 189}})
|
||||||
coll.save(person)
|
|
||||||
|
|
||||||
self.assertEquals(Person.objects(height=189).count(), 1)
|
self.assertEquals(Person.objects(height=189).count(), 1)
|
||||||
|
|
||||||
@@ -3365,7 +3401,6 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
class User(Document):
|
class User(Document):
|
||||||
company = ReferenceField(Company)
|
company = ReferenceField(Company)
|
||||||
|
|
||||||
|
|
||||||
# Ensure index creation exception aren't swallowed (#1688)
|
# Ensure index creation exception aren't swallowed (#1688)
|
||||||
with self.assertRaises(DuplicateKeyError):
|
with self.assertRaises(DuplicateKeyError):
|
||||||
User.objects().select_related()
|
User.objects().select_related()
|
||||||
|
@@ -32,12 +32,12 @@ class TestJson(unittest.TestCase):
|
|||||||
string = StringField(db_field='s')
|
string = StringField(db_field='s')
|
||||||
embedded = EmbeddedDocumentField(Embedded, db_field='e')
|
embedded = EmbeddedDocumentField(Embedded, db_field='e')
|
||||||
|
|
||||||
doc = Doc( string="Hello", embedded=Embedded(string="Inner Hello"))
|
doc = Doc(string="Hello", embedded=Embedded(string="Inner Hello"))
|
||||||
doc_json = doc.to_json(sort_keys=True, use_db_field=False,separators=(',', ':'))
|
doc_json = doc.to_json(sort_keys=True, use_db_field=False, separators=(',', ':'))
|
||||||
|
|
||||||
expected_json = """{"embedded":{"string":"Inner Hello"},"string":"Hello"}"""
|
expected_json = """{"embedded":{"string":"Inner Hello"},"string":"Hello"}"""
|
||||||
|
|
||||||
self.assertEqual( doc_json, expected_json)
|
self.assertEqual(doc_json, expected_json)
|
||||||
|
|
||||||
def test_json_simple(self):
|
def test_json_simple(self):
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,16 @@ TEST_IMAGE_PATH = os.path.join(os.path.dirname(__file__), 'mongoengine.png')
|
|||||||
TEST_IMAGE2_PATH = os.path.join(os.path.dirname(__file__), 'mongodb_leaf.png')
|
TEST_IMAGE2_PATH = os.path.join(os.path.dirname(__file__), 'mongodb_leaf.png')
|
||||||
|
|
||||||
|
|
||||||
|
def get_file(path):
|
||||||
|
"""Use a BytesIO instead of a file to allow
|
||||||
|
to have a one-liner and avoid that the file remains opened"""
|
||||||
|
bytes_io = StringIO()
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
bytes_io.write(f.read())
|
||||||
|
bytes_io.seek(0)
|
||||||
|
return bytes_io
|
||||||
|
|
||||||
|
|
||||||
class FileTest(MongoDBTestCase):
|
class FileTest(MongoDBTestCase):
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
@@ -247,8 +257,8 @@ class FileTest(MongoDBTestCase):
|
|||||||
Animal.drop_collection()
|
Animal.drop_collection()
|
||||||
marmot = Animal(genus='Marmota', family='Sciuridae')
|
marmot = Animal(genus='Marmota', family='Sciuridae')
|
||||||
|
|
||||||
marmot_photo = open(TEST_IMAGE_PATH, 'rb') # Retrieve a photo from disk
|
marmot_photo_content = get_file(TEST_IMAGE_PATH) # Retrieve a photo from disk
|
||||||
marmot.photo.put(marmot_photo, content_type='image/jpeg', foo='bar')
|
marmot.photo.put(marmot_photo_content, content_type='image/jpeg', foo='bar')
|
||||||
marmot.photo.close()
|
marmot.photo.close()
|
||||||
marmot.save()
|
marmot.save()
|
||||||
|
|
||||||
@@ -261,11 +271,11 @@ class FileTest(MongoDBTestCase):
|
|||||||
the_file = FileField()
|
the_file = FileField()
|
||||||
TestFile.drop_collection()
|
TestFile.drop_collection()
|
||||||
|
|
||||||
test_file = TestFile(the_file=open(TEST_IMAGE_PATH, 'rb')).save()
|
test_file = TestFile(the_file=get_file(TEST_IMAGE_PATH)).save()
|
||||||
self.assertEqual(test_file.the_file.get().length, 8313)
|
self.assertEqual(test_file.the_file.get().length, 8313)
|
||||||
|
|
||||||
test_file = TestFile.objects.first()
|
test_file = TestFile.objects.first()
|
||||||
test_file.the_file = open(TEST_IMAGE2_PATH, 'rb')
|
test_file.the_file = get_file(TEST_IMAGE2_PATH)
|
||||||
test_file.save()
|
test_file.save()
|
||||||
self.assertEqual(test_file.the_file.get().length, 4971)
|
self.assertEqual(test_file.the_file.get().length, 4971)
|
||||||
|
|
||||||
@@ -379,7 +389,7 @@ class FileTest(MongoDBTestCase):
|
|||||||
self.assertEqual("%s" % e, "Invalid image: cannot identify image file %s" % f)
|
self.assertEqual("%s" % e, "Invalid image: cannot identify image file %s" % f)
|
||||||
|
|
||||||
t = TestImage()
|
t = TestImage()
|
||||||
t.image.put(open(TEST_IMAGE_PATH, 'rb'))
|
t.image.put(get_file(TEST_IMAGE_PATH))
|
||||||
t.save()
|
t.save()
|
||||||
|
|
||||||
t = TestImage.objects.first()
|
t = TestImage.objects.first()
|
||||||
@@ -400,11 +410,11 @@ class FileTest(MongoDBTestCase):
|
|||||||
the_file = ImageField()
|
the_file = ImageField()
|
||||||
TestFile.drop_collection()
|
TestFile.drop_collection()
|
||||||
|
|
||||||
test_file = TestFile(the_file=open(TEST_IMAGE_PATH, 'rb')).save()
|
test_file = TestFile(the_file=get_file(TEST_IMAGE_PATH)).save()
|
||||||
self.assertEqual(test_file.the_file.size, (371, 76))
|
self.assertEqual(test_file.the_file.size, (371, 76))
|
||||||
|
|
||||||
test_file = TestFile.objects.first()
|
test_file = TestFile.objects.first()
|
||||||
test_file.the_file = open(TEST_IMAGE2_PATH, 'rb')
|
test_file.the_file = get_file(TEST_IMAGE2_PATH)
|
||||||
test_file.save()
|
test_file.save()
|
||||||
self.assertEqual(test_file.the_file.size, (45, 101))
|
self.assertEqual(test_file.the_file.size, (45, 101))
|
||||||
|
|
||||||
@@ -418,7 +428,7 @@ class FileTest(MongoDBTestCase):
|
|||||||
TestImage.drop_collection()
|
TestImage.drop_collection()
|
||||||
|
|
||||||
t = TestImage()
|
t = TestImage()
|
||||||
t.image.put(open(TEST_IMAGE_PATH, 'rb'))
|
t.image.put(get_file(TEST_IMAGE_PATH))
|
||||||
t.save()
|
t.save()
|
||||||
|
|
||||||
t = TestImage.objects.first()
|
t = TestImage.objects.first()
|
||||||
@@ -441,7 +451,7 @@ class FileTest(MongoDBTestCase):
|
|||||||
TestImage.drop_collection()
|
TestImage.drop_collection()
|
||||||
|
|
||||||
t = TestImage()
|
t = TestImage()
|
||||||
t.image.put(open(TEST_IMAGE_PATH, 'rb'))
|
t.image.put(get_file(TEST_IMAGE_PATH))
|
||||||
t.save()
|
t.save()
|
||||||
|
|
||||||
t = TestImage.objects.first()
|
t = TestImage.objects.first()
|
||||||
@@ -464,7 +474,7 @@ class FileTest(MongoDBTestCase):
|
|||||||
TestImage.drop_collection()
|
TestImage.drop_collection()
|
||||||
|
|
||||||
t = TestImage()
|
t = TestImage()
|
||||||
t.image.put(open(TEST_IMAGE_PATH, 'rb'))
|
t.image.put(get_file(TEST_IMAGE_PATH))
|
||||||
t.save()
|
t.save()
|
||||||
|
|
||||||
t = TestImage.objects.first()
|
t = TestImage.objects.first()
|
||||||
@@ -542,8 +552,8 @@ class FileTest(MongoDBTestCase):
|
|||||||
TestImage.drop_collection()
|
TestImage.drop_collection()
|
||||||
|
|
||||||
t = TestImage()
|
t = TestImage()
|
||||||
t.image1.put(open(TEST_IMAGE_PATH, 'rb'))
|
t.image1.put(get_file(TEST_IMAGE_PATH))
|
||||||
t.image2.put(open(TEST_IMAGE2_PATH, 'rb'))
|
t.image2.put(get_file(TEST_IMAGE2_PATH))
|
||||||
t.save()
|
t.save()
|
||||||
|
|
||||||
test = TestImage.objects.first()
|
test = TestImage.objects.first()
|
||||||
@@ -563,12 +573,10 @@ class FileTest(MongoDBTestCase):
|
|||||||
Animal.drop_collection()
|
Animal.drop_collection()
|
||||||
marmot = Animal(genus='Marmota', family='Sciuridae')
|
marmot = Animal(genus='Marmota', family='Sciuridae')
|
||||||
|
|
||||||
marmot_photo = open(TEST_IMAGE_PATH, 'rb') # Retrieve a photo from disk
|
with open(TEST_IMAGE_PATH, 'rb') as marmot_photo: # Retrieve a photo from disk
|
||||||
|
photos_field = marmot._fields['photos'].field
|
||||||
photos_field = marmot._fields['photos'].field
|
new_proxy = photos_field.get_proxy_obj('photos', marmot)
|
||||||
new_proxy = photos_field.get_proxy_obj('photos', marmot)
|
new_proxy.put(marmot_photo, content_type='image/jpeg', foo='bar')
|
||||||
new_proxy.put(marmot_photo, content_type='image/jpeg', foo='bar')
|
|
||||||
marmot_photo.close()
|
|
||||||
|
|
||||||
marmot.photos.append(new_proxy)
|
marmot.photos.append(new_proxy)
|
||||||
marmot.save()
|
marmot.save()
|
||||||
@@ -578,5 +586,6 @@ class FileTest(MongoDBTestCase):
|
|||||||
self.assertEqual(marmot.photos[0].foo, 'bar')
|
self.assertEqual(marmot.photos[0].foo, 'bar')
|
||||||
self.assertEqual(marmot.photos[0].get().length, 8313)
|
self.assertEqual(marmot.photos[0].get().length, 8313)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
143
tests/fields/test_binary_field.py
Normal file
143
tests/fields/test_binary_field.py
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from nose.plugins.skip import SkipTest
|
||||||
|
import six
|
||||||
|
|
||||||
|
from bson import Binary
|
||||||
|
|
||||||
|
from mongoengine import *
|
||||||
|
from tests.utils import MongoDBTestCase
|
||||||
|
|
||||||
|
BIN_VALUE = six.b('\xa9\xf3\x8d(\xd7\x03\x84\xb4k[\x0f\xe3\xa2\x19\x85p[J\xa3\xd2>\xde\xe6\x87\xb1\x7f\xc6\xe6\xd9r\x18\xf5')
|
||||||
|
|
||||||
|
|
||||||
|
class TestBinaryField(MongoDBTestCase):
|
||||||
|
def test_binary_fields(self):
|
||||||
|
"""Ensure that binary fields can be stored and retrieved.
|
||||||
|
"""
|
||||||
|
class Attachment(Document):
|
||||||
|
content_type = StringField()
|
||||||
|
blob = BinaryField()
|
||||||
|
|
||||||
|
BLOB = six.b('\xe6\x00\xc4\xff\x07')
|
||||||
|
MIME_TYPE = 'application/octet-stream'
|
||||||
|
|
||||||
|
Attachment.drop_collection()
|
||||||
|
|
||||||
|
attachment = Attachment(content_type=MIME_TYPE, blob=BLOB)
|
||||||
|
attachment.save()
|
||||||
|
|
||||||
|
attachment_1 = Attachment.objects().first()
|
||||||
|
self.assertEqual(MIME_TYPE, attachment_1.content_type)
|
||||||
|
self.assertEqual(BLOB, six.binary_type(attachment_1.blob))
|
||||||
|
|
||||||
|
def test_validation_succeeds(self):
|
||||||
|
"""Ensure that valid values can be assigned to binary fields.
|
||||||
|
"""
|
||||||
|
class AttachmentRequired(Document):
|
||||||
|
blob = BinaryField(required=True)
|
||||||
|
|
||||||
|
class AttachmentSizeLimit(Document):
|
||||||
|
blob = BinaryField(max_bytes=4)
|
||||||
|
|
||||||
|
attachment_required = AttachmentRequired()
|
||||||
|
self.assertRaises(ValidationError, attachment_required.validate)
|
||||||
|
attachment_required.blob = Binary(six.b('\xe6\x00\xc4\xff\x07'))
|
||||||
|
attachment_required.validate()
|
||||||
|
|
||||||
|
_5_BYTES = six.b('\xe6\x00\xc4\xff\x07')
|
||||||
|
_4_BYTES = six.b('\xe6\x00\xc4\xff')
|
||||||
|
self.assertRaises(ValidationError, AttachmentSizeLimit(blob=_5_BYTES).validate)
|
||||||
|
AttachmentSizeLimit(blob=_4_BYTES).validate()
|
||||||
|
|
||||||
|
def test_validation_fails(self):
|
||||||
|
"""Ensure that invalid values cannot be assigned to binary fields."""
|
||||||
|
|
||||||
|
class Attachment(Document):
|
||||||
|
blob = BinaryField()
|
||||||
|
|
||||||
|
for invalid_data in (2, u'Im_a_unicode', ['some_str']):
|
||||||
|
self.assertRaises(ValidationError, Attachment(blob=invalid_data).validate)
|
||||||
|
|
||||||
|
def test__primary(self):
|
||||||
|
class Attachment(Document):
|
||||||
|
id = BinaryField(primary_key=True)
|
||||||
|
|
||||||
|
Attachment.drop_collection()
|
||||||
|
binary_id = uuid.uuid4().bytes
|
||||||
|
att = Attachment(id=binary_id).save()
|
||||||
|
self.assertEqual(1, Attachment.objects.count())
|
||||||
|
self.assertEqual(1, Attachment.objects.filter(id=att.id).count())
|
||||||
|
att.delete()
|
||||||
|
self.assertEqual(0, Attachment.objects.count())
|
||||||
|
|
||||||
|
def test_primary_filter_by_binary_pk_as_str(self):
|
||||||
|
raise SkipTest("Querying by id as string is not currently supported")
|
||||||
|
|
||||||
|
class Attachment(Document):
|
||||||
|
id = BinaryField(primary_key=True)
|
||||||
|
|
||||||
|
Attachment.drop_collection()
|
||||||
|
binary_id = uuid.uuid4().bytes
|
||||||
|
att = Attachment(id=binary_id).save()
|
||||||
|
self.assertEqual(1, Attachment.objects.filter(id=binary_id).count())
|
||||||
|
att.delete()
|
||||||
|
self.assertEqual(0, Attachment.objects.count())
|
||||||
|
|
||||||
|
def test_match_querying_with_bytes(self):
|
||||||
|
class MyDocument(Document):
|
||||||
|
bin_field = BinaryField()
|
||||||
|
|
||||||
|
MyDocument.drop_collection()
|
||||||
|
|
||||||
|
doc = MyDocument(bin_field=BIN_VALUE).save()
|
||||||
|
matched_doc = MyDocument.objects(bin_field=BIN_VALUE).first()
|
||||||
|
self.assertEqual(matched_doc.id, doc.id)
|
||||||
|
|
||||||
|
def test_match_querying_with_binary(self):
|
||||||
|
class MyDocument(Document):
|
||||||
|
bin_field = BinaryField()
|
||||||
|
|
||||||
|
MyDocument.drop_collection()
|
||||||
|
|
||||||
|
doc = MyDocument(bin_field=BIN_VALUE).save()
|
||||||
|
|
||||||
|
matched_doc = MyDocument.objects(bin_field=Binary(BIN_VALUE)).first()
|
||||||
|
self.assertEqual(matched_doc.id, doc.id)
|
||||||
|
|
||||||
|
def test_modify_operation__set(self):
|
||||||
|
"""Ensures no regression of bug #1127"""
|
||||||
|
class MyDocument(Document):
|
||||||
|
some_field = StringField()
|
||||||
|
bin_field = BinaryField()
|
||||||
|
|
||||||
|
MyDocument.drop_collection()
|
||||||
|
|
||||||
|
doc = MyDocument.objects(some_field='test').modify(
|
||||||
|
upsert=True, new=True,
|
||||||
|
set__bin_field=BIN_VALUE
|
||||||
|
)
|
||||||
|
self.assertEqual(doc.some_field, 'test')
|
||||||
|
if six.PY3:
|
||||||
|
self.assertEqual(doc.bin_field, BIN_VALUE)
|
||||||
|
else:
|
||||||
|
self.assertEqual(doc.bin_field, Binary(BIN_VALUE))
|
||||||
|
|
||||||
|
def test_update_one(self):
|
||||||
|
"""Ensures no regression of bug #1127"""
|
||||||
|
class MyDocument(Document):
|
||||||
|
bin_field = BinaryField()
|
||||||
|
|
||||||
|
MyDocument.drop_collection()
|
||||||
|
|
||||||
|
bin_data = six.b('\xe6\x00\xc4\xff\x07')
|
||||||
|
doc = MyDocument(bin_field=bin_data).save()
|
||||||
|
|
||||||
|
n_updated = MyDocument.objects(bin_field=bin_data).update_one(bin_field=BIN_VALUE)
|
||||||
|
self.assertEqual(n_updated, 1)
|
||||||
|
fetched = MyDocument.objects.with_id(doc.id)
|
||||||
|
if six.PY3:
|
||||||
|
self.assertEqual(fetched.bin_field, BIN_VALUE)
|
||||||
|
else:
|
||||||
|
self.assertEqual(fetched.bin_field, Binary(BIN_VALUE))
|
49
tests/fields/test_boolean_field.py
Normal file
49
tests/fields/test_boolean_field.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from mongoengine import *
|
||||||
|
|
||||||
|
from tests.utils import MongoDBTestCase, get_as_pymongo
|
||||||
|
|
||||||
|
|
||||||
|
class TestBooleanField(MongoDBTestCase):
|
||||||
|
def test_storage(self):
|
||||||
|
class Person(Document):
|
||||||
|
admin = BooleanField()
|
||||||
|
|
||||||
|
person = Person(admin=True)
|
||||||
|
person.save()
|
||||||
|
self.assertEqual(
|
||||||
|
get_as_pymongo(person),
|
||||||
|
{'_id': person.id,
|
||||||
|
'admin': True})
|
||||||
|
|
||||||
|
def test_validation(self):
|
||||||
|
"""Ensure that invalid values cannot be assigned to boolean
|
||||||
|
fields.
|
||||||
|
"""
|
||||||
|
class Person(Document):
|
||||||
|
admin = BooleanField()
|
||||||
|
|
||||||
|
person = Person()
|
||||||
|
person.admin = True
|
||||||
|
person.validate()
|
||||||
|
|
||||||
|
person.admin = 2
|
||||||
|
self.assertRaises(ValidationError, person.validate)
|
||||||
|
person.admin = 'Yes'
|
||||||
|
self.assertRaises(ValidationError, person.validate)
|
||||||
|
person.admin = 'False'
|
||||||
|
self.assertRaises(ValidationError, person.validate)
|
||||||
|
|
||||||
|
def test_weirdness_constructor(self):
|
||||||
|
"""When attribute is set in contructor, it gets cast into a bool
|
||||||
|
which causes some weird behavior. We dont necessarily want to maintain this behavior
|
||||||
|
but its a known issue
|
||||||
|
"""
|
||||||
|
class Person(Document):
|
||||||
|
admin = BooleanField()
|
||||||
|
|
||||||
|
new_person = Person(admin='False')
|
||||||
|
self.assertTrue(new_person.admin)
|
||||||
|
|
||||||
|
new_person = Person(admin='0')
|
||||||
|
self.assertTrue(new_person.admin)
|
443
tests/fields/test_cached_reference_field.py
Normal file
443
tests/fields/test_cached_reference_field.py
Normal file
@@ -0,0 +1,443 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from mongoengine import *
|
||||||
|
|
||||||
|
from tests.utils import MongoDBTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestCachedReferenceField(MongoDBTestCase):
|
||||||
|
|
||||||
|
def test_get_and_save(self):
|
||||||
|
"""
|
||||||
|
Tests #1047: CachedReferenceField creates DBRefs on to_python,
|
||||||
|
but can't save them on to_mongo.
|
||||||
|
"""
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
class Ocorrence(Document):
|
||||||
|
person = StringField()
|
||||||
|
animal = CachedReferenceField(Animal)
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocorrence.drop_collection()
|
||||||
|
|
||||||
|
Ocorrence(person="testte",
|
||||||
|
animal=Animal(name="Leopard", tag="heavy").save()).save()
|
||||||
|
p = Ocorrence.objects.get()
|
||||||
|
p.person = 'new_testte'
|
||||||
|
p.save()
|
||||||
|
|
||||||
|
def test_general_things(self):
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
class Ocorrence(Document):
|
||||||
|
person = StringField()
|
||||||
|
animal = CachedReferenceField(
|
||||||
|
Animal, fields=['tag'])
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocorrence.drop_collection()
|
||||||
|
|
||||||
|
a = Animal(name="Leopard", tag="heavy")
|
||||||
|
a.save()
|
||||||
|
|
||||||
|
self.assertEqual(Animal._cached_reference_fields, [Ocorrence.animal])
|
||||||
|
o = Ocorrence(person="teste", animal=a)
|
||||||
|
o.save()
|
||||||
|
|
||||||
|
p = Ocorrence(person="Wilson")
|
||||||
|
p.save()
|
||||||
|
|
||||||
|
self.assertEqual(Ocorrence.objects(animal=None).count(), 1)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
a.to_mongo(fields=['tag']), {'tag': 'heavy', "_id": a.pk})
|
||||||
|
|
||||||
|
self.assertEqual(o.to_mongo()['animal']['tag'], 'heavy')
|
||||||
|
|
||||||
|
# counts
|
||||||
|
Ocorrence(person="teste 2").save()
|
||||||
|
Ocorrence(person="teste 3").save()
|
||||||
|
|
||||||
|
count = Ocorrence.objects(animal__tag='heavy').count()
|
||||||
|
self.assertEqual(count, 1)
|
||||||
|
|
||||||
|
ocorrence = Ocorrence.objects(animal__tag='heavy').first()
|
||||||
|
self.assertEqual(ocorrence.person, "teste")
|
||||||
|
self.assertIsInstance(ocorrence.animal, Animal)
|
||||||
|
|
||||||
|
def test_with_decimal(self):
|
||||||
|
class PersonAuto(Document):
|
||||||
|
name = StringField()
|
||||||
|
salary = DecimalField()
|
||||||
|
|
||||||
|
class SocialTest(Document):
|
||||||
|
group = StringField()
|
||||||
|
person = CachedReferenceField(
|
||||||
|
PersonAuto,
|
||||||
|
fields=('salary',))
|
||||||
|
|
||||||
|
PersonAuto.drop_collection()
|
||||||
|
SocialTest.drop_collection()
|
||||||
|
|
||||||
|
p = PersonAuto(name="Alberto", salary=Decimal('7000.00'))
|
||||||
|
p.save()
|
||||||
|
|
||||||
|
s = SocialTest(group="dev", person=p)
|
||||||
|
s.save()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
SocialTest.objects._collection.find_one({'person.salary': 7000.00}), {
|
||||||
|
'_id': s.pk,
|
||||||
|
'group': s.group,
|
||||||
|
'person': {
|
||||||
|
'_id': p.pk,
|
||||||
|
'salary': 7000.00
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_cached_reference_field_reference(self):
|
||||||
|
class Group(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
class Person(Document):
|
||||||
|
name = StringField()
|
||||||
|
group = ReferenceField(Group)
|
||||||
|
|
||||||
|
class SocialData(Document):
|
||||||
|
obs = StringField()
|
||||||
|
tags = ListField(
|
||||||
|
StringField())
|
||||||
|
person = CachedReferenceField(
|
||||||
|
Person,
|
||||||
|
fields=('group',))
|
||||||
|
|
||||||
|
Group.drop_collection()
|
||||||
|
Person.drop_collection()
|
||||||
|
SocialData.drop_collection()
|
||||||
|
|
||||||
|
g1 = Group(name='dev')
|
||||||
|
g1.save()
|
||||||
|
|
||||||
|
g2 = Group(name="designers")
|
||||||
|
g2.save()
|
||||||
|
|
||||||
|
p1 = Person(name="Alberto", group=g1)
|
||||||
|
p1.save()
|
||||||
|
|
||||||
|
p2 = Person(name="Andre", group=g1)
|
||||||
|
p2.save()
|
||||||
|
|
||||||
|
p3 = Person(name="Afro design", group=g2)
|
||||||
|
p3.save()
|
||||||
|
|
||||||
|
s1 = SocialData(obs="testing 123", person=p1, tags=['tag1', 'tag2'])
|
||||||
|
s1.save()
|
||||||
|
|
||||||
|
s2 = SocialData(obs="testing 321", person=p3, tags=['tag3', 'tag4'])
|
||||||
|
s2.save()
|
||||||
|
|
||||||
|
self.assertEqual(SocialData.objects._collection.find_one(
|
||||||
|
{'tags': 'tag2'}), {
|
||||||
|
'_id': s1.pk,
|
||||||
|
'obs': 'testing 123',
|
||||||
|
'tags': ['tag1', 'tag2'],
|
||||||
|
'person': {
|
||||||
|
'_id': p1.pk,
|
||||||
|
'group': g1.pk
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(SocialData.objects(person__group=g2).count(), 1)
|
||||||
|
self.assertEqual(SocialData.objects(person__group=g2).first(), s2)
|
||||||
|
|
||||||
|
def test_cached_reference_field_push_with_fields(self):
|
||||||
|
class Product(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
Product.drop_collection()
|
||||||
|
|
||||||
|
class Basket(Document):
|
||||||
|
products = ListField(CachedReferenceField(Product, fields=['name']))
|
||||||
|
|
||||||
|
Basket.drop_collection()
|
||||||
|
product1 = Product(name='abc').save()
|
||||||
|
product2 = Product(name='def').save()
|
||||||
|
basket = Basket(products=[product1]).save()
|
||||||
|
self.assertEqual(
|
||||||
|
Basket.objects._collection.find_one(),
|
||||||
|
{
|
||||||
|
'_id': basket.pk,
|
||||||
|
'products': [
|
||||||
|
{
|
||||||
|
'_id': product1.pk,
|
||||||
|
'name': product1.name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# push to list
|
||||||
|
basket.update(push__products=product2)
|
||||||
|
basket.reload()
|
||||||
|
self.assertEqual(
|
||||||
|
Basket.objects._collection.find_one(),
|
||||||
|
{
|
||||||
|
'_id': basket.pk,
|
||||||
|
'products': [
|
||||||
|
{
|
||||||
|
'_id': product1.pk,
|
||||||
|
'name': product1.name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'_id': product2.pk,
|
||||||
|
'name': product2.name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_cached_reference_field_update_all(self):
|
||||||
|
class Person(Document):
|
||||||
|
TYPES = (
|
||||||
|
('pf', "PF"),
|
||||||
|
('pj', "PJ")
|
||||||
|
)
|
||||||
|
name = StringField()
|
||||||
|
tp = StringField(
|
||||||
|
choices=TYPES
|
||||||
|
)
|
||||||
|
|
||||||
|
father = CachedReferenceField('self', fields=('tp',))
|
||||||
|
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
a1 = Person(name="Wilson Father", tp="pj")
|
||||||
|
a1.save()
|
||||||
|
|
||||||
|
a2 = Person(name='Wilson Junior', tp='pf', father=a1)
|
||||||
|
a2.save()
|
||||||
|
|
||||||
|
self.assertEqual(dict(a2.to_mongo()), {
|
||||||
|
"_id": a2.pk,
|
||||||
|
"name": u"Wilson Junior",
|
||||||
|
"tp": u"pf",
|
||||||
|
"father": {
|
||||||
|
"_id": a1.pk,
|
||||||
|
"tp": u"pj"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(Person.objects(father=a1)._query, {
|
||||||
|
'father._id': a1.pk
|
||||||
|
})
|
||||||
|
self.assertEqual(Person.objects(father=a1).count(), 1)
|
||||||
|
|
||||||
|
Person.objects.update(set__tp="pf")
|
||||||
|
Person.father.sync_all()
|
||||||
|
|
||||||
|
a2.reload()
|
||||||
|
self.assertEqual(dict(a2.to_mongo()), {
|
||||||
|
"_id": a2.pk,
|
||||||
|
"name": u"Wilson Junior",
|
||||||
|
"tp": u"pf",
|
||||||
|
"father": {
|
||||||
|
"_id": a1.pk,
|
||||||
|
"tp": u"pf"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_cached_reference_fields_on_embedded_documents(self):
|
||||||
|
with self.assertRaises(InvalidDocumentError):
|
||||||
|
class Test(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
type('WrongEmbeddedDocument', (
|
||||||
|
EmbeddedDocument,), {
|
||||||
|
'test': CachedReferenceField(Test)
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_cached_reference_auto_sync(self):
|
||||||
|
class Person(Document):
|
||||||
|
TYPES = (
|
||||||
|
('pf', "PF"),
|
||||||
|
('pj', "PJ")
|
||||||
|
)
|
||||||
|
name = StringField()
|
||||||
|
tp = StringField(
|
||||||
|
choices=TYPES
|
||||||
|
)
|
||||||
|
|
||||||
|
father = CachedReferenceField('self', fields=('tp',))
|
||||||
|
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
a1 = Person(name="Wilson Father", tp="pj")
|
||||||
|
a1.save()
|
||||||
|
|
||||||
|
a2 = Person(name='Wilson Junior', tp='pf', father=a1)
|
||||||
|
a2.save()
|
||||||
|
|
||||||
|
a1.tp = 'pf'
|
||||||
|
a1.save()
|
||||||
|
|
||||||
|
a2.reload()
|
||||||
|
self.assertEqual(dict(a2.to_mongo()), {
|
||||||
|
'_id': a2.pk,
|
||||||
|
'name': 'Wilson Junior',
|
||||||
|
'tp': 'pf',
|
||||||
|
'father': {
|
||||||
|
'_id': a1.pk,
|
||||||
|
'tp': 'pf'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_cached_reference_auto_sync_disabled(self):
|
||||||
|
class Persone(Document):
|
||||||
|
TYPES = (
|
||||||
|
('pf', "PF"),
|
||||||
|
('pj', "PJ")
|
||||||
|
)
|
||||||
|
name = StringField()
|
||||||
|
tp = StringField(
|
||||||
|
choices=TYPES
|
||||||
|
)
|
||||||
|
|
||||||
|
father = CachedReferenceField(
|
||||||
|
'self', fields=('tp',), auto_sync=False)
|
||||||
|
|
||||||
|
Persone.drop_collection()
|
||||||
|
|
||||||
|
a1 = Persone(name="Wilson Father", tp="pj")
|
||||||
|
a1.save()
|
||||||
|
|
||||||
|
a2 = Persone(name='Wilson Junior', tp='pf', father=a1)
|
||||||
|
a2.save()
|
||||||
|
|
||||||
|
a1.tp = 'pf'
|
||||||
|
a1.save()
|
||||||
|
|
||||||
|
self.assertEqual(Persone.objects._collection.find_one({'_id': a2.pk}), {
|
||||||
|
'_id': a2.pk,
|
||||||
|
'name': 'Wilson Junior',
|
||||||
|
'tp': 'pf',
|
||||||
|
'father': {
|
||||||
|
'_id': a1.pk,
|
||||||
|
'tp': 'pj'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_cached_reference_embedded_fields(self):
|
||||||
|
class Owner(EmbeddedDocument):
|
||||||
|
TPS = (
|
||||||
|
('n', "Normal"),
|
||||||
|
('u', "Urgent")
|
||||||
|
)
|
||||||
|
name = StringField()
|
||||||
|
tp = StringField(
|
||||||
|
verbose_name="Type",
|
||||||
|
db_field="t",
|
||||||
|
choices=TPS)
|
||||||
|
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
owner = EmbeddedDocumentField(Owner)
|
||||||
|
|
||||||
|
class Ocorrence(Document):
|
||||||
|
person = StringField()
|
||||||
|
animal = CachedReferenceField(
|
||||||
|
Animal, fields=['tag', 'owner.tp'])
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocorrence.drop_collection()
|
||||||
|
|
||||||
|
a = Animal(name="Leopard", tag="heavy",
|
||||||
|
owner=Owner(tp='u', name="Wilson Júnior")
|
||||||
|
)
|
||||||
|
a.save()
|
||||||
|
|
||||||
|
o = Ocorrence(person="teste", animal=a)
|
||||||
|
o.save()
|
||||||
|
self.assertEqual(dict(a.to_mongo(fields=['tag', 'owner.tp'])), {
|
||||||
|
'_id': a.pk,
|
||||||
|
'tag': 'heavy',
|
||||||
|
'owner': {
|
||||||
|
't': 'u'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.assertEqual(o.to_mongo()['animal']['tag'], 'heavy')
|
||||||
|
self.assertEqual(o.to_mongo()['animal']['owner']['t'], 'u')
|
||||||
|
|
||||||
|
# counts
|
||||||
|
Ocorrence(person="teste 2").save()
|
||||||
|
Ocorrence(person="teste 3").save()
|
||||||
|
|
||||||
|
count = Ocorrence.objects(
|
||||||
|
animal__tag='heavy', animal__owner__tp='u').count()
|
||||||
|
self.assertEqual(count, 1)
|
||||||
|
|
||||||
|
ocorrence = Ocorrence.objects(
|
||||||
|
animal__tag='heavy',
|
||||||
|
animal__owner__tp='u').first()
|
||||||
|
self.assertEqual(ocorrence.person, "teste")
|
||||||
|
self.assertIsInstance(ocorrence.animal, Animal)
|
||||||
|
|
||||||
|
def test_cached_reference_embedded_list_fields(self):
|
||||||
|
class Owner(EmbeddedDocument):
|
||||||
|
name = StringField()
|
||||||
|
tags = ListField(StringField())
|
||||||
|
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
owner = EmbeddedDocumentField(Owner)
|
||||||
|
|
||||||
|
class Ocorrence(Document):
|
||||||
|
person = StringField()
|
||||||
|
animal = CachedReferenceField(
|
||||||
|
Animal, fields=['tag', 'owner.tags'])
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocorrence.drop_collection()
|
||||||
|
|
||||||
|
a = Animal(name="Leopard", tag="heavy",
|
||||||
|
owner=Owner(tags=['cool', 'funny'],
|
||||||
|
name="Wilson Júnior")
|
||||||
|
)
|
||||||
|
a.save()
|
||||||
|
|
||||||
|
o = Ocorrence(person="teste 2", animal=a)
|
||||||
|
o.save()
|
||||||
|
self.assertEqual(dict(a.to_mongo(fields=['tag', 'owner.tags'])), {
|
||||||
|
'_id': a.pk,
|
||||||
|
'tag': 'heavy',
|
||||||
|
'owner': {
|
||||||
|
'tags': ['cool', 'funny']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(o.to_mongo()['animal']['tag'], 'heavy')
|
||||||
|
self.assertEqual(o.to_mongo()['animal']['owner']['tags'],
|
||||||
|
['cool', 'funny'])
|
||||||
|
|
||||||
|
# counts
|
||||||
|
Ocorrence(person="teste 2").save()
|
||||||
|
Ocorrence(person="teste 3").save()
|
||||||
|
|
||||||
|
query = Ocorrence.objects(
|
||||||
|
animal__tag='heavy', animal__owner__tags='cool')._query
|
||||||
|
self.assertEqual(
|
||||||
|
query, {'animal.owner.tags': 'cool', 'animal.tag': 'heavy'})
|
||||||
|
|
||||||
|
ocorrence = Ocorrence.objects(
|
||||||
|
animal__tag='heavy',
|
||||||
|
animal__owner__tags='cool').first()
|
||||||
|
self.assertEqual(ocorrence.person, "teste 2")
|
||||||
|
self.assertIsInstance(ocorrence.animal, Animal)
|
184
tests/fields/test_complex_datetime_field.py
Normal file
184
tests/fields/test_complex_datetime_field.py
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import datetime
|
||||||
|
import math
|
||||||
|
import itertools
|
||||||
|
import re
|
||||||
|
|
||||||
|
from mongoengine import *
|
||||||
|
|
||||||
|
from tests.utils import MongoDBTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class ComplexDateTimeFieldTest(MongoDBTestCase):
|
||||||
|
def test_complexdatetime_storage(self):
|
||||||
|
"""Tests for complex datetime fields - which can handle
|
||||||
|
microseconds without rounding.
|
||||||
|
"""
|
||||||
|
class LogEntry(Document):
|
||||||
|
date = ComplexDateTimeField()
|
||||||
|
date_with_dots = ComplexDateTimeField(separator='.')
|
||||||
|
|
||||||
|
LogEntry.drop_collection()
|
||||||
|
|
||||||
|
# Post UTC - microseconds are rounded (down) nearest millisecond and
|
||||||
|
# dropped - with default datetimefields
|
||||||
|
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 999)
|
||||||
|
log = LogEntry()
|
||||||
|
log.date = d1
|
||||||
|
log.save()
|
||||||
|
log.reload()
|
||||||
|
self.assertEqual(log.date, d1)
|
||||||
|
|
||||||
|
# Post UTC - microseconds are rounded (down) nearest millisecond - with
|
||||||
|
# default datetimefields
|
||||||
|
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 9999)
|
||||||
|
log.date = d1
|
||||||
|
log.save()
|
||||||
|
log.reload()
|
||||||
|
self.assertEqual(log.date, d1)
|
||||||
|
|
||||||
|
# Pre UTC dates microseconds below 1000 are dropped - with default
|
||||||
|
# datetimefields
|
||||||
|
d1 = datetime.datetime(1969, 12, 31, 23, 59, 59, 999)
|
||||||
|
log.date = d1
|
||||||
|
log.save()
|
||||||
|
log.reload()
|
||||||
|
self.assertEqual(log.date, d1)
|
||||||
|
|
||||||
|
# Pre UTC microseconds above 1000 is wonky - with default datetimefields
|
||||||
|
# log.date has an invalid microsecond value so I can't construct
|
||||||
|
# a date to compare.
|
||||||
|
for i in range(1001, 3113, 33):
|
||||||
|
d1 = datetime.datetime(1969, 12, 31, 23, 59, 59, i)
|
||||||
|
log.date = d1
|
||||||
|
log.save()
|
||||||
|
log.reload()
|
||||||
|
self.assertEqual(log.date, d1)
|
||||||
|
log1 = LogEntry.objects.get(date=d1)
|
||||||
|
self.assertEqual(log, log1)
|
||||||
|
|
||||||
|
# Test string padding
|
||||||
|
microsecond = map(int, [math.pow(10, x) for x in range(6)])
|
||||||
|
mm = dd = hh = ii = ss = [1, 10]
|
||||||
|
|
||||||
|
for values in itertools.product([2014], mm, dd, hh, ii, ss, microsecond):
|
||||||
|
stored = LogEntry(date=datetime.datetime(*values)).to_mongo()['date']
|
||||||
|
self.assertTrue(re.match('^\d{4},\d{2},\d{2},\d{2},\d{2},\d{2},\d{6}$', stored) is not None)
|
||||||
|
|
||||||
|
# Test separator
|
||||||
|
stored = LogEntry(date_with_dots=datetime.datetime(2014, 1, 1)).to_mongo()['date_with_dots']
|
||||||
|
self.assertTrue(re.match('^\d{4}.\d{2}.\d{2}.\d{2}.\d{2}.\d{2}.\d{6}$', stored) is not None)
|
||||||
|
|
||||||
|
def test_complexdatetime_usage(self):
|
||||||
|
"""Tests for complex datetime fields - which can handle
|
||||||
|
microseconds without rounding.
|
||||||
|
"""
|
||||||
|
class LogEntry(Document):
|
||||||
|
date = ComplexDateTimeField()
|
||||||
|
|
||||||
|
LogEntry.drop_collection()
|
||||||
|
|
||||||
|
d1 = datetime.datetime(1950, 1, 1, 0, 0, 1, 999)
|
||||||
|
log = LogEntry()
|
||||||
|
log.date = d1
|
||||||
|
log.save()
|
||||||
|
|
||||||
|
log1 = LogEntry.objects.get(date=d1)
|
||||||
|
self.assertEqual(log, log1)
|
||||||
|
|
||||||
|
# create extra 59 log entries for a total of 60
|
||||||
|
for i in range(1951, 2010):
|
||||||
|
d = datetime.datetime(i, 1, 1, 0, 0, 1, 999)
|
||||||
|
LogEntry(date=d).save()
|
||||||
|
|
||||||
|
self.assertEqual(LogEntry.objects.count(), 60)
|
||||||
|
|
||||||
|
# Test ordering
|
||||||
|
logs = LogEntry.objects.order_by("date")
|
||||||
|
i = 0
|
||||||
|
while i < 59:
|
||||||
|
self.assertTrue(logs[i].date <= logs[i + 1].date)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
logs = LogEntry.objects.order_by("-date")
|
||||||
|
i = 0
|
||||||
|
while i < 59:
|
||||||
|
self.assertTrue(logs[i].date >= logs[i + 1].date)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# Test searching
|
||||||
|
logs = LogEntry.objects.filter(date__gte=datetime.datetime(1980, 1, 1))
|
||||||
|
self.assertEqual(logs.count(), 30)
|
||||||
|
|
||||||
|
logs = LogEntry.objects.filter(date__lte=datetime.datetime(1980, 1, 1))
|
||||||
|
self.assertEqual(logs.count(), 30)
|
||||||
|
|
||||||
|
logs = LogEntry.objects.filter(
|
||||||
|
date__lte=datetime.datetime(2011, 1, 1),
|
||||||
|
date__gte=datetime.datetime(2000, 1, 1),
|
||||||
|
)
|
||||||
|
self.assertEqual(logs.count(), 10)
|
||||||
|
|
||||||
|
LogEntry.drop_collection()
|
||||||
|
|
||||||
|
# Test microsecond-level ordering/filtering
|
||||||
|
for microsecond in (99, 999, 9999, 10000):
|
||||||
|
LogEntry(
|
||||||
|
date=datetime.datetime(2015, 1, 1, 0, 0, 0, microsecond)
|
||||||
|
).save()
|
||||||
|
|
||||||
|
logs = list(LogEntry.objects.order_by('date'))
|
||||||
|
for next_idx, log in enumerate(logs[:-1], start=1):
|
||||||
|
next_log = logs[next_idx]
|
||||||
|
self.assertTrue(log.date < next_log.date)
|
||||||
|
|
||||||
|
logs = list(LogEntry.objects.order_by('-date'))
|
||||||
|
for next_idx, log in enumerate(logs[:-1], start=1):
|
||||||
|
next_log = logs[next_idx]
|
||||||
|
self.assertTrue(log.date > next_log.date)
|
||||||
|
|
||||||
|
logs = LogEntry.objects.filter(
|
||||||
|
date__lte=datetime.datetime(2015, 1, 1, 0, 0, 0, 10000))
|
||||||
|
self.assertEqual(logs.count(), 4)
|
||||||
|
|
||||||
|
def test_no_default_value(self):
|
||||||
|
class Log(Document):
|
||||||
|
timestamp = ComplexDateTimeField()
|
||||||
|
|
||||||
|
Log.drop_collection()
|
||||||
|
|
||||||
|
log = Log()
|
||||||
|
self.assertIsNone(log.timestamp)
|
||||||
|
log.save()
|
||||||
|
|
||||||
|
fetched_log = Log.objects.with_id(log.id)
|
||||||
|
self.assertIsNone(fetched_log.timestamp)
|
||||||
|
|
||||||
|
def test_default_static_value(self):
|
||||||
|
NOW = datetime.datetime.utcnow()
|
||||||
|
class Log(Document):
|
||||||
|
timestamp = ComplexDateTimeField(default=NOW)
|
||||||
|
|
||||||
|
Log.drop_collection()
|
||||||
|
|
||||||
|
log = Log()
|
||||||
|
self.assertEqual(log.timestamp, NOW)
|
||||||
|
log.save()
|
||||||
|
|
||||||
|
fetched_log = Log.objects.with_id(log.id)
|
||||||
|
self.assertEqual(fetched_log.timestamp, NOW)
|
||||||
|
|
||||||
|
def test_default_callable(self):
|
||||||
|
NOW = datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
class Log(Document):
|
||||||
|
timestamp = ComplexDateTimeField(default=datetime.datetime.utcnow)
|
||||||
|
|
||||||
|
Log.drop_collection()
|
||||||
|
|
||||||
|
log = Log()
|
||||||
|
self.assertGreaterEqual(log.timestamp, NOW)
|
||||||
|
log.save()
|
||||||
|
|
||||||
|
fetched_log = Log.objects.with_id(log.id)
|
||||||
|
self.assertGreaterEqual(fetched_log.timestamp, NOW)
|
165
tests/fields/test_date_field.py
Normal file
165
tests/fields/test_date_field.py
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import datetime
|
||||||
|
import six
|
||||||
|
|
||||||
|
try:
|
||||||
|
import dateutil
|
||||||
|
except ImportError:
|
||||||
|
dateutil = None
|
||||||
|
|
||||||
|
from mongoengine import *
|
||||||
|
|
||||||
|
from tests.utils import MongoDBTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestDateField(MongoDBTestCase):
|
||||||
|
def test_date_from_empty_string(self):
|
||||||
|
"""
|
||||||
|
Ensure an exception is raised when trying to
|
||||||
|
cast an empty string to datetime.
|
||||||
|
"""
|
||||||
|
class MyDoc(Document):
|
||||||
|
dt = DateField()
|
||||||
|
|
||||||
|
md = MyDoc(dt='')
|
||||||
|
self.assertRaises(ValidationError, md.save)
|
||||||
|
|
||||||
|
def test_date_from_whitespace_string(self):
|
||||||
|
"""
|
||||||
|
Ensure an exception is raised when trying to
|
||||||
|
cast a whitespace-only string to datetime.
|
||||||
|
"""
|
||||||
|
class MyDoc(Document):
|
||||||
|
dt = DateField()
|
||||||
|
|
||||||
|
md = MyDoc(dt=' ')
|
||||||
|
self.assertRaises(ValidationError, md.save)
|
||||||
|
|
||||||
|
def test_default_values_today(self):
|
||||||
|
"""Ensure that default field values are used when creating
|
||||||
|
a document.
|
||||||
|
"""
|
||||||
|
class Person(Document):
|
||||||
|
day = DateField(default=datetime.date.today)
|
||||||
|
|
||||||
|
person = Person()
|
||||||
|
person.validate()
|
||||||
|
self.assertEqual(person.day, person.day)
|
||||||
|
self.assertEqual(person.day, datetime.date.today())
|
||||||
|
self.assertEqual(person._data['day'], person.day)
|
||||||
|
|
||||||
|
def test_date(self):
|
||||||
|
"""Tests showing pymongo date fields
|
||||||
|
|
||||||
|
See: http://api.mongodb.org/python/current/api/bson/son.html#dt
|
||||||
|
"""
|
||||||
|
class LogEntry(Document):
|
||||||
|
date = DateField()
|
||||||
|
|
||||||
|
LogEntry.drop_collection()
|
||||||
|
|
||||||
|
# Test can save dates
|
||||||
|
log = LogEntry()
|
||||||
|
log.date = datetime.date.today()
|
||||||
|
log.save()
|
||||||
|
log.reload()
|
||||||
|
self.assertEqual(log.date, datetime.date.today())
|
||||||
|
|
||||||
|
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 999)
|
||||||
|
d2 = datetime.datetime(1970, 1, 1, 0, 0, 1)
|
||||||
|
log = LogEntry()
|
||||||
|
log.date = d1
|
||||||
|
log.save()
|
||||||
|
log.reload()
|
||||||
|
self.assertEqual(log.date, d1.date())
|
||||||
|
self.assertEqual(log.date, d2.date())
|
||||||
|
|
||||||
|
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 9999)
|
||||||
|
d2 = datetime.datetime(1970, 1, 1, 0, 0, 1, 9000)
|
||||||
|
log.date = d1
|
||||||
|
log.save()
|
||||||
|
log.reload()
|
||||||
|
self.assertEqual(log.date, d1.date())
|
||||||
|
self.assertEqual(log.date, d2.date())
|
||||||
|
|
||||||
|
if not six.PY3:
|
||||||
|
# Pre UTC dates microseconds below 1000 are dropped
|
||||||
|
# This does not seem to be true in PY3
|
||||||
|
d1 = datetime.datetime(1969, 12, 31, 23, 59, 59, 999)
|
||||||
|
d2 = datetime.datetime(1969, 12, 31, 23, 59, 59)
|
||||||
|
log.date = d1
|
||||||
|
log.save()
|
||||||
|
log.reload()
|
||||||
|
self.assertEqual(log.date, d1.date())
|
||||||
|
self.assertEqual(log.date, d2.date())
|
||||||
|
|
||||||
|
def test_regular_usage(self):
|
||||||
|
"""Tests for regular datetime fields"""
|
||||||
|
class LogEntry(Document):
|
||||||
|
date = DateField()
|
||||||
|
|
||||||
|
LogEntry.drop_collection()
|
||||||
|
|
||||||
|
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1)
|
||||||
|
log = LogEntry()
|
||||||
|
log.date = d1
|
||||||
|
log.validate()
|
||||||
|
log.save()
|
||||||
|
|
||||||
|
for query in (d1, d1.isoformat(' ')):
|
||||||
|
log1 = LogEntry.objects.get(date=query)
|
||||||
|
self.assertEqual(log, log1)
|
||||||
|
|
||||||
|
if dateutil:
|
||||||
|
log1 = LogEntry.objects.get(date=d1.isoformat('T'))
|
||||||
|
self.assertEqual(log, log1)
|
||||||
|
|
||||||
|
# create additional 19 log entries for a total of 20
|
||||||
|
for i in range(1971, 1990):
|
||||||
|
d = datetime.datetime(i, 1, 1, 0, 0, 1)
|
||||||
|
LogEntry(date=d).save()
|
||||||
|
|
||||||
|
self.assertEqual(LogEntry.objects.count(), 20)
|
||||||
|
|
||||||
|
# Test ordering
|
||||||
|
logs = LogEntry.objects.order_by("date")
|
||||||
|
i = 0
|
||||||
|
while i < 19:
|
||||||
|
self.assertTrue(logs[i].date <= logs[i + 1].date)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
logs = LogEntry.objects.order_by("-date")
|
||||||
|
i = 0
|
||||||
|
while i < 19:
|
||||||
|
self.assertTrue(logs[i].date >= logs[i + 1].date)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# Test searching
|
||||||
|
logs = LogEntry.objects.filter(date__gte=datetime.datetime(1980, 1, 1))
|
||||||
|
self.assertEqual(logs.count(), 10)
|
||||||
|
|
||||||
|
def test_validation(self):
|
||||||
|
"""Ensure that invalid values cannot be assigned to datetime
|
||||||
|
fields.
|
||||||
|
"""
|
||||||
|
class LogEntry(Document):
|
||||||
|
time = DateField()
|
||||||
|
|
||||||
|
log = LogEntry()
|
||||||
|
log.time = datetime.datetime.now()
|
||||||
|
log.validate()
|
||||||
|
|
||||||
|
log.time = datetime.date.today()
|
||||||
|
log.validate()
|
||||||
|
|
||||||
|
log.time = datetime.datetime.now().isoformat(' ')
|
||||||
|
log.validate()
|
||||||
|
|
||||||
|
if dateutil:
|
||||||
|
log.time = datetime.datetime.now().isoformat('T')
|
||||||
|
log.validate()
|
||||||
|
|
||||||
|
log.time = -1
|
||||||
|
self.assertRaises(ValidationError, log.validate)
|
||||||
|
log.time = 'ABC'
|
||||||
|
self.assertRaises(ValidationError, log.validate)
|
203
tests/fields/test_datetime_field.py
Normal file
203
tests/fields/test_datetime_field.py
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import datetime
|
||||||
|
import six
|
||||||
|
|
||||||
|
try:
|
||||||
|
import dateutil
|
||||||
|
except ImportError:
|
||||||
|
dateutil = None
|
||||||
|
|
||||||
|
from mongoengine import *
|
||||||
|
from mongoengine import connection
|
||||||
|
|
||||||
|
from tests.utils import MongoDBTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestDateTimeField(MongoDBTestCase):
|
||||||
|
def test_datetime_from_empty_string(self):
|
||||||
|
"""
|
||||||
|
Ensure an exception is raised when trying to
|
||||||
|
cast an empty string to datetime.
|
||||||
|
"""
|
||||||
|
class MyDoc(Document):
|
||||||
|
dt = DateTimeField()
|
||||||
|
|
||||||
|
md = MyDoc(dt='')
|
||||||
|
self.assertRaises(ValidationError, md.save)
|
||||||
|
|
||||||
|
def test_datetime_from_whitespace_string(self):
|
||||||
|
"""
|
||||||
|
Ensure an exception is raised when trying to
|
||||||
|
cast a whitespace-only string to datetime.
|
||||||
|
"""
|
||||||
|
class MyDoc(Document):
|
||||||
|
dt = DateTimeField()
|
||||||
|
|
||||||
|
md = MyDoc(dt=' ')
|
||||||
|
self.assertRaises(ValidationError, md.save)
|
||||||
|
|
||||||
|
def test_default_value_utcnow(self):
|
||||||
|
"""Ensure that default field values are used when creating
|
||||||
|
a document.
|
||||||
|
"""
|
||||||
|
class Person(Document):
|
||||||
|
created = DateTimeField(default=datetime.datetime.utcnow)
|
||||||
|
|
||||||
|
utcnow = datetime.datetime.utcnow()
|
||||||
|
person = Person()
|
||||||
|
person.validate()
|
||||||
|
person_created_t0 = person.created
|
||||||
|
self.assertLess(person.created - utcnow, datetime.timedelta(seconds=1))
|
||||||
|
self.assertEqual(person_created_t0, person.created) # make sure it does not change
|
||||||
|
self.assertEqual(person._data['created'], person.created)
|
||||||
|
|
||||||
|
def test_handling_microseconds(self):
|
||||||
|
"""Tests showing pymongo datetime fields handling of microseconds.
|
||||||
|
Microseconds are rounded to the nearest millisecond and pre UTC
|
||||||
|
handling is wonky.
|
||||||
|
|
||||||
|
See: http://api.mongodb.org/python/current/api/bson/son.html#dt
|
||||||
|
"""
|
||||||
|
class LogEntry(Document):
|
||||||
|
date = DateTimeField()
|
||||||
|
|
||||||
|
LogEntry.drop_collection()
|
||||||
|
|
||||||
|
# Test can save dates
|
||||||
|
log = LogEntry()
|
||||||
|
log.date = datetime.date.today()
|
||||||
|
log.save()
|
||||||
|
log.reload()
|
||||||
|
self.assertEqual(log.date.date(), datetime.date.today())
|
||||||
|
|
||||||
|
# Post UTC - microseconds are rounded (down) nearest millisecond and
|
||||||
|
# dropped
|
||||||
|
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 999)
|
||||||
|
d2 = datetime.datetime(1970, 1, 1, 0, 0, 1)
|
||||||
|
log = LogEntry()
|
||||||
|
log.date = d1
|
||||||
|
log.save()
|
||||||
|
log.reload()
|
||||||
|
self.assertNotEqual(log.date, d1)
|
||||||
|
self.assertEqual(log.date, d2)
|
||||||
|
|
||||||
|
# Post UTC - microseconds are rounded (down) nearest millisecond
|
||||||
|
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 9999)
|
||||||
|
d2 = datetime.datetime(1970, 1, 1, 0, 0, 1, 9000)
|
||||||
|
log.date = d1
|
||||||
|
log.save()
|
||||||
|
log.reload()
|
||||||
|
self.assertNotEqual(log.date, d1)
|
||||||
|
self.assertEqual(log.date, d2)
|
||||||
|
|
||||||
|
if not six.PY3:
|
||||||
|
# Pre UTC dates microseconds below 1000 are dropped
|
||||||
|
# This does not seem to be true in PY3
|
||||||
|
d1 = datetime.datetime(1969, 12, 31, 23, 59, 59, 999)
|
||||||
|
d2 = datetime.datetime(1969, 12, 31, 23, 59, 59)
|
||||||
|
log.date = d1
|
||||||
|
log.save()
|
||||||
|
log.reload()
|
||||||
|
self.assertNotEqual(log.date, d1)
|
||||||
|
self.assertEqual(log.date, d2)
|
||||||
|
|
||||||
|
def test_regular_usage(self):
|
||||||
|
"""Tests for regular datetime fields"""
|
||||||
|
class LogEntry(Document):
|
||||||
|
date = DateTimeField()
|
||||||
|
|
||||||
|
LogEntry.drop_collection()
|
||||||
|
|
||||||
|
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1)
|
||||||
|
log = LogEntry()
|
||||||
|
log.date = d1
|
||||||
|
log.validate()
|
||||||
|
log.save()
|
||||||
|
|
||||||
|
for query in (d1, d1.isoformat(' ')):
|
||||||
|
log1 = LogEntry.objects.get(date=query)
|
||||||
|
self.assertEqual(log, log1)
|
||||||
|
|
||||||
|
if dateutil:
|
||||||
|
log1 = LogEntry.objects.get(date=d1.isoformat('T'))
|
||||||
|
self.assertEqual(log, log1)
|
||||||
|
|
||||||
|
# create additional 19 log entries for a total of 20
|
||||||
|
for i in range(1971, 1990):
|
||||||
|
d = datetime.datetime(i, 1, 1, 0, 0, 1)
|
||||||
|
LogEntry(date=d).save()
|
||||||
|
|
||||||
|
self.assertEqual(LogEntry.objects.count(), 20)
|
||||||
|
|
||||||
|
# Test ordering
|
||||||
|
logs = LogEntry.objects.order_by("date")
|
||||||
|
i = 0
|
||||||
|
while i < 19:
|
||||||
|
self.assertTrue(logs[i].date <= logs[i + 1].date)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
logs = LogEntry.objects.order_by("-date")
|
||||||
|
i = 0
|
||||||
|
while i < 19:
|
||||||
|
self.assertTrue(logs[i].date >= logs[i + 1].date)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# Test searching
|
||||||
|
logs = LogEntry.objects.filter(date__gte=datetime.datetime(1980, 1, 1))
|
||||||
|
self.assertEqual(logs.count(), 10)
|
||||||
|
|
||||||
|
logs = LogEntry.objects.filter(date__lte=datetime.datetime(1980, 1, 1))
|
||||||
|
self.assertEqual(logs.count(), 10)
|
||||||
|
|
||||||
|
logs = LogEntry.objects.filter(
|
||||||
|
date__lte=datetime.datetime(1980, 1, 1),
|
||||||
|
date__gte=datetime.datetime(1975, 1, 1),
|
||||||
|
)
|
||||||
|
self.assertEqual(logs.count(), 5)
|
||||||
|
|
||||||
|
def test_datetime_validation(self):
|
||||||
|
"""Ensure that invalid values cannot be assigned to datetime
|
||||||
|
fields.
|
||||||
|
"""
|
||||||
|
class LogEntry(Document):
|
||||||
|
time = DateTimeField()
|
||||||
|
|
||||||
|
log = LogEntry()
|
||||||
|
log.time = datetime.datetime.now()
|
||||||
|
log.validate()
|
||||||
|
|
||||||
|
log.time = datetime.date.today()
|
||||||
|
log.validate()
|
||||||
|
|
||||||
|
log.time = datetime.datetime.now().isoformat(' ')
|
||||||
|
log.validate()
|
||||||
|
|
||||||
|
if dateutil:
|
||||||
|
log.time = datetime.datetime.now().isoformat('T')
|
||||||
|
log.validate()
|
||||||
|
|
||||||
|
log.time = -1
|
||||||
|
self.assertRaises(ValidationError, log.validate)
|
||||||
|
log.time = 'ABC'
|
||||||
|
self.assertRaises(ValidationError, log.validate)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDateTimeTzAware(MongoDBTestCase):
|
||||||
|
def test_datetime_tz_aware_mark_as_changed(self):
|
||||||
|
# Reset the connections
|
||||||
|
connection._connection_settings = {}
|
||||||
|
connection._connections = {}
|
||||||
|
connection._dbs = {}
|
||||||
|
|
||||||
|
connect(db='mongoenginetest', tz_aware=True)
|
||||||
|
|
||||||
|
class LogEntry(Document):
|
||||||
|
time = DateTimeField()
|
||||||
|
|
||||||
|
LogEntry.drop_collection()
|
||||||
|
|
||||||
|
LogEntry(time=datetime.datetime(2013, 1, 1, 0, 0, 0)).save()
|
||||||
|
|
||||||
|
log = LogEntry.objects.first()
|
||||||
|
log.time = datetime.datetime(2013, 1, 1, 0, 0, 0)
|
||||||
|
self.assertEqual(['time'], log._changed_fields)
|
91
tests/fields/test_decimal_field.py
Normal file
91
tests/fields/test_decimal_field.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from mongoengine import *
|
||||||
|
|
||||||
|
from tests.utils import MongoDBTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestDecimalField(MongoDBTestCase):
|
||||||
|
|
||||||
|
def test_validation(self):
|
||||||
|
"""Ensure that invalid values cannot be assigned to decimal fields.
|
||||||
|
"""
|
||||||
|
class Person(Document):
|
||||||
|
height = DecimalField(min_value=Decimal('0.1'),
|
||||||
|
max_value=Decimal('3.5'))
|
||||||
|
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
Person(height=Decimal('1.89')).save()
|
||||||
|
person = Person.objects.first()
|
||||||
|
self.assertEqual(person.height, Decimal('1.89'))
|
||||||
|
|
||||||
|
person.height = '2.0'
|
||||||
|
person.save()
|
||||||
|
person.height = 0.01
|
||||||
|
self.assertRaises(ValidationError, person.validate)
|
||||||
|
person.height = Decimal('0.01')
|
||||||
|
self.assertRaises(ValidationError, person.validate)
|
||||||
|
person.height = Decimal('4.0')
|
||||||
|
self.assertRaises(ValidationError, person.validate)
|
||||||
|
person.height = 'something invalid'
|
||||||
|
self.assertRaises(ValidationError, person.validate)
|
||||||
|
|
||||||
|
person_2 = Person(height='something invalid')
|
||||||
|
self.assertRaises(ValidationError, person_2.validate)
|
||||||
|
|
||||||
|
def test_comparison(self):
|
||||||
|
class Person(Document):
|
||||||
|
money = DecimalField()
|
||||||
|
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
Person(money=6).save()
|
||||||
|
Person(money=7).save()
|
||||||
|
Person(money=8).save()
|
||||||
|
Person(money=10).save()
|
||||||
|
|
||||||
|
self.assertEqual(2, Person.objects(money__gt=Decimal("7")).count())
|
||||||
|
self.assertEqual(2, Person.objects(money__gt=7).count())
|
||||||
|
self.assertEqual(2, Person.objects(money__gt="7").count())
|
||||||
|
|
||||||
|
self.assertEqual(3, Person.objects(money__gte="7").count())
|
||||||
|
|
||||||
|
def test_storage(self):
|
||||||
|
class Person(Document):
|
||||||
|
float_value = DecimalField(precision=4)
|
||||||
|
string_value = DecimalField(precision=4, force_string=True)
|
||||||
|
|
||||||
|
Person.drop_collection()
|
||||||
|
values_to_store = [10, 10.1, 10.11, "10.111", Decimal("10.1111"), Decimal("10.11111")]
|
||||||
|
for store_at_creation in [True, False]:
|
||||||
|
for value in values_to_store:
|
||||||
|
# to_python is called explicitly if values were sent in the kwargs of __init__
|
||||||
|
if store_at_creation:
|
||||||
|
Person(float_value=value, string_value=value).save()
|
||||||
|
else:
|
||||||
|
person = Person.objects.create()
|
||||||
|
person.float_value = value
|
||||||
|
person.string_value = value
|
||||||
|
person.save()
|
||||||
|
|
||||||
|
# How its stored
|
||||||
|
expected = [
|
||||||
|
{'float_value': 10.0, 'string_value': '10.0000'},
|
||||||
|
{'float_value': 10.1, 'string_value': '10.1000'},
|
||||||
|
{'float_value': 10.11, 'string_value': '10.1100'},
|
||||||
|
{'float_value': 10.111, 'string_value': '10.1110'},
|
||||||
|
{'float_value': 10.1111, 'string_value': '10.1111'},
|
||||||
|
{'float_value': 10.1111, 'string_value': '10.1111'}]
|
||||||
|
expected.extend(expected)
|
||||||
|
actual = list(Person.objects.exclude('id').as_pymongo())
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
# How it comes out locally
|
||||||
|
expected = [Decimal('10.0000'), Decimal('10.1000'), Decimal('10.1100'),
|
||||||
|
Decimal('10.1110'), Decimal('10.1111'), Decimal('10.1111')]
|
||||||
|
expected.extend(expected)
|
||||||
|
for field_name in ['float_value', 'string_value']:
|
||||||
|
actual = list(Person.objects().scalar(field_name))
|
||||||
|
self.assertEqual(expected, actual)
|
324
tests/fields/test_dict_field.py
Normal file
324
tests/fields/test_dict_field.py
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from mongoengine import *
|
||||||
|
from mongoengine.base import BaseDict
|
||||||
|
|
||||||
|
from tests.utils import MongoDBTestCase, get_as_pymongo
|
||||||
|
|
||||||
|
|
||||||
|
class TestDictField(MongoDBTestCase):
|
||||||
|
|
||||||
|
def test_storage(self):
|
||||||
|
class BlogPost(Document):
|
||||||
|
info = DictField()
|
||||||
|
|
||||||
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
|
info = {'testkey': 'testvalue'}
|
||||||
|
post = BlogPost(info=info).save()
|
||||||
|
self.assertEqual(
|
||||||
|
get_as_pymongo(post),
|
||||||
|
{
|
||||||
|
'_id': post.id,
|
||||||
|
'info': info
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_general_things(self):
|
||||||
|
"""Ensure that dict types work as expected."""
|
||||||
|
class BlogPost(Document):
|
||||||
|
info = DictField()
|
||||||
|
|
||||||
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
|
post = BlogPost()
|
||||||
|
post.info = 'my post'
|
||||||
|
self.assertRaises(ValidationError, post.validate)
|
||||||
|
|
||||||
|
post.info = ['test', 'test']
|
||||||
|
self.assertRaises(ValidationError, post.validate)
|
||||||
|
|
||||||
|
post.info = {'$title': 'test'}
|
||||||
|
self.assertRaises(ValidationError, post.validate)
|
||||||
|
|
||||||
|
post.info = {'nested': {'$title': 'test'}}
|
||||||
|
self.assertRaises(ValidationError, post.validate)
|
||||||
|
|
||||||
|
post.info = {'the.title': 'test'}
|
||||||
|
self.assertRaises(ValidationError, post.validate)
|
||||||
|
|
||||||
|
post.info = {'nested': {'the.title': 'test'}}
|
||||||
|
self.assertRaises(ValidationError, post.validate)
|
||||||
|
|
||||||
|
post.info = {1: 'test'}
|
||||||
|
self.assertRaises(ValidationError, post.validate)
|
||||||
|
|
||||||
|
post.info = {'title': 'test'}
|
||||||
|
post.save()
|
||||||
|
|
||||||
|
post = BlogPost()
|
||||||
|
post.info = {'title': 'dollar_sign', 'details': {'te$t': 'test'}}
|
||||||
|
post.save()
|
||||||
|
|
||||||
|
post = BlogPost()
|
||||||
|
post.info = {'details': {'test': 'test'}}
|
||||||
|
post.save()
|
||||||
|
|
||||||
|
post = BlogPost()
|
||||||
|
post.info = {'details': {'test': 3}}
|
||||||
|
post.save()
|
||||||
|
|
||||||
|
self.assertEqual(BlogPost.objects.count(), 4)
|
||||||
|
self.assertEqual(
|
||||||
|
BlogPost.objects.filter(info__title__exact='test').count(), 1)
|
||||||
|
self.assertEqual(
|
||||||
|
BlogPost.objects.filter(info__details__test__exact='test').count(), 1)
|
||||||
|
|
||||||
|
post = BlogPost.objects.filter(info__title__exact='dollar_sign').first()
|
||||||
|
self.assertIn('te$t', post['info']['details'])
|
||||||
|
|
||||||
|
# Confirm handles non strings or non existing keys
|
||||||
|
self.assertEqual(
|
||||||
|
BlogPost.objects.filter(info__details__test__exact=5).count(), 0)
|
||||||
|
self.assertEqual(
|
||||||
|
BlogPost.objects.filter(info__made_up__test__exact='test').count(), 0)
|
||||||
|
|
||||||
|
post = BlogPost.objects.create(info={'title': 'original'})
|
||||||
|
post.info.update({'title': 'updated'})
|
||||||
|
post.save()
|
||||||
|
post.reload()
|
||||||
|
self.assertEqual('updated', post.info['title'])
|
||||||
|
|
||||||
|
post.info.setdefault('authors', [])
|
||||||
|
post.save()
|
||||||
|
post.reload()
|
||||||
|
self.assertEqual([], post.info['authors'])
|
||||||
|
|
||||||
|
def test_dictfield_dump_document(self):
|
||||||
|
"""Ensure a DictField can handle another document's dump."""
|
||||||
|
class Doc(Document):
|
||||||
|
field = DictField()
|
||||||
|
|
||||||
|
class ToEmbed(Document):
|
||||||
|
id = IntField(primary_key=True, default=1)
|
||||||
|
recursive = DictField()
|
||||||
|
|
||||||
|
class ToEmbedParent(Document):
|
||||||
|
id = IntField(primary_key=True, default=1)
|
||||||
|
recursive = DictField()
|
||||||
|
|
||||||
|
meta = {'allow_inheritance': True}
|
||||||
|
|
||||||
|
class ToEmbedChild(ToEmbedParent):
|
||||||
|
pass
|
||||||
|
|
||||||
|
to_embed_recursive = ToEmbed(id=1).save()
|
||||||
|
to_embed = ToEmbed(
|
||||||
|
id=2, recursive=to_embed_recursive.to_mongo().to_dict()).save()
|
||||||
|
doc = Doc(field=to_embed.to_mongo().to_dict())
|
||||||
|
doc.save()
|
||||||
|
self.assertIsInstance(doc.field, dict)
|
||||||
|
self.assertEqual(doc.field, {'_id': 2, 'recursive': {'_id': 1, 'recursive': {}}})
|
||||||
|
# Same thing with a Document with a _cls field
|
||||||
|
to_embed_recursive = ToEmbedChild(id=1).save()
|
||||||
|
to_embed_child = ToEmbedChild(
|
||||||
|
id=2, recursive=to_embed_recursive.to_mongo().to_dict()).save()
|
||||||
|
doc = Doc(field=to_embed_child.to_mongo().to_dict())
|
||||||
|
doc.save()
|
||||||
|
self.assertIsInstance(doc.field, dict)
|
||||||
|
expected = {
|
||||||
|
'_id': 2, '_cls': 'ToEmbedParent.ToEmbedChild',
|
||||||
|
'recursive': {'_id': 1, '_cls': 'ToEmbedParent.ToEmbedChild', 'recursive': {}}
|
||||||
|
}
|
||||||
|
self.assertEqual(doc.field, expected)
|
||||||
|
|
||||||
|
def test_dictfield_strict(self):
|
||||||
|
"""Ensure that dict field handles validation if provided a strict field type."""
|
||||||
|
class Simple(Document):
|
||||||
|
mapping = DictField(field=IntField())
|
||||||
|
|
||||||
|
Simple.drop_collection()
|
||||||
|
|
||||||
|
e = Simple()
|
||||||
|
e.mapping['someint'] = 1
|
||||||
|
e.save()
|
||||||
|
|
||||||
|
# try creating an invalid mapping
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
e.mapping['somestring'] = "abc"
|
||||||
|
e.save()
|
||||||
|
|
||||||
|
def test_dictfield_complex(self):
|
||||||
|
"""Ensure that the dict field can handle the complex types."""
|
||||||
|
class SettingBase(EmbeddedDocument):
|
||||||
|
meta = {'allow_inheritance': True}
|
||||||
|
|
||||||
|
class StringSetting(SettingBase):
|
||||||
|
value = StringField()
|
||||||
|
|
||||||
|
class IntegerSetting(SettingBase):
|
||||||
|
value = IntField()
|
||||||
|
|
||||||
|
class Simple(Document):
|
||||||
|
mapping = DictField()
|
||||||
|
|
||||||
|
Simple.drop_collection()
|
||||||
|
|
||||||
|
e = Simple()
|
||||||
|
e.mapping['somestring'] = StringSetting(value='foo')
|
||||||
|
e.mapping['someint'] = IntegerSetting(value=42)
|
||||||
|
e.mapping['nested_dict'] = {'number': 1, 'string': 'Hi!',
|
||||||
|
'float': 1.001,
|
||||||
|
'complex': IntegerSetting(value=42),
|
||||||
|
'list': [IntegerSetting(value=42),
|
||||||
|
StringSetting(value='foo')]}
|
||||||
|
e.save()
|
||||||
|
|
||||||
|
e2 = Simple.objects.get(id=e.id)
|
||||||
|
self.assertIsInstance(e2.mapping['somestring'], StringSetting)
|
||||||
|
self.assertIsInstance(e2.mapping['someint'], IntegerSetting)
|
||||||
|
|
||||||
|
# Test querying
|
||||||
|
self.assertEqual(
|
||||||
|
Simple.objects.filter(mapping__someint__value=42).count(), 1)
|
||||||
|
self.assertEqual(
|
||||||
|
Simple.objects.filter(mapping__nested_dict__number=1).count(), 1)
|
||||||
|
self.assertEqual(
|
||||||
|
Simple.objects.filter(mapping__nested_dict__complex__value=42).count(), 1)
|
||||||
|
self.assertEqual(
|
||||||
|
Simple.objects.filter(mapping__nested_dict__list__0__value=42).count(), 1)
|
||||||
|
self.assertEqual(
|
||||||
|
Simple.objects.filter(mapping__nested_dict__list__1__value='foo').count(), 1)
|
||||||
|
|
||||||
|
# Confirm can update
|
||||||
|
Simple.objects().update(
|
||||||
|
set__mapping={"someint": IntegerSetting(value=10)})
|
||||||
|
Simple.objects().update(
|
||||||
|
set__mapping__nested_dict__list__1=StringSetting(value='Boo'))
|
||||||
|
self.assertEqual(
|
||||||
|
Simple.objects.filter(mapping__nested_dict__list__1__value='foo').count(), 0)
|
||||||
|
self.assertEqual(
|
||||||
|
Simple.objects.filter(mapping__nested_dict__list__1__value='Boo').count(), 1)
|
||||||
|
|
||||||
|
def test_push_dict(self):
|
||||||
|
class MyModel(Document):
|
||||||
|
events = ListField(DictField())
|
||||||
|
|
||||||
|
doc = MyModel(events=[{'a': 1}]).save()
|
||||||
|
raw_doc = get_as_pymongo(doc)
|
||||||
|
expected_raw_doc = {
|
||||||
|
'_id': doc.id,
|
||||||
|
'events': [{'a': 1}]
|
||||||
|
}
|
||||||
|
self.assertEqual(raw_doc, expected_raw_doc)
|
||||||
|
|
||||||
|
MyModel.objects(id=doc.id).update(push__events={})
|
||||||
|
raw_doc = get_as_pymongo(doc)
|
||||||
|
expected_raw_doc = {
|
||||||
|
'_id': doc.id,
|
||||||
|
'events': [{'a': 1}, {}]
|
||||||
|
}
|
||||||
|
self.assertEqual(raw_doc, expected_raw_doc)
|
||||||
|
|
||||||
|
def test_ensure_unique_default_instances(self):
|
||||||
|
"""Ensure that every field has it's own unique default instance."""
|
||||||
|
class D(Document):
|
||||||
|
data = DictField()
|
||||||
|
data2 = DictField(default=lambda: {})
|
||||||
|
|
||||||
|
d1 = D()
|
||||||
|
d1.data['foo'] = 'bar'
|
||||||
|
d1.data2['foo'] = 'bar'
|
||||||
|
d2 = D()
|
||||||
|
self.assertEqual(d2.data, {})
|
||||||
|
self.assertEqual(d2.data2, {})
|
||||||
|
|
||||||
|
def test_dict_field_invalid_dict_value(self):
|
||||||
|
class DictFieldTest(Document):
|
||||||
|
dictionary = DictField(required=True)
|
||||||
|
|
||||||
|
DictFieldTest.drop_collection()
|
||||||
|
|
||||||
|
test = DictFieldTest(dictionary=None)
|
||||||
|
test.dictionary # Just access to test getter
|
||||||
|
self.assertRaises(ValidationError, test.validate)
|
||||||
|
|
||||||
|
test = DictFieldTest(dictionary=False)
|
||||||
|
test.dictionary # Just access to test getter
|
||||||
|
self.assertRaises(ValidationError, test.validate)
|
||||||
|
|
||||||
|
def test_dict_field_raises_validation_error_if_wrongly_assign_embedded_doc(self):
|
||||||
|
class DictFieldTest(Document):
|
||||||
|
dictionary = DictField(required=True)
|
||||||
|
|
||||||
|
DictFieldTest.drop_collection()
|
||||||
|
|
||||||
|
class Embedded(EmbeddedDocument):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
embed = Embedded(name='garbage')
|
||||||
|
doc = DictFieldTest(dictionary=embed)
|
||||||
|
with self.assertRaises(ValidationError) as ctx_err:
|
||||||
|
doc.validate()
|
||||||
|
self.assertIn("'dictionary'", str(ctx_err.exception))
|
||||||
|
self.assertIn('Only dictionaries may be used in a DictField', str(ctx_err.exception))
|
||||||
|
|
||||||
|
def test_atomic_update_dict_field(self):
|
||||||
|
"""Ensure that the entire DictField can be atomically updated."""
|
||||||
|
class Simple(Document):
|
||||||
|
mapping = DictField(field=ListField(IntField(required=True)))
|
||||||
|
|
||||||
|
Simple.drop_collection()
|
||||||
|
|
||||||
|
e = Simple()
|
||||||
|
e.mapping['someints'] = [1, 2]
|
||||||
|
e.save()
|
||||||
|
e.update(set__mapping={"ints": [3, 4]})
|
||||||
|
e.reload()
|
||||||
|
self.assertEqual(BaseDict, type(e.mapping))
|
||||||
|
self.assertEqual({"ints": [3, 4]}, e.mapping)
|
||||||
|
|
||||||
|
# try creating an invalid mapping
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
e.update(set__mapping={"somestrings": ["foo", "bar", ]})
|
||||||
|
|
||||||
|
def test_dictfield_with_referencefield_complex_nesting_cases(self):
|
||||||
|
"""Ensure complex nesting inside DictField handles dereferencing of ReferenceField(dbref=True | False)"""
|
||||||
|
# Relates to Issue #1453
|
||||||
|
class Doc(Document):
|
||||||
|
s = StringField()
|
||||||
|
|
||||||
|
class Simple(Document):
|
||||||
|
mapping0 = DictField(ReferenceField(Doc, dbref=True))
|
||||||
|
mapping1 = DictField(ReferenceField(Doc, dbref=False))
|
||||||
|
mapping2 = DictField(ListField(ReferenceField(Doc, dbref=True)))
|
||||||
|
mapping3 = DictField(ListField(ReferenceField(Doc, dbref=False)))
|
||||||
|
mapping4 = DictField(DictField(field=ReferenceField(Doc, dbref=True)))
|
||||||
|
mapping5 = DictField(DictField(field=ReferenceField(Doc, dbref=False)))
|
||||||
|
mapping6 = DictField(ListField(DictField(ReferenceField(Doc, dbref=True))))
|
||||||
|
mapping7 = DictField(ListField(DictField(ReferenceField(Doc, dbref=False))))
|
||||||
|
mapping8 = DictField(ListField(DictField(ListField(ReferenceField(Doc, dbref=True)))))
|
||||||
|
mapping9 = DictField(ListField(DictField(ListField(ReferenceField(Doc, dbref=False)))))
|
||||||
|
|
||||||
|
Doc.drop_collection()
|
||||||
|
Simple.drop_collection()
|
||||||
|
|
||||||
|
d = Doc(s='aa').save()
|
||||||
|
e = Simple()
|
||||||
|
e.mapping0['someint'] = e.mapping1['someint'] = d
|
||||||
|
e.mapping2['someint'] = e.mapping3['someint'] = [d]
|
||||||
|
e.mapping4['someint'] = e.mapping5['someint'] = {'d': d}
|
||||||
|
e.mapping6['someint'] = e.mapping7['someint'] = [{'d': d}]
|
||||||
|
e.mapping8['someint'] = e.mapping9['someint'] = [{'d': [d]}]
|
||||||
|
e.save()
|
||||||
|
|
||||||
|
s = Simple.objects.first()
|
||||||
|
self.assertIsInstance(s.mapping0['someint'], Doc)
|
||||||
|
self.assertIsInstance(s.mapping1['someint'], Doc)
|
||||||
|
self.assertIsInstance(s.mapping2['someint'][0], Doc)
|
||||||
|
self.assertIsInstance(s.mapping3['someint'][0], Doc)
|
||||||
|
self.assertIsInstance(s.mapping4['someint']['d'], Doc)
|
||||||
|
self.assertIsInstance(s.mapping5['someint']['d'], Doc)
|
||||||
|
self.assertIsInstance(s.mapping6['someint'][0]['d'], Doc)
|
||||||
|
self.assertIsInstance(s.mapping7['someint'][0]['d'], Doc)
|
||||||
|
self.assertIsInstance(s.mapping8['someint'][0]['d'][0], Doc)
|
||||||
|
self.assertIsInstance(s.mapping9['someint'][0]['d'][0], Doc)
|
120
tests/fields/test_email_field.py
Normal file
120
tests/fields/test_email_field.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys
|
||||||
|
from unittest import SkipTest
|
||||||
|
|
||||||
|
from mongoengine import *
|
||||||
|
|
||||||
|
from tests.utils import MongoDBTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestEmailField(MongoDBTestCase):
|
||||||
|
def test_generic_behavior(self):
|
||||||
|
class User(Document):
|
||||||
|
email = EmailField()
|
||||||
|
|
||||||
|
user = User(email='ross@example.com')
|
||||||
|
user.validate()
|
||||||
|
|
||||||
|
user = User(email='ross@example.co.uk')
|
||||||
|
user.validate()
|
||||||
|
|
||||||
|
user = User(email=('Kofq@rhom0e4klgauOhpbpNdogawnyIKvQS0wk2mjqrgGQ5S'
|
||||||
|
'aJIazqqWkm7.net'))
|
||||||
|
user.validate()
|
||||||
|
|
||||||
|
user = User(email='new-tld@example.technology')
|
||||||
|
user.validate()
|
||||||
|
|
||||||
|
user = User(email='ross@example.com.')
|
||||||
|
self.assertRaises(ValidationError, user.validate)
|
||||||
|
|
||||||
|
# unicode domain
|
||||||
|
user = User(email=u'user@пример.рф')
|
||||||
|
user.validate()
|
||||||
|
|
||||||
|
# invalid unicode domain
|
||||||
|
user = User(email=u'user@пример')
|
||||||
|
self.assertRaises(ValidationError, user.validate)
|
||||||
|
|
||||||
|
# invalid data type
|
||||||
|
user = User(email=123)
|
||||||
|
self.assertRaises(ValidationError, user.validate)
|
||||||
|
|
||||||
|
def test_email_field_unicode_user(self):
|
||||||
|
# Don't run this test on pypy3, which doesn't support unicode regex:
|
||||||
|
# https://bitbucket.org/pypy/pypy/issues/1821/regular-expression-doesnt-find-unicode
|
||||||
|
if sys.version_info[:2] == (3, 2):
|
||||||
|
raise SkipTest('unicode email addresses are not supported on PyPy 3')
|
||||||
|
|
||||||
|
class User(Document):
|
||||||
|
email = EmailField()
|
||||||
|
|
||||||
|
# unicode user shouldn't validate by default...
|
||||||
|
user = User(email=u'Dörte@Sörensen.example.com')
|
||||||
|
self.assertRaises(ValidationError, user.validate)
|
||||||
|
|
||||||
|
# ...but it should be fine with allow_utf8_user set to True
|
||||||
|
class User(Document):
|
||||||
|
email = EmailField(allow_utf8_user=True)
|
||||||
|
|
||||||
|
user = User(email=u'Dörte@Sörensen.example.com')
|
||||||
|
user.validate()
|
||||||
|
|
||||||
|
def test_email_field_domain_whitelist(self):
|
||||||
|
class User(Document):
|
||||||
|
email = EmailField()
|
||||||
|
|
||||||
|
# localhost domain shouldn't validate by default...
|
||||||
|
user = User(email='me@localhost')
|
||||||
|
self.assertRaises(ValidationError, user.validate)
|
||||||
|
|
||||||
|
# ...but it should be fine if it's whitelisted
|
||||||
|
class User(Document):
|
||||||
|
email = EmailField(domain_whitelist=['localhost'])
|
||||||
|
|
||||||
|
user = User(email='me@localhost')
|
||||||
|
user.validate()
|
||||||
|
|
||||||
|
def test_email_field_ip_domain(self):
|
||||||
|
class User(Document):
|
||||||
|
email = EmailField()
|
||||||
|
|
||||||
|
valid_ipv4 = 'email@[127.0.0.1]'
|
||||||
|
valid_ipv6 = 'email@[2001:dB8::1]'
|
||||||
|
invalid_ip = 'email@[324.0.0.1]'
|
||||||
|
|
||||||
|
# IP address as a domain shouldn't validate by default...
|
||||||
|
user = User(email=valid_ipv4)
|
||||||
|
self.assertRaises(ValidationError, user.validate)
|
||||||
|
|
||||||
|
user = User(email=valid_ipv6)
|
||||||
|
self.assertRaises(ValidationError, user.validate)
|
||||||
|
|
||||||
|
user = User(email=invalid_ip)
|
||||||
|
self.assertRaises(ValidationError, user.validate)
|
||||||
|
|
||||||
|
# ...but it should be fine with allow_ip_domain set to True
|
||||||
|
class User(Document):
|
||||||
|
email = EmailField(allow_ip_domain=True)
|
||||||
|
|
||||||
|
user = User(email=valid_ipv4)
|
||||||
|
user.validate()
|
||||||
|
|
||||||
|
user = User(email=valid_ipv6)
|
||||||
|
user.validate()
|
||||||
|
|
||||||
|
# invalid IP should still fail validation
|
||||||
|
user = User(email=invalid_ip)
|
||||||
|
self.assertRaises(ValidationError, user.validate)
|
||||||
|
|
||||||
|
def test_email_field_honors_regex(self):
|
||||||
|
class User(Document):
|
||||||
|
email = EmailField(regex=r'\w+@example.com')
|
||||||
|
|
||||||
|
# Fails regex validation
|
||||||
|
user = User(email='me@foo.com')
|
||||||
|
self.assertRaises(ValidationError, user.validate)
|
||||||
|
|
||||||
|
# Passes regex validation
|
||||||
|
user = User(email='me@example.com')
|
||||||
|
self.assertIsNone(user.validate())
|
58
tests/fields/test_float_field.py
Normal file
58
tests/fields/test_float_field.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import six
|
||||||
|
|
||||||
|
from mongoengine import *
|
||||||
|
|
||||||
|
from tests.utils import MongoDBTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestFloatField(MongoDBTestCase):
|
||||||
|
|
||||||
|
def test_float_ne_operator(self):
|
||||||
|
class TestDocument(Document):
|
||||||
|
float_fld = FloatField()
|
||||||
|
|
||||||
|
TestDocument.drop_collection()
|
||||||
|
|
||||||
|
TestDocument(float_fld=None).save()
|
||||||
|
TestDocument(float_fld=1).save()
|
||||||
|
|
||||||
|
self.assertEqual(1, TestDocument.objects(float_fld__ne=None).count())
|
||||||
|
self.assertEqual(1, TestDocument.objects(float_fld__ne=1).count())
|
||||||
|
|
||||||
|
def test_validation(self):
|
||||||
|
"""Ensure that invalid values cannot be assigned to float fields.
|
||||||
|
"""
|
||||||
|
class Person(Document):
|
||||||
|
height = FloatField(min_value=0.1, max_value=3.5)
|
||||||
|
|
||||||
|
class BigPerson(Document):
|
||||||
|
height = FloatField()
|
||||||
|
|
||||||
|
person = Person()
|
||||||
|
person.height = 1.89
|
||||||
|
person.validate()
|
||||||
|
|
||||||
|
person.height = '2.0'
|
||||||
|
self.assertRaises(ValidationError, person.validate)
|
||||||
|
|
||||||
|
person.height = 0.01
|
||||||
|
self.assertRaises(ValidationError, person.validate)
|
||||||
|
|
||||||
|
person.height = 4.0
|
||||||
|
self.assertRaises(ValidationError, person.validate)
|
||||||
|
|
||||||
|
person_2 = Person(height='something invalid')
|
||||||
|
self.assertRaises(ValidationError, person_2.validate)
|
||||||
|
|
||||||
|
big_person = BigPerson()
|
||||||
|
|
||||||
|
for value, value_type in enumerate(six.integer_types):
|
||||||
|
big_person.height = value_type(value)
|
||||||
|
big_person.validate()
|
||||||
|
|
||||||
|
big_person.height = 2 ** 500
|
||||||
|
big_person.validate()
|
||||||
|
|
||||||
|
big_person.height = 2 ** 100000 # Too big for a float value
|
||||||
|
self.assertRaises(ValidationError, big_person.validate)
|
42
tests/fields/test_int_field.py
Normal file
42
tests/fields/test_int_field.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from mongoengine import *
|
||||||
|
|
||||||
|
from tests.utils import MongoDBTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestIntField(MongoDBTestCase):
|
||||||
|
|
||||||
|
def test_int_validation(self):
|
||||||
|
"""Ensure that invalid values cannot be assigned to int fields.
|
||||||
|
"""
|
||||||
|
class Person(Document):
|
||||||
|
age = IntField(min_value=0, max_value=110)
|
||||||
|
|
||||||
|
person = Person()
|
||||||
|
person.age = 0
|
||||||
|
person.validate()
|
||||||
|
|
||||||
|
person.age = 50
|
||||||
|
person.validate()
|
||||||
|
|
||||||
|
person.age = 110
|
||||||
|
person.validate()
|
||||||
|
|
||||||
|
person.age = -1
|
||||||
|
self.assertRaises(ValidationError, person.validate)
|
||||||
|
person.age = 120
|
||||||
|
self.assertRaises(ValidationError, person.validate)
|
||||||
|
person.age = 'ten'
|
||||||
|
self.assertRaises(ValidationError, person.validate)
|
||||||
|
|
||||||
|
def test_ne_operator(self):
|
||||||
|
class TestDocument(Document):
|
||||||
|
int_fld = IntField()
|
||||||
|
|
||||||
|
TestDocument.drop_collection()
|
||||||
|
|
||||||
|
TestDocument(int_fld=None).save()
|
||||||
|
TestDocument(int_fld=1).save()
|
||||||
|
|
||||||
|
self.assertEqual(1, TestDocument.objects(int_fld__ne=None).count())
|
||||||
|
self.assertEqual(1, TestDocument.objects(int_fld__ne=1).count())
|
524
tests/fields/test_lazy_reference_field.py
Normal file
524
tests/fields/test_lazy_reference_field.py
Normal file
@@ -0,0 +1,524 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from bson import DBRef, ObjectId
|
||||||
|
|
||||||
|
from mongoengine import *
|
||||||
|
from mongoengine.base import LazyReference
|
||||||
|
|
||||||
|
from tests.utils import MongoDBTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestLazyReferenceField(MongoDBTestCase):
|
||||||
|
def test_lazy_reference_config(self):
|
||||||
|
# Make sure ReferenceField only accepts a document class or a string
|
||||||
|
# with a document class name.
|
||||||
|
self.assertRaises(ValidationError, LazyReferenceField, EmbeddedDocument)
|
||||||
|
|
||||||
|
def test_lazy_reference_simple(self):
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
class Ocurrence(Document):
|
||||||
|
person = StringField()
|
||||||
|
animal = LazyReferenceField(Animal)
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocurrence.drop_collection()
|
||||||
|
|
||||||
|
animal = Animal(name="Leopard", tag="heavy").save()
|
||||||
|
Ocurrence(person="test", animal=animal).save()
|
||||||
|
p = Ocurrence.objects.get()
|
||||||
|
self.assertIsInstance(p.animal, LazyReference)
|
||||||
|
fetched_animal = p.animal.fetch()
|
||||||
|
self.assertEqual(fetched_animal, animal)
|
||||||
|
# `fetch` keep cache on referenced document by default...
|
||||||
|
animal.tag = "not so heavy"
|
||||||
|
animal.save()
|
||||||
|
double_fetch = p.animal.fetch()
|
||||||
|
self.assertIs(fetched_animal, double_fetch)
|
||||||
|
self.assertEqual(double_fetch.tag, "heavy")
|
||||||
|
# ...unless specified otherwise
|
||||||
|
fetch_force = p.animal.fetch(force=True)
|
||||||
|
self.assertIsNot(fetch_force, fetched_animal)
|
||||||
|
self.assertEqual(fetch_force.tag, "not so heavy")
|
||||||
|
|
||||||
|
def test_lazy_reference_fetch_invalid_ref(self):
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
class Ocurrence(Document):
|
||||||
|
person = StringField()
|
||||||
|
animal = LazyReferenceField(Animal)
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocurrence.drop_collection()
|
||||||
|
|
||||||
|
animal = Animal(name="Leopard", tag="heavy").save()
|
||||||
|
Ocurrence(person="test", animal=animal).save()
|
||||||
|
animal.delete()
|
||||||
|
p = Ocurrence.objects.get()
|
||||||
|
self.assertIsInstance(p.animal, LazyReference)
|
||||||
|
with self.assertRaises(DoesNotExist):
|
||||||
|
p.animal.fetch()
|
||||||
|
|
||||||
|
def test_lazy_reference_set(self):
|
||||||
|
class Animal(Document):
|
||||||
|
meta = {'allow_inheritance': True}
|
||||||
|
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
class Ocurrence(Document):
|
||||||
|
person = StringField()
|
||||||
|
animal = LazyReferenceField(Animal)
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocurrence.drop_collection()
|
||||||
|
|
||||||
|
class SubAnimal(Animal):
|
||||||
|
nick = StringField()
|
||||||
|
|
||||||
|
animal = Animal(name="Leopard", tag="heavy").save()
|
||||||
|
sub_animal = SubAnimal(nick='doggo', name='dog').save()
|
||||||
|
for ref in (
|
||||||
|
animal,
|
||||||
|
animal.pk,
|
||||||
|
DBRef(animal._get_collection_name(), animal.pk),
|
||||||
|
LazyReference(Animal, animal.pk),
|
||||||
|
|
||||||
|
sub_animal,
|
||||||
|
sub_animal.pk,
|
||||||
|
DBRef(sub_animal._get_collection_name(), sub_animal.pk),
|
||||||
|
LazyReference(SubAnimal, sub_animal.pk),
|
||||||
|
):
|
||||||
|
p = Ocurrence(person="test", animal=ref).save()
|
||||||
|
p.reload()
|
||||||
|
self.assertIsInstance(p.animal, LazyReference)
|
||||||
|
p.animal.fetch()
|
||||||
|
|
||||||
|
def test_lazy_reference_bad_set(self):
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
class Ocurrence(Document):
|
||||||
|
person = StringField()
|
||||||
|
animal = LazyReferenceField(Animal)
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocurrence.drop_collection()
|
||||||
|
|
||||||
|
class BadDoc(Document):
|
||||||
|
pass
|
||||||
|
|
||||||
|
animal = Animal(name="Leopard", tag="heavy").save()
|
||||||
|
baddoc = BadDoc().save()
|
||||||
|
for bad in (
|
||||||
|
42,
|
||||||
|
'foo',
|
||||||
|
baddoc,
|
||||||
|
DBRef(baddoc._get_collection_name(), animal.pk),
|
||||||
|
LazyReference(BadDoc, animal.pk)
|
||||||
|
):
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
p = Ocurrence(person="test", animal=bad).save()
|
||||||
|
|
||||||
|
def test_lazy_reference_query_conversion(self):
|
||||||
|
"""Ensure that LazyReferenceFields can be queried using objects and values
|
||||||
|
of the type of the primary key of the referenced object.
|
||||||
|
"""
|
||||||
|
class Member(Document):
|
||||||
|
user_num = IntField(primary_key=True)
|
||||||
|
|
||||||
|
class BlogPost(Document):
|
||||||
|
title = StringField()
|
||||||
|
author = LazyReferenceField(Member, dbref=False)
|
||||||
|
|
||||||
|
Member.drop_collection()
|
||||||
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
|
m1 = Member(user_num=1)
|
||||||
|
m1.save()
|
||||||
|
m2 = Member(user_num=2)
|
||||||
|
m2.save()
|
||||||
|
|
||||||
|
post1 = BlogPost(title='post 1', author=m1)
|
||||||
|
post1.save()
|
||||||
|
|
||||||
|
post2 = BlogPost(title='post 2', author=m2)
|
||||||
|
post2.save()
|
||||||
|
|
||||||
|
post = BlogPost.objects(author=m1).first()
|
||||||
|
self.assertEqual(post.id, post1.id)
|
||||||
|
|
||||||
|
post = BlogPost.objects(author=m2).first()
|
||||||
|
self.assertEqual(post.id, post2.id)
|
||||||
|
|
||||||
|
# Same thing by passing a LazyReference instance
|
||||||
|
post = BlogPost.objects(author=LazyReference(Member, m2.pk)).first()
|
||||||
|
self.assertEqual(post.id, post2.id)
|
||||||
|
|
||||||
|
def test_lazy_reference_query_conversion_dbref(self):
|
||||||
|
"""Ensure that LazyReferenceFields can be queried using objects and values
|
||||||
|
of the type of the primary key of the referenced object.
|
||||||
|
"""
|
||||||
|
class Member(Document):
|
||||||
|
user_num = IntField(primary_key=True)
|
||||||
|
|
||||||
|
class BlogPost(Document):
|
||||||
|
title = StringField()
|
||||||
|
author = LazyReferenceField(Member, dbref=True)
|
||||||
|
|
||||||
|
Member.drop_collection()
|
||||||
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
|
m1 = Member(user_num=1)
|
||||||
|
m1.save()
|
||||||
|
m2 = Member(user_num=2)
|
||||||
|
m2.save()
|
||||||
|
|
||||||
|
post1 = BlogPost(title='post 1', author=m1)
|
||||||
|
post1.save()
|
||||||
|
|
||||||
|
post2 = BlogPost(title='post 2', author=m2)
|
||||||
|
post2.save()
|
||||||
|
|
||||||
|
post = BlogPost.objects(author=m1).first()
|
||||||
|
self.assertEqual(post.id, post1.id)
|
||||||
|
|
||||||
|
post = BlogPost.objects(author=m2).first()
|
||||||
|
self.assertEqual(post.id, post2.id)
|
||||||
|
|
||||||
|
# Same thing by passing a LazyReference instance
|
||||||
|
post = BlogPost.objects(author=LazyReference(Member, m2.pk)).first()
|
||||||
|
self.assertEqual(post.id, post2.id)
|
||||||
|
|
||||||
|
def test_lazy_reference_passthrough(self):
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
class Ocurrence(Document):
|
||||||
|
animal = LazyReferenceField(Animal, passthrough=False)
|
||||||
|
animal_passthrough = LazyReferenceField(Animal, passthrough=True)
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocurrence.drop_collection()
|
||||||
|
|
||||||
|
animal = Animal(name="Leopard", tag="heavy").save()
|
||||||
|
Ocurrence(animal=animal, animal_passthrough=animal).save()
|
||||||
|
p = Ocurrence.objects.get()
|
||||||
|
self.assertIsInstance(p.animal, LazyReference)
|
||||||
|
with self.assertRaises(KeyError):
|
||||||
|
p.animal['name']
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
p.animal.name
|
||||||
|
self.assertEqual(p.animal.pk, animal.pk)
|
||||||
|
|
||||||
|
self.assertEqual(p.animal_passthrough.name, "Leopard")
|
||||||
|
self.assertEqual(p.animal_passthrough['name'], "Leopard")
|
||||||
|
|
||||||
|
# Should not be able to access referenced document's methods
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
p.animal.save
|
||||||
|
with self.assertRaises(KeyError):
|
||||||
|
p.animal['save']
|
||||||
|
|
||||||
|
def test_lazy_reference_not_set(self):
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
class Ocurrence(Document):
|
||||||
|
person = StringField()
|
||||||
|
animal = LazyReferenceField(Animal)
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocurrence.drop_collection()
|
||||||
|
|
||||||
|
Ocurrence(person='foo').save()
|
||||||
|
p = Ocurrence.objects.get()
|
||||||
|
self.assertIs(p.animal, None)
|
||||||
|
|
||||||
|
def test_lazy_reference_equality(self):
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
|
||||||
|
animal = Animal(name="Leopard", tag="heavy").save()
|
||||||
|
animalref = LazyReference(Animal, animal.pk)
|
||||||
|
self.assertEqual(animal, animalref)
|
||||||
|
self.assertEqual(animalref, animal)
|
||||||
|
|
||||||
|
other_animalref = LazyReference(Animal, ObjectId("54495ad94c934721ede76f90"))
|
||||||
|
self.assertNotEqual(animal, other_animalref)
|
||||||
|
self.assertNotEqual(other_animalref, animal)
|
||||||
|
|
||||||
|
def test_lazy_reference_embedded(self):
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
class EmbeddedOcurrence(EmbeddedDocument):
|
||||||
|
in_list = ListField(LazyReferenceField(Animal))
|
||||||
|
direct = LazyReferenceField(Animal)
|
||||||
|
|
||||||
|
class Ocurrence(Document):
|
||||||
|
in_list = ListField(LazyReferenceField(Animal))
|
||||||
|
in_embedded = EmbeddedDocumentField(EmbeddedOcurrence)
|
||||||
|
direct = LazyReferenceField(Animal)
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocurrence.drop_collection()
|
||||||
|
|
||||||
|
animal1 = Animal('doggo').save()
|
||||||
|
animal2 = Animal('cheeta').save()
|
||||||
|
|
||||||
|
def check_fields_type(occ):
|
||||||
|
self.assertIsInstance(occ.direct, LazyReference)
|
||||||
|
for elem in occ.in_list:
|
||||||
|
self.assertIsInstance(elem, LazyReference)
|
||||||
|
self.assertIsInstance(occ.in_embedded.direct, LazyReference)
|
||||||
|
for elem in occ.in_embedded.in_list:
|
||||||
|
self.assertIsInstance(elem, LazyReference)
|
||||||
|
|
||||||
|
occ = Ocurrence(
|
||||||
|
in_list=[animal1, animal2],
|
||||||
|
in_embedded={'in_list': [animal1, animal2], 'direct': animal1},
|
||||||
|
direct=animal1
|
||||||
|
).save()
|
||||||
|
check_fields_type(occ)
|
||||||
|
occ.reload()
|
||||||
|
check_fields_type(occ)
|
||||||
|
occ.direct = animal1.id
|
||||||
|
occ.in_list = [animal1.id, animal2.id]
|
||||||
|
occ.in_embedded.direct = animal1.id
|
||||||
|
occ.in_embedded.in_list = [animal1.id, animal2.id]
|
||||||
|
check_fields_type(occ)
|
||||||
|
|
||||||
|
|
||||||
|
class TestGenericLazyReferenceField(MongoDBTestCase):
|
||||||
|
def test_generic_lazy_reference_simple(self):
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
class Ocurrence(Document):
|
||||||
|
person = StringField()
|
||||||
|
animal = GenericLazyReferenceField()
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocurrence.drop_collection()
|
||||||
|
|
||||||
|
animal = Animal(name="Leopard", tag="heavy").save()
|
||||||
|
Ocurrence(person="test", animal=animal).save()
|
||||||
|
p = Ocurrence.objects.get()
|
||||||
|
self.assertIsInstance(p.animal, LazyReference)
|
||||||
|
fetched_animal = p.animal.fetch()
|
||||||
|
self.assertEqual(fetched_animal, animal)
|
||||||
|
# `fetch` keep cache on referenced document by default...
|
||||||
|
animal.tag = "not so heavy"
|
||||||
|
animal.save()
|
||||||
|
double_fetch = p.animal.fetch()
|
||||||
|
self.assertIs(fetched_animal, double_fetch)
|
||||||
|
self.assertEqual(double_fetch.tag, "heavy")
|
||||||
|
# ...unless specified otherwise
|
||||||
|
fetch_force = p.animal.fetch(force=True)
|
||||||
|
self.assertIsNot(fetch_force, fetched_animal)
|
||||||
|
self.assertEqual(fetch_force.tag, "not so heavy")
|
||||||
|
|
||||||
|
def test_generic_lazy_reference_choices(self):
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
class Vegetal(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
class Mineral(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
class Ocurrence(Document):
|
||||||
|
living_thing = GenericLazyReferenceField(choices=[Animal, Vegetal])
|
||||||
|
thing = GenericLazyReferenceField()
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Vegetal.drop_collection()
|
||||||
|
Mineral.drop_collection()
|
||||||
|
Ocurrence.drop_collection()
|
||||||
|
|
||||||
|
animal = Animal(name="Leopard").save()
|
||||||
|
vegetal = Vegetal(name="Oak").save()
|
||||||
|
mineral = Mineral(name="Granite").save()
|
||||||
|
|
||||||
|
occ_animal = Ocurrence(living_thing=animal, thing=animal).save()
|
||||||
|
occ_vegetal = Ocurrence(living_thing=vegetal, thing=vegetal).save()
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
Ocurrence(living_thing=mineral).save()
|
||||||
|
|
||||||
|
occ = Ocurrence.objects.get(living_thing=animal)
|
||||||
|
self.assertEqual(occ, occ_animal)
|
||||||
|
self.assertIsInstance(occ.thing, LazyReference)
|
||||||
|
self.assertIsInstance(occ.living_thing, LazyReference)
|
||||||
|
|
||||||
|
occ.thing = vegetal
|
||||||
|
occ.living_thing = vegetal
|
||||||
|
occ.save()
|
||||||
|
|
||||||
|
occ.thing = mineral
|
||||||
|
occ.living_thing = mineral
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
occ.save()
|
||||||
|
|
||||||
|
def test_generic_lazy_reference_set(self):
|
||||||
|
class Animal(Document):
|
||||||
|
meta = {'allow_inheritance': True}
|
||||||
|
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
class Ocurrence(Document):
|
||||||
|
person = StringField()
|
||||||
|
animal = GenericLazyReferenceField()
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocurrence.drop_collection()
|
||||||
|
|
||||||
|
class SubAnimal(Animal):
|
||||||
|
nick = StringField()
|
||||||
|
|
||||||
|
animal = Animal(name="Leopard", tag="heavy").save()
|
||||||
|
sub_animal = SubAnimal(nick='doggo', name='dog').save()
|
||||||
|
for ref in (
|
||||||
|
animal,
|
||||||
|
LazyReference(Animal, animal.pk),
|
||||||
|
{'_cls': 'Animal', '_ref': DBRef(animal._get_collection_name(), animal.pk)},
|
||||||
|
|
||||||
|
sub_animal,
|
||||||
|
LazyReference(SubAnimal, sub_animal.pk),
|
||||||
|
{'_cls': 'SubAnimal', '_ref': DBRef(sub_animal._get_collection_name(), sub_animal.pk)},
|
||||||
|
):
|
||||||
|
p = Ocurrence(person="test", animal=ref).save()
|
||||||
|
p.reload()
|
||||||
|
self.assertIsInstance(p.animal, (LazyReference, Document))
|
||||||
|
p.animal.fetch()
|
||||||
|
|
||||||
|
def test_generic_lazy_reference_bad_set(self):
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
class Ocurrence(Document):
|
||||||
|
person = StringField()
|
||||||
|
animal = GenericLazyReferenceField(choices=['Animal'])
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocurrence.drop_collection()
|
||||||
|
|
||||||
|
class BadDoc(Document):
|
||||||
|
pass
|
||||||
|
|
||||||
|
animal = Animal(name="Leopard", tag="heavy").save()
|
||||||
|
baddoc = BadDoc().save()
|
||||||
|
for bad in (
|
||||||
|
42,
|
||||||
|
'foo',
|
||||||
|
baddoc,
|
||||||
|
LazyReference(BadDoc, animal.pk)
|
||||||
|
):
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
p = Ocurrence(person="test", animal=bad).save()
|
||||||
|
|
||||||
|
def test_generic_lazy_reference_query_conversion(self):
|
||||||
|
class Member(Document):
|
||||||
|
user_num = IntField(primary_key=True)
|
||||||
|
|
||||||
|
class BlogPost(Document):
|
||||||
|
title = StringField()
|
||||||
|
author = GenericLazyReferenceField()
|
||||||
|
|
||||||
|
Member.drop_collection()
|
||||||
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
|
m1 = Member(user_num=1)
|
||||||
|
m1.save()
|
||||||
|
m2 = Member(user_num=2)
|
||||||
|
m2.save()
|
||||||
|
|
||||||
|
post1 = BlogPost(title='post 1', author=m1)
|
||||||
|
post1.save()
|
||||||
|
|
||||||
|
post2 = BlogPost(title='post 2', author=m2)
|
||||||
|
post2.save()
|
||||||
|
|
||||||
|
post = BlogPost.objects(author=m1).first()
|
||||||
|
self.assertEqual(post.id, post1.id)
|
||||||
|
|
||||||
|
post = BlogPost.objects(author=m2).first()
|
||||||
|
self.assertEqual(post.id, post2.id)
|
||||||
|
|
||||||
|
# Same thing by passing a LazyReference instance
|
||||||
|
post = BlogPost.objects(author=LazyReference(Member, m2.pk)).first()
|
||||||
|
self.assertEqual(post.id, post2.id)
|
||||||
|
|
||||||
|
def test_generic_lazy_reference_not_set(self):
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
class Ocurrence(Document):
|
||||||
|
person = StringField()
|
||||||
|
animal = GenericLazyReferenceField()
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocurrence.drop_collection()
|
||||||
|
|
||||||
|
Ocurrence(person='foo').save()
|
||||||
|
p = Ocurrence.objects.get()
|
||||||
|
self.assertIs(p.animal, None)
|
||||||
|
|
||||||
|
def test_generic_lazy_reference_embedded(self):
|
||||||
|
class Animal(Document):
|
||||||
|
name = StringField()
|
||||||
|
tag = StringField()
|
||||||
|
|
||||||
|
class EmbeddedOcurrence(EmbeddedDocument):
|
||||||
|
in_list = ListField(GenericLazyReferenceField())
|
||||||
|
direct = GenericLazyReferenceField()
|
||||||
|
|
||||||
|
class Ocurrence(Document):
|
||||||
|
in_list = ListField(GenericLazyReferenceField())
|
||||||
|
in_embedded = EmbeddedDocumentField(EmbeddedOcurrence)
|
||||||
|
direct = GenericLazyReferenceField()
|
||||||
|
|
||||||
|
Animal.drop_collection()
|
||||||
|
Ocurrence.drop_collection()
|
||||||
|
|
||||||
|
animal1 = Animal('doggo').save()
|
||||||
|
animal2 = Animal('cheeta').save()
|
||||||
|
|
||||||
|
def check_fields_type(occ):
|
||||||
|
self.assertIsInstance(occ.direct, LazyReference)
|
||||||
|
for elem in occ.in_list:
|
||||||
|
self.assertIsInstance(elem, LazyReference)
|
||||||
|
self.assertIsInstance(occ.in_embedded.direct, LazyReference)
|
||||||
|
for elem in occ.in_embedded.in_list:
|
||||||
|
self.assertIsInstance(elem, LazyReference)
|
||||||
|
|
||||||
|
occ = Ocurrence(
|
||||||
|
in_list=[animal1, animal2],
|
||||||
|
in_embedded={'in_list': [animal1, animal2], 'direct': animal1},
|
||||||
|
direct=animal1
|
||||||
|
).save()
|
||||||
|
check_fields_type(occ)
|
||||||
|
occ.reload()
|
||||||
|
check_fields_type(occ)
|
||||||
|
animal1_ref = {'_cls': 'Animal', '_ref': DBRef(animal1._get_collection_name(), animal1.pk)}
|
||||||
|
animal2_ref = {'_cls': 'Animal', '_ref': DBRef(animal2._get_collection_name(), animal2.pk)}
|
||||||
|
occ.direct = animal1_ref
|
||||||
|
occ.in_list = [animal1_ref, animal2_ref]
|
||||||
|
occ.in_embedded.direct = animal1_ref
|
||||||
|
occ.in_embedded.in_list = [animal1_ref, animal2_ref]
|
||||||
|
check_fields_type(occ)
|
56
tests/fields/test_long_field.py
Normal file
56
tests/fields/test_long_field.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import six
|
||||||
|
|
||||||
|
try:
|
||||||
|
from bson.int64 import Int64
|
||||||
|
except ImportError:
|
||||||
|
Int64 = long
|
||||||
|
|
||||||
|
from mongoengine import *
|
||||||
|
from mongoengine.connection import get_db
|
||||||
|
|
||||||
|
from tests.utils import MongoDBTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestLongField(MongoDBTestCase):
|
||||||
|
|
||||||
|
def test_long_field_is_considered_as_int64(self):
|
||||||
|
"""
|
||||||
|
Tests that long fields are stored as long in mongo, even if long
|
||||||
|
value is small enough to be an int.
|
||||||
|
"""
|
||||||
|
class TestLongFieldConsideredAsInt64(Document):
|
||||||
|
some_long = LongField()
|
||||||
|
|
||||||
|
doc = TestLongFieldConsideredAsInt64(some_long=42).save()
|
||||||
|
db = get_db()
|
||||||
|
self.assertIsInstance(db.test_long_field_considered_as_int64.find()[0]['some_long'], Int64)
|
||||||
|
self.assertIsInstance(doc.some_long, six.integer_types)
|
||||||
|
|
||||||
|
def test_long_validation(self):
|
||||||
|
"""Ensure that invalid values cannot be assigned to long fields.
|
||||||
|
"""
|
||||||
|
class TestDocument(Document):
|
||||||
|
value = LongField(min_value=0, max_value=110)
|
||||||
|
|
||||||
|
doc = TestDocument()
|
||||||
|
doc.value = 50
|
||||||
|
doc.validate()
|
||||||
|
|
||||||
|
doc.value = -1
|
||||||
|
self.assertRaises(ValidationError, doc.validate)
|
||||||
|
doc.age = 120
|
||||||
|
self.assertRaises(ValidationError, doc.validate)
|
||||||
|
doc.age = 'ten'
|
||||||
|
self.assertRaises(ValidationError, doc.validate)
|
||||||
|
|
||||||
|
def test_long_ne_operator(self):
|
||||||
|
class TestDocument(Document):
|
||||||
|
long_fld = LongField()
|
||||||
|
|
||||||
|
TestDocument.drop_collection()
|
||||||
|
|
||||||
|
TestDocument(long_fld=None).save()
|
||||||
|
TestDocument(long_fld=1).save()
|
||||||
|
|
||||||
|
self.assertEqual(1, TestDocument.objects(long_fld__ne=None).count())
|
144
tests/fields/test_map_field.py
Normal file
144
tests/fields/test_map_field.py
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from mongoengine import *
|
||||||
|
|
||||||
|
from tests.utils import MongoDBTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestMapField(MongoDBTestCase):
|
||||||
|
|
||||||
|
def test_mapfield(self):
|
||||||
|
"""Ensure that the MapField handles the declared type."""
|
||||||
|
class Simple(Document):
|
||||||
|
mapping = MapField(IntField())
|
||||||
|
|
||||||
|
Simple.drop_collection()
|
||||||
|
|
||||||
|
e = Simple()
|
||||||
|
e.mapping['someint'] = 1
|
||||||
|
e.save()
|
||||||
|
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
e.mapping['somestring'] = "abc"
|
||||||
|
e.save()
|
||||||
|
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
class NoDeclaredType(Document):
|
||||||
|
mapping = MapField()
|
||||||
|
|
||||||
|
def test_complex_mapfield(self):
|
||||||
|
"""Ensure that the MapField can handle complex declared types."""
|
||||||
|
|
||||||
|
class SettingBase(EmbeddedDocument):
|
||||||
|
meta = {"allow_inheritance": True}
|
||||||
|
|
||||||
|
class StringSetting(SettingBase):
|
||||||
|
value = StringField()
|
||||||
|
|
||||||
|
class IntegerSetting(SettingBase):
|
||||||
|
value = IntField()
|
||||||
|
|
||||||
|
class Extensible(Document):
|
||||||
|
mapping = MapField(EmbeddedDocumentField(SettingBase))
|
||||||
|
|
||||||
|
Extensible.drop_collection()
|
||||||
|
|
||||||
|
e = Extensible()
|
||||||
|
e.mapping['somestring'] = StringSetting(value='foo')
|
||||||
|
e.mapping['someint'] = IntegerSetting(value=42)
|
||||||
|
e.save()
|
||||||
|
|
||||||
|
e2 = Extensible.objects.get(id=e.id)
|
||||||
|
self.assertIsInstance(e2.mapping['somestring'], StringSetting)
|
||||||
|
self.assertIsInstance(e2.mapping['someint'], IntegerSetting)
|
||||||
|
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
e.mapping['someint'] = 123
|
||||||
|
e.save()
|
||||||
|
|
||||||
|
def test_embedded_mapfield_db_field(self):
|
||||||
|
class Embedded(EmbeddedDocument):
|
||||||
|
number = IntField(default=0, db_field='i')
|
||||||
|
|
||||||
|
class Test(Document):
|
||||||
|
my_map = MapField(field=EmbeddedDocumentField(Embedded),
|
||||||
|
db_field='x')
|
||||||
|
|
||||||
|
Test.drop_collection()
|
||||||
|
|
||||||
|
test = Test()
|
||||||
|
test.my_map['DICTIONARY_KEY'] = Embedded(number=1)
|
||||||
|
test.save()
|
||||||
|
|
||||||
|
Test.objects.update_one(inc__my_map__DICTIONARY_KEY__number=1)
|
||||||
|
|
||||||
|
test = Test.objects.get()
|
||||||
|
self.assertEqual(test.my_map['DICTIONARY_KEY'].number, 2)
|
||||||
|
doc = self.db.test.find_one()
|
||||||
|
self.assertEqual(doc['x']['DICTIONARY_KEY']['i'], 2)
|
||||||
|
|
||||||
|
def test_mapfield_numerical_index(self):
|
||||||
|
"""Ensure that MapField accept numeric strings as indexes."""
|
||||||
|
|
||||||
|
class Embedded(EmbeddedDocument):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
class Test(Document):
|
||||||
|
my_map = MapField(EmbeddedDocumentField(Embedded))
|
||||||
|
|
||||||
|
Test.drop_collection()
|
||||||
|
|
||||||
|
test = Test()
|
||||||
|
test.my_map['1'] = Embedded(name='test')
|
||||||
|
test.save()
|
||||||
|
test.my_map['1'].name = 'test updated'
|
||||||
|
test.save()
|
||||||
|
|
||||||
|
def test_map_field_lookup(self):
|
||||||
|
"""Ensure MapField lookups succeed on Fields without a lookup
|
||||||
|
method.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Action(EmbeddedDocument):
|
||||||
|
operation = StringField()
|
||||||
|
object = StringField()
|
||||||
|
|
||||||
|
class Log(Document):
|
||||||
|
name = StringField()
|
||||||
|
visited = MapField(DateTimeField())
|
||||||
|
actions = MapField(EmbeddedDocumentField(Action))
|
||||||
|
|
||||||
|
Log.drop_collection()
|
||||||
|
Log(name="wilson", visited={'friends': datetime.datetime.now()},
|
||||||
|
actions={'friends': Action(operation='drink', object='beer')}).save()
|
||||||
|
|
||||||
|
self.assertEqual(1, Log.objects(
|
||||||
|
visited__friends__exists=True).count())
|
||||||
|
|
||||||
|
self.assertEqual(1, Log.objects(
|
||||||
|
actions__friends__operation='drink',
|
||||||
|
actions__friends__object='beer').count())
|
||||||
|
|
||||||
|
def test_map_field_unicode(self):
|
||||||
|
class Info(EmbeddedDocument):
|
||||||
|
description = StringField()
|
||||||
|
value_list = ListField(field=StringField())
|
||||||
|
|
||||||
|
class BlogPost(Document):
|
||||||
|
info_dict = MapField(field=EmbeddedDocumentField(Info))
|
||||||
|
|
||||||
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
|
tree = BlogPost(info_dict={
|
||||||
|
u"éééé": {
|
||||||
|
'description': u"VALUE: éééé"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
tree.save()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
BlogPost.objects.get(id=tree.id).info_dict[u"éééé"].description,
|
||||||
|
u"VALUE: éééé"
|
||||||
|
)
|
219
tests/fields/test_reference_field.py
Normal file
219
tests/fields/test_reference_field.py
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from bson import SON, DBRef
|
||||||
|
|
||||||
|
from mongoengine import *
|
||||||
|
|
||||||
|
from tests.utils import MongoDBTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestReferenceField(MongoDBTestCase):
|
||||||
|
def test_reference_validation(self):
|
||||||
|
"""Ensure that invalid document objects cannot be assigned to
|
||||||
|
reference fields.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class User(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
class BlogPost(Document):
|
||||||
|
content = StringField()
|
||||||
|
author = ReferenceField(User)
|
||||||
|
|
||||||
|
User.drop_collection()
|
||||||
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
|
# Make sure ReferenceField only accepts a document class or a string
|
||||||
|
# with a document class name.
|
||||||
|
self.assertRaises(ValidationError, ReferenceField, EmbeddedDocument)
|
||||||
|
|
||||||
|
user = User(name='Test User')
|
||||||
|
|
||||||
|
# Ensure that the referenced object must have been saved
|
||||||
|
post1 = BlogPost(content='Chips and gravy taste good.')
|
||||||
|
post1.author = user
|
||||||
|
self.assertRaises(ValidationError, post1.save)
|
||||||
|
|
||||||
|
# Check that an invalid object type cannot be used
|
||||||
|
post2 = BlogPost(content='Chips and chilli taste good.')
|
||||||
|
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
|
||||||
|
post1.save()
|
||||||
|
|
||||||
|
# Make sure referencing a saved document of the *wrong* type fails
|
||||||
|
post2.save()
|
||||||
|
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):
|
||||||
|
name = StringField()
|
||||||
|
parent = ReferenceField('self', dbref=True)
|
||||||
|
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
p1 = Person(name="John").save()
|
||||||
|
Person(name="Ross", parent=p1).save()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
Person._get_collection().find_one({'name': 'Ross'})['parent'],
|
||||||
|
DBRef('person', p1.pk)
|
||||||
|
)
|
||||||
|
|
||||||
|
p = Person.objects.get(name="Ross")
|
||||||
|
self.assertEqual(p.parent, p1)
|
||||||
|
|
||||||
|
def test_dbref_to_mongo(self):
|
||||||
|
"""Make sure that calling to_mongo on a ReferenceField which
|
||||||
|
has dbref=False, but actually actually contains a DBRef returns
|
||||||
|
an ID of that DBRef.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Person(Document):
|
||||||
|
name = StringField()
|
||||||
|
parent = ReferenceField('self', dbref=False)
|
||||||
|
|
||||||
|
p = Person(
|
||||||
|
name='Steve',
|
||||||
|
parent=DBRef('person', 'abcdefghijklmnop')
|
||||||
|
)
|
||||||
|
self.assertEqual(p.to_mongo(), SON([
|
||||||
|
('name', u'Steve'),
|
||||||
|
('parent', 'abcdefghijklmnop')
|
||||||
|
]))
|
||||||
|
|
||||||
|
def test_objectid_reference_fields(self):
|
||||||
|
class Person(Document):
|
||||||
|
name = StringField()
|
||||||
|
parent = ReferenceField('self', dbref=False)
|
||||||
|
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
p1 = Person(name="John").save()
|
||||||
|
Person(name="Ross", parent=p1).save()
|
||||||
|
|
||||||
|
col = Person._get_collection()
|
||||||
|
data = col.find_one({'name': 'Ross'})
|
||||||
|
self.assertEqual(data['parent'], p1.pk)
|
||||||
|
|
||||||
|
p = Person.objects.get(name="Ross")
|
||||||
|
self.assertEqual(p.parent, p1)
|
||||||
|
|
||||||
|
def test_undefined_reference(self):
|
||||||
|
"""Ensure that ReferenceFields may reference undefined Documents.
|
||||||
|
"""
|
||||||
|
class Product(Document):
|
||||||
|
name = StringField()
|
||||||
|
company = ReferenceField('Company')
|
||||||
|
|
||||||
|
class Company(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
Product.drop_collection()
|
||||||
|
Company.drop_collection()
|
||||||
|
|
||||||
|
ten_gen = Company(name='10gen')
|
||||||
|
ten_gen.save()
|
||||||
|
mongodb = Product(name='MongoDB', company=ten_gen)
|
||||||
|
mongodb.save()
|
||||||
|
|
||||||
|
me = Product(name='MongoEngine')
|
||||||
|
me.save()
|
||||||
|
|
||||||
|
obj = Product.objects(company=ten_gen).first()
|
||||||
|
self.assertEqual(obj, mongodb)
|
||||||
|
self.assertEqual(obj.company, ten_gen)
|
||||||
|
|
||||||
|
obj = Product.objects(company=None).first()
|
||||||
|
self.assertEqual(obj, me)
|
||||||
|
|
||||||
|
obj = Product.objects.get(company=None)
|
||||||
|
self.assertEqual(obj, me)
|
||||||
|
|
||||||
|
def test_reference_query_conversion(self):
|
||||||
|
"""Ensure that ReferenceFields can be queried using objects and values
|
||||||
|
of the type of the primary key of the referenced object.
|
||||||
|
"""
|
||||||
|
class Member(Document):
|
||||||
|
user_num = IntField(primary_key=True)
|
||||||
|
|
||||||
|
class BlogPost(Document):
|
||||||
|
title = StringField()
|
||||||
|
author = ReferenceField(Member, dbref=False)
|
||||||
|
|
||||||
|
Member.drop_collection()
|
||||||
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
|
m1 = Member(user_num=1)
|
||||||
|
m1.save()
|
||||||
|
m2 = Member(user_num=2)
|
||||||
|
m2.save()
|
||||||
|
|
||||||
|
post1 = BlogPost(title='post 1', author=m1)
|
||||||
|
post1.save()
|
||||||
|
|
||||||
|
post2 = BlogPost(title='post 2', author=m2)
|
||||||
|
post2.save()
|
||||||
|
|
||||||
|
post = BlogPost.objects(author=m1).first()
|
||||||
|
self.assertEqual(post.id, post1.id)
|
||||||
|
|
||||||
|
post = BlogPost.objects(author=m2).first()
|
||||||
|
self.assertEqual(post.id, post2.id)
|
||||||
|
|
||||||
|
def test_reference_query_conversion_dbref(self):
|
||||||
|
"""Ensure that ReferenceFields can be queried using objects and values
|
||||||
|
of the type of the primary key of the referenced object.
|
||||||
|
"""
|
||||||
|
class Member(Document):
|
||||||
|
user_num = IntField(primary_key=True)
|
||||||
|
|
||||||
|
class BlogPost(Document):
|
||||||
|
title = StringField()
|
||||||
|
author = ReferenceField(Member, dbref=True)
|
||||||
|
|
||||||
|
Member.drop_collection()
|
||||||
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
|
m1 = Member(user_num=1)
|
||||||
|
m1.save()
|
||||||
|
m2 = Member(user_num=2)
|
||||||
|
m2.save()
|
||||||
|
|
||||||
|
post1 = BlogPost(title='post 1', author=m1)
|
||||||
|
post1.save()
|
||||||
|
|
||||||
|
post2 = BlogPost(title='post 2', author=m2)
|
||||||
|
post2.save()
|
||||||
|
|
||||||
|
post = BlogPost.objects(author=m1).first()
|
||||||
|
self.assertEqual(post.id, post1.id)
|
||||||
|
|
||||||
|
post = BlogPost.objects(author=m2).first()
|
||||||
|
self.assertEqual(post.id, post2.id)
|
271
tests/fields/test_sequence_field.py
Normal file
271
tests/fields/test_sequence_field.py
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from mongoengine import *
|
||||||
|
|
||||||
|
from tests.utils import MongoDBTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestSequenceField(MongoDBTestCase):
|
||||||
|
def test_sequence_field(self):
|
||||||
|
class Person(Document):
|
||||||
|
id = SequenceField(primary_key=True)
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
self.db['mongoengine.counters'].drop()
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
for x in range(10):
|
||||||
|
Person(name="Person %s" % x).save()
|
||||||
|
|
||||||
|
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
|
||||||
|
self.assertEqual(c['next'], 10)
|
||||||
|
|
||||||
|
ids = [i.id for i in Person.objects]
|
||||||
|
self.assertEqual(ids, range(1, 11))
|
||||||
|
|
||||||
|
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
|
||||||
|
self.assertEqual(c['next'], 10)
|
||||||
|
|
||||||
|
Person.id.set_next_value(1000)
|
||||||
|
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
|
||||||
|
self.assertEqual(c['next'], 1000)
|
||||||
|
|
||||||
|
def test_sequence_field_get_next_value(self):
|
||||||
|
class Person(Document):
|
||||||
|
id = SequenceField(primary_key=True)
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
self.db['mongoengine.counters'].drop()
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
for x in range(10):
|
||||||
|
Person(name="Person %s" % x).save()
|
||||||
|
|
||||||
|
self.assertEqual(Person.id.get_next_value(), 11)
|
||||||
|
self.db['mongoengine.counters'].drop()
|
||||||
|
|
||||||
|
self.assertEqual(Person.id.get_next_value(), 1)
|
||||||
|
|
||||||
|
class Person(Document):
|
||||||
|
id = SequenceField(primary_key=True, value_decorator=str)
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
self.db['mongoengine.counters'].drop()
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
for x in range(10):
|
||||||
|
Person(name="Person %s" % x).save()
|
||||||
|
|
||||||
|
self.assertEqual(Person.id.get_next_value(), '11')
|
||||||
|
self.db['mongoengine.counters'].drop()
|
||||||
|
|
||||||
|
self.assertEqual(Person.id.get_next_value(), '1')
|
||||||
|
|
||||||
|
def test_sequence_field_sequence_name(self):
|
||||||
|
class Person(Document):
|
||||||
|
id = SequenceField(primary_key=True, sequence_name='jelly')
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
self.db['mongoengine.counters'].drop()
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
for x in range(10):
|
||||||
|
Person(name="Person %s" % x).save()
|
||||||
|
|
||||||
|
c = self.db['mongoengine.counters'].find_one({'_id': 'jelly.id'})
|
||||||
|
self.assertEqual(c['next'], 10)
|
||||||
|
|
||||||
|
ids = [i.id for i in Person.objects]
|
||||||
|
self.assertEqual(ids, range(1, 11))
|
||||||
|
|
||||||
|
c = self.db['mongoengine.counters'].find_one({'_id': 'jelly.id'})
|
||||||
|
self.assertEqual(c['next'], 10)
|
||||||
|
|
||||||
|
Person.id.set_next_value(1000)
|
||||||
|
c = self.db['mongoengine.counters'].find_one({'_id': 'jelly.id'})
|
||||||
|
self.assertEqual(c['next'], 1000)
|
||||||
|
|
||||||
|
def test_multiple_sequence_fields(self):
|
||||||
|
class Person(Document):
|
||||||
|
id = SequenceField(primary_key=True)
|
||||||
|
counter = SequenceField()
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
self.db['mongoengine.counters'].drop()
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
for x in range(10):
|
||||||
|
Person(name="Person %s" % x).save()
|
||||||
|
|
||||||
|
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
|
||||||
|
self.assertEqual(c['next'], 10)
|
||||||
|
|
||||||
|
ids = [i.id for i in Person.objects]
|
||||||
|
self.assertEqual(ids, range(1, 11))
|
||||||
|
|
||||||
|
counters = [i.counter for i in Person.objects]
|
||||||
|
self.assertEqual(counters, range(1, 11))
|
||||||
|
|
||||||
|
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
|
||||||
|
self.assertEqual(c['next'], 10)
|
||||||
|
|
||||||
|
Person.id.set_next_value(1000)
|
||||||
|
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
|
||||||
|
self.assertEqual(c['next'], 1000)
|
||||||
|
|
||||||
|
Person.counter.set_next_value(999)
|
||||||
|
c = self.db['mongoengine.counters'].find_one({'_id': 'person.counter'})
|
||||||
|
self.assertEqual(c['next'], 999)
|
||||||
|
|
||||||
|
def test_sequence_fields_reload(self):
|
||||||
|
class Animal(Document):
|
||||||
|
counter = SequenceField()
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
self.db['mongoengine.counters'].drop()
|
||||||
|
Animal.drop_collection()
|
||||||
|
|
||||||
|
a = Animal(name="Boi").save()
|
||||||
|
|
||||||
|
self.assertEqual(a.counter, 1)
|
||||||
|
a.reload()
|
||||||
|
self.assertEqual(a.counter, 1)
|
||||||
|
|
||||||
|
a.counter = None
|
||||||
|
self.assertEqual(a.counter, 2)
|
||||||
|
a.save()
|
||||||
|
|
||||||
|
self.assertEqual(a.counter, 2)
|
||||||
|
|
||||||
|
a = Animal.objects.first()
|
||||||
|
self.assertEqual(a.counter, 2)
|
||||||
|
a.reload()
|
||||||
|
self.assertEqual(a.counter, 2)
|
||||||
|
|
||||||
|
def test_multiple_sequence_fields_on_docs(self):
|
||||||
|
class Animal(Document):
|
||||||
|
id = SequenceField(primary_key=True)
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
class Person(Document):
|
||||||
|
id = SequenceField(primary_key=True)
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
self.db['mongoengine.counters'].drop()
|
||||||
|
Animal.drop_collection()
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
for x in range(10):
|
||||||
|
Animal(name="Animal %s" % x).save()
|
||||||
|
Person(name="Person %s" % x).save()
|
||||||
|
|
||||||
|
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
|
||||||
|
self.assertEqual(c['next'], 10)
|
||||||
|
|
||||||
|
c = self.db['mongoengine.counters'].find_one({'_id': 'animal.id'})
|
||||||
|
self.assertEqual(c['next'], 10)
|
||||||
|
|
||||||
|
ids = [i.id for i in Person.objects]
|
||||||
|
self.assertEqual(ids, range(1, 11))
|
||||||
|
|
||||||
|
id = [i.id for i in Animal.objects]
|
||||||
|
self.assertEqual(id, range(1, 11))
|
||||||
|
|
||||||
|
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
|
||||||
|
self.assertEqual(c['next'], 10)
|
||||||
|
|
||||||
|
c = self.db['mongoengine.counters'].find_one({'_id': 'animal.id'})
|
||||||
|
self.assertEqual(c['next'], 10)
|
||||||
|
|
||||||
|
def test_sequence_field_value_decorator(self):
|
||||||
|
class Person(Document):
|
||||||
|
id = SequenceField(primary_key=True, value_decorator=str)
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
self.db['mongoengine.counters'].drop()
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
for x in range(10):
|
||||||
|
p = Person(name="Person %s" % x)
|
||||||
|
p.save()
|
||||||
|
|
||||||
|
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
|
||||||
|
self.assertEqual(c['next'], 10)
|
||||||
|
|
||||||
|
ids = [i.id for i in Person.objects]
|
||||||
|
self.assertEqual(ids, map(str, range(1, 11)))
|
||||||
|
|
||||||
|
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
|
||||||
|
self.assertEqual(c['next'], 10)
|
||||||
|
|
||||||
|
def test_embedded_sequence_field(self):
|
||||||
|
class Comment(EmbeddedDocument):
|
||||||
|
id = SequenceField()
|
||||||
|
content = StringField(required=True)
|
||||||
|
|
||||||
|
class Post(Document):
|
||||||
|
title = StringField(required=True)
|
||||||
|
comments = ListField(EmbeddedDocumentField(Comment))
|
||||||
|
|
||||||
|
self.db['mongoengine.counters'].drop()
|
||||||
|
Post.drop_collection()
|
||||||
|
|
||||||
|
Post(title="MongoEngine",
|
||||||
|
comments=[Comment(content="NoSQL Rocks"),
|
||||||
|
Comment(content="MongoEngine Rocks")]).save()
|
||||||
|
c = self.db['mongoengine.counters'].find_one({'_id': 'comment.id'})
|
||||||
|
self.assertEqual(c['next'], 2)
|
||||||
|
post = Post.objects.first()
|
||||||
|
self.assertEqual(1, post.comments[0].id)
|
||||||
|
self.assertEqual(2, post.comments[1].id)
|
||||||
|
|
||||||
|
def test_inherited_sequencefield(self):
|
||||||
|
class Base(Document):
|
||||||
|
name = StringField()
|
||||||
|
counter = SequenceField()
|
||||||
|
meta = {'abstract': True}
|
||||||
|
|
||||||
|
class Foo(Base):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Bar(Base):
|
||||||
|
pass
|
||||||
|
|
||||||
|
bar = Bar(name='Bar')
|
||||||
|
bar.save()
|
||||||
|
|
||||||
|
foo = Foo(name='Foo')
|
||||||
|
foo.save()
|
||||||
|
|
||||||
|
self.assertTrue('base.counter' in
|
||||||
|
self.db['mongoengine.counters'].find().distinct('_id'))
|
||||||
|
self.assertFalse(('foo.counter' or 'bar.counter') in
|
||||||
|
self.db['mongoengine.counters'].find().distinct('_id'))
|
||||||
|
self.assertNotEqual(foo.counter, bar.counter)
|
||||||
|
self.assertEqual(foo._fields['counter'].owner_document, Base)
|
||||||
|
self.assertEqual(bar._fields['counter'].owner_document, Base)
|
||||||
|
|
||||||
|
def test_no_inherited_sequencefield(self):
|
||||||
|
class Base(Document):
|
||||||
|
name = StringField()
|
||||||
|
meta = {'abstract': True}
|
||||||
|
|
||||||
|
class Foo(Base):
|
||||||
|
counter = SequenceField()
|
||||||
|
|
||||||
|
class Bar(Base):
|
||||||
|
counter = SequenceField()
|
||||||
|
|
||||||
|
bar = Bar(name='Bar')
|
||||||
|
bar.save()
|
||||||
|
|
||||||
|
foo = Foo(name='Foo')
|
||||||
|
foo.save()
|
||||||
|
|
||||||
|
self.assertFalse('base.counter' in
|
||||||
|
self.db['mongoengine.counters'].find().distinct('_id'))
|
||||||
|
self.assertTrue(('foo.counter' and 'bar.counter') in
|
||||||
|
self.db['mongoengine.counters'].find().distinct('_id'))
|
||||||
|
self.assertEqual(foo.counter, bar.counter)
|
||||||
|
self.assertEqual(foo._fields['counter'].owner_document, Foo)
|
||||||
|
self.assertEqual(bar._fields['counter'].owner_document, Bar)
|
59
tests/fields/test_url_field.py
Normal file
59
tests/fields/test_url_field.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from mongoengine import *
|
||||||
|
|
||||||
|
from tests.utils import MongoDBTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestURLField(MongoDBTestCase):
|
||||||
|
|
||||||
|
def test_validation(self):
|
||||||
|
"""Ensure that URLFields validate urls properly."""
|
||||||
|
class Link(Document):
|
||||||
|
url = URLField()
|
||||||
|
|
||||||
|
link = Link()
|
||||||
|
link.url = 'google'
|
||||||
|
self.assertRaises(ValidationError, link.validate)
|
||||||
|
|
||||||
|
link.url = 'http://www.google.com:8080'
|
||||||
|
link.validate()
|
||||||
|
|
||||||
|
def test_unicode_url_validation(self):
|
||||||
|
"""Ensure unicode URLs are validated properly."""
|
||||||
|
class Link(Document):
|
||||||
|
url = URLField()
|
||||||
|
|
||||||
|
link = Link()
|
||||||
|
link.url = u'http://привет.com'
|
||||||
|
|
||||||
|
# TODO fix URL validation - this *IS* a valid URL
|
||||||
|
# For now we just want to make sure that the error message is correct
|
||||||
|
with self.assertRaises(ValidationError) as ctx_err:
|
||||||
|
link.validate()
|
||||||
|
self.assertEqual(unicode(ctx_err.exception),
|
||||||
|
u"ValidationError (Link:None) (Invalid URL: http://\u043f\u0440\u0438\u0432\u0435\u0442.com: ['url'])")
|
||||||
|
|
||||||
|
def test_url_scheme_validation(self):
|
||||||
|
"""Ensure that URLFields validate urls with specific schemes properly.
|
||||||
|
"""
|
||||||
|
class Link(Document):
|
||||||
|
url = URLField()
|
||||||
|
|
||||||
|
class SchemeLink(Document):
|
||||||
|
url = URLField(schemes=['ws', 'irc'])
|
||||||
|
|
||||||
|
link = Link()
|
||||||
|
link.url = 'ws://google.com'
|
||||||
|
self.assertRaises(ValidationError, link.validate)
|
||||||
|
|
||||||
|
scheme_link = SchemeLink()
|
||||||
|
scheme_link.url = 'ws://google.com'
|
||||||
|
scheme_link.validate()
|
||||||
|
|
||||||
|
def test_underscore_allowed_in_domains_names(self):
|
||||||
|
class Link(Document):
|
||||||
|
url = URLField()
|
||||||
|
|
||||||
|
link = Link()
|
||||||
|
link.url = 'https://san_leandro-ca.geebo.com'
|
||||||
|
link.validate()
|
65
tests/fields/test_uuid_field.py
Normal file
65
tests/fields/test_uuid_field.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from mongoengine import *
|
||||||
|
|
||||||
|
from tests.utils import MongoDBTestCase, get_as_pymongo
|
||||||
|
|
||||||
|
|
||||||
|
class Person(Document):
|
||||||
|
api_key = UUIDField(binary=False)
|
||||||
|
|
||||||
|
|
||||||
|
class TestUUIDField(MongoDBTestCase):
|
||||||
|
def test_storage(self):
|
||||||
|
uid = uuid.uuid4()
|
||||||
|
person = Person(api_key=uid).save()
|
||||||
|
self.assertEqual(
|
||||||
|
get_as_pymongo(person),
|
||||||
|
{'_id': person.id,
|
||||||
|
'api_key': str(uid)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_field_string(self):
|
||||||
|
"""Test UUID fields storing as String
|
||||||
|
"""
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
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())
|
||||||
|
for api_key in valid:
|
||||||
|
person.api_key = api_key
|
||||||
|
person.validate()
|
||||||
|
|
||||||
|
invalid = ('9d159858-549b-4975-9f98-dd2f987c113g',
|
||||||
|
'9d159858-549b-4975-9f98-dd2f987c113')
|
||||||
|
for api_key in invalid:
|
||||||
|
person.api_key = api_key
|
||||||
|
self.assertRaises(ValidationError, person.validate)
|
||||||
|
|
||||||
|
def test_field_binary(self):
|
||||||
|
"""Test UUID fields storing as Binary object."""
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
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())
|
||||||
|
for api_key in valid:
|
||||||
|
person.api_key = api_key
|
||||||
|
person.validate()
|
||||||
|
|
||||||
|
invalid = ('9d159858-549b-4975-9f98-dd2f987c113g',
|
||||||
|
'9d159858-549b-4975-9f98-dd2f987c113')
|
||||||
|
for api_key in invalid:
|
||||||
|
person.api_key = api_key
|
||||||
|
self.assertRaises(ValidationError, person.validate)
|
@@ -48,6 +48,7 @@ class PickleSignalsTest(Document):
|
|||||||
def post_delete(self, sender, document, **kwargs):
|
def post_delete(self, sender, document, **kwargs):
|
||||||
pickled = pickle.dumps(document)
|
pickled = pickle.dumps(document)
|
||||||
|
|
||||||
|
|
||||||
signals.post_save.connect(PickleSignalsTest.post_save, sender=PickleSignalsTest)
|
signals.post_save.connect(PickleSignalsTest.post_save, sender=PickleSignalsTest)
|
||||||
signals.post_delete.connect(PickleSignalsTest.post_delete, sender=PickleSignalsTest)
|
signals.post_delete.connect(PickleSignalsTest.post_delete, sender=PickleSignalsTest)
|
||||||
|
|
||||||
|
@@ -208,7 +208,7 @@ class OnlyExcludeAllTest(unittest.TestCase):
|
|||||||
|
|
||||||
BlogPost.drop_collection()
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
post = BlogPost(content='Had a good coffee today...', various={'test_dynamic':{'some': True}})
|
post = BlogPost(content='Had a good coffee today...', various={'test_dynamic': {'some': True}})
|
||||||
post.author = User(name='Test User')
|
post.author = User(name='Test User')
|
||||||
post.comments = [Comment(title='I aggree', text='Great post!'), Comment(title='Coffee', text='I hate coffee')]
|
post.comments = [Comment(title='I aggree', text='Great post!'), Comment(title='Coffee', text='I hate coffee')]
|
||||||
post.save()
|
post.save()
|
||||||
@@ -413,7 +413,6 @@ class OnlyExcludeAllTest(unittest.TestCase):
|
|||||||
numbers = Numbers.objects.fields(embedded__n={"$slice": [-5, 10]}).get()
|
numbers = Numbers.objects.fields(embedded__n={"$slice": [-5, 10]}).get()
|
||||||
self.assertEqual(numbers.embedded.n, [-5, -4, -3, -2, -1])
|
self.assertEqual(numbers.embedded.n, [-5, -4, -3, -2, -1])
|
||||||
|
|
||||||
|
|
||||||
def test_exclude_from_subclasses_docs(self):
|
def test_exclude_from_subclasses_docs(self):
|
||||||
|
|
||||||
class Base(Document):
|
class Base(Document):
|
||||||
@@ -436,5 +435,6 @@ class OnlyExcludeAllTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertRaises(LookUpError, Base.objects.exclude, "made_up")
|
self.assertRaises(LookUpError, Base.objects.exclude, "made_up")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -534,11 +534,11 @@ class GeoQueriesTest(MongoDBTestCase):
|
|||||||
|
|
||||||
Location.drop_collection()
|
Location.drop_collection()
|
||||||
|
|
||||||
Location(loc=[1,2]).save()
|
Location(loc=[1, 2]).save()
|
||||||
loc = Location.objects.as_pymongo()[0]
|
loc = Location.objects.as_pymongo()[0]
|
||||||
self.assertEqual(loc["loc"], {"type": "Point", "coordinates": [1, 2]})
|
self.assertEqual(loc["loc"], {"type": "Point", "coordinates": [1, 2]})
|
||||||
|
|
||||||
Location.objects.update(set__loc=[2,1])
|
Location.objects.update(set__loc=[2, 1])
|
||||||
loc = Location.objects.as_pymongo()[0]
|
loc = Location.objects.as_pymongo()[0]
|
||||||
self.assertEqual(loc["loc"], {"type": "Point", "coordinates": [2, 1]})
|
self.assertEqual(loc["loc"], {"type": "Point", "coordinates": [2, 1]})
|
||||||
|
|
||||||
|
@@ -6,10 +6,12 @@ from mongoengine.connection import connect
|
|||||||
|
|
||||||
__author__ = 'stas'
|
__author__ = 'stas'
|
||||||
|
|
||||||
|
|
||||||
class Person(Document):
|
class Person(Document):
|
||||||
name = StringField()
|
name = StringField()
|
||||||
age = IntField()
|
age = IntField()
|
||||||
|
|
||||||
|
|
||||||
class TestQuerysetPickable(unittest.TestCase):
|
class TestQuerysetPickable(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
Test for adding pickling support for QuerySet instances
|
Test for adding pickling support for QuerySet instances
|
||||||
@@ -18,7 +20,7 @@ class TestQuerysetPickable(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestQuerysetPickable, self).setUp()
|
super(TestQuerysetPickable, self).setUp()
|
||||||
|
|
||||||
connection = connect(db="test") #type: pymongo.mongo_client.MongoClient
|
connection = connect(db="test") # type: pymongo.mongo_client.MongoClient
|
||||||
|
|
||||||
connection.drop_database("test")
|
connection.drop_database("test")
|
||||||
|
|
||||||
@@ -27,7 +29,6 @@ class TestQuerysetPickable(unittest.TestCase):
|
|||||||
age=21
|
age=21
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_picke_simple_qs(self):
|
def test_picke_simple_qs(self):
|
||||||
|
|
||||||
qs = Person.objects.all()
|
qs = Person.objects.all()
|
||||||
@@ -46,10 +47,10 @@ class TestQuerysetPickable(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(qs.count(), loadedQs.count())
|
self.assertEqual(qs.count(), loadedQs.count())
|
||||||
|
|
||||||
#can update loadedQs
|
# can update loadedQs
|
||||||
loadedQs.update(age=23)
|
loadedQs.update(age=23)
|
||||||
|
|
||||||
#check
|
# check
|
||||||
self.assertEqual(Person.objects.first().age, 23)
|
self.assertEqual(Person.objects.first().age, 23)
|
||||||
|
|
||||||
def test_pickle_support_filtration(self):
|
def test_pickle_support_filtration(self):
|
||||||
@@ -70,7 +71,7 @@ class TestQuerysetPickable(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(loaded.count(), 2)
|
self.assertEqual(loaded.count(), 2)
|
||||||
self.assertEqual(loaded.filter(name="Bob").first().age, 23)
|
self.assertEqual(loaded.filter(name="Bob").first().age, 23)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@@ -12,18 +12,17 @@ from pymongo.errors import ConfigurationError
|
|||||||
from pymongo.read_preferences import ReadPreference
|
from pymongo.read_preferences import ReadPreference
|
||||||
from pymongo.results import UpdateResult
|
from pymongo.results import UpdateResult
|
||||||
import six
|
import six
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
from mongoengine.connection import get_connection, get_db
|
from mongoengine.connection import get_connection, get_db
|
||||||
from mongoengine.context_managers import query_counter, switch_db
|
from mongoengine.context_managers import query_counter, switch_db
|
||||||
from mongoengine.errors import InvalidQueryError
|
from mongoengine.errors import InvalidQueryError
|
||||||
from mongoengine.python_support import IS_PYMONGO_3
|
from mongoengine.mongodb_support import get_mongodb_version, MONGODB_32
|
||||||
|
from mongoengine.pymongo_support import IS_PYMONGO_3
|
||||||
from mongoengine.queryset import (DoesNotExist, MultipleObjectsReturned,
|
from mongoengine.queryset import (DoesNotExist, MultipleObjectsReturned,
|
||||||
QuerySet, QuerySetManager, queryset_manager)
|
QuerySet, QuerySetManager, queryset_manager)
|
||||||
|
from tests.utils import requires_mongodb_gte_26, skip_pymongo3
|
||||||
from tests.utils import requires_mongodb_gte_26, skip_pymongo3, get_mongodb_version, MONGODB_32
|
|
||||||
|
|
||||||
__all__ = ("QuerySetTest",)
|
|
||||||
|
|
||||||
|
|
||||||
class db_ops_tracker(query_counter):
|
class db_ops_tracker(query_counter):
|
||||||
@@ -395,6 +394,16 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
list(qs)
|
list(qs)
|
||||||
|
|
||||||
|
def test_batch_size_cloned(self):
|
||||||
|
class A(Document):
|
||||||
|
s = StringField()
|
||||||
|
|
||||||
|
# test that batch size gets cloned
|
||||||
|
qs = A.objects.batch_size(5)
|
||||||
|
self.assertEqual(qs._batch_size, 5)
|
||||||
|
qs_clone = qs.clone()
|
||||||
|
self.assertEqual(qs_clone._batch_size, 5)
|
||||||
|
|
||||||
def test_update_write_concern(self):
|
def test_update_write_concern(self):
|
||||||
"""Test that passing write_concern works"""
|
"""Test that passing write_concern works"""
|
||||||
self.Person.drop_collection()
|
self.Person.drop_collection()
|
||||||
@@ -851,8 +860,8 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 0)
|
self.assertEqual(q, 0)
|
||||||
Blog.objects.insert(blogs, load_bulk=False)
|
Blog.objects.insert(blogs, load_bulk=False)
|
||||||
|
|
||||||
if MONGO_VER == MONGODB_32:
|
if MONGO_VER >= MONGODB_32:
|
||||||
self.assertEqual(q, 1) # 1 entry containing the list of inserts
|
self.assertEqual(q, 1) # 1 entry containing the list of inserts
|
||||||
else:
|
else:
|
||||||
self.assertEqual(q, len(blogs)) # 1 entry per doc inserted
|
self.assertEqual(q, len(blogs)) # 1 entry per doc inserted
|
||||||
|
|
||||||
@@ -868,8 +877,8 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 0)
|
self.assertEqual(q, 0)
|
||||||
Blog.objects.insert(blogs)
|
Blog.objects.insert(blogs)
|
||||||
|
|
||||||
if MONGO_VER == MONGODB_32:
|
if MONGO_VER >= MONGODB_32:
|
||||||
self.assertEqual(q, 2) # 1 for insert 1 for fetch
|
self.assertEqual(q, 2) # 1 for insert 1 for fetch
|
||||||
else:
|
else:
|
||||||
self.assertEqual(q, len(blogs)+1) # + 1 to fetch all docs
|
self.assertEqual(q, len(blogs)+1) # + 1 to fetch all docs
|
||||||
|
|
||||||
@@ -1203,7 +1212,7 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
"""Ensure filters can be chained together.
|
"""Ensure filters can be chained together.
|
||||||
"""
|
"""
|
||||||
class Blog(Document):
|
class Blog(Document):
|
||||||
id = StringField(unique=True, primary_key=True)
|
id = StringField(primary_key=True)
|
||||||
|
|
||||||
class BlogPost(Document):
|
class BlogPost(Document):
|
||||||
blog = ReferenceField(Blog)
|
blog = ReferenceField(Blog)
|
||||||
@@ -1315,7 +1324,7 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
order_by() w/o any arguments.
|
order_by() w/o any arguments.
|
||||||
"""
|
"""
|
||||||
MONGO_VER = self.mongodb_version
|
MONGO_VER = self.mongodb_version
|
||||||
ORDER_BY_KEY = 'sort' if MONGO_VER == MONGODB_32 else '$orderby'
|
ORDER_BY_KEY = 'sort' if MONGO_VER >= MONGODB_32 else '$orderby'
|
||||||
|
|
||||||
class BlogPost(Document):
|
class BlogPost(Document):
|
||||||
title = StringField()
|
title = StringField()
|
||||||
@@ -2233,6 +2242,19 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
bar.reload()
|
bar.reload()
|
||||||
self.assertEqual(len(bar.foos), 0)
|
self.assertEqual(len(bar.foos), 0)
|
||||||
|
|
||||||
|
def test_update_one_check_return_with_full_result(self):
|
||||||
|
class BlogTag(Document):
|
||||||
|
name = StringField(required=True)
|
||||||
|
|
||||||
|
BlogTag.drop_collection()
|
||||||
|
|
||||||
|
BlogTag(name='garbage').save()
|
||||||
|
default_update = BlogTag.objects.update_one(name='new')
|
||||||
|
self.assertEqual(default_update, 1)
|
||||||
|
|
||||||
|
full_result_update = BlogTag.objects.update_one(name='new', full_result=True)
|
||||||
|
self.assertIsInstance(full_result_update, UpdateResult)
|
||||||
|
|
||||||
def test_update_one_pop_generic_reference(self):
|
def test_update_one_pop_generic_reference(self):
|
||||||
|
|
||||||
class BlogTag(Document):
|
class BlogTag(Document):
|
||||||
@@ -2510,8 +2532,8 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
def test_comment(self):
|
def test_comment(self):
|
||||||
"""Make sure adding a comment to the query gets added to the query"""
|
"""Make sure adding a comment to the query gets added to the query"""
|
||||||
MONGO_VER = self.mongodb_version
|
MONGO_VER = self.mongodb_version
|
||||||
QUERY_KEY = 'filter' if MONGO_VER == MONGODB_32 else '$query'
|
QUERY_KEY = 'filter' if MONGO_VER >= MONGODB_32 else '$query'
|
||||||
COMMENT_KEY = 'comment' if MONGO_VER == MONGODB_32 else '$comment'
|
COMMENT_KEY = 'comment' if MONGO_VER >= MONGODB_32 else '$comment'
|
||||||
|
|
||||||
class User(Document):
|
class User(Document):
|
||||||
age = IntField()
|
age = IntField()
|
||||||
@@ -3335,7 +3357,7 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
meta = {'indexes': [
|
meta = {'indexes': [
|
||||||
{'fields': ['$title', "$content"],
|
{'fields': ['$title', "$content"],
|
||||||
'default_language': 'portuguese',
|
'default_language': 'portuguese',
|
||||||
'weight': {'title': 10, 'content': 2}
|
'weights': {'title': 10, 'content': 2}
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
|
|
||||||
@@ -4026,7 +4048,7 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
info = [(value['key'],
|
info = [(value['key'],
|
||||||
value.get('unique', False),
|
value.get('unique', False),
|
||||||
value.get('sparse', False))
|
value.get('sparse', False))
|
||||||
for key, value in info.iteritems()]
|
for key, value in iteritems(info)]
|
||||||
self.assertIn(([('_cls', 1), ('message', 1)], False, False), info)
|
self.assertIn(([('_cls', 1), ('message', 1)], False, False), info)
|
||||||
|
|
||||||
def test_where(self):
|
def test_where(self):
|
||||||
@@ -4037,7 +4059,7 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
fielda = IntField()
|
fielda = IntField()
|
||||||
fieldb = IntField()
|
fieldb = IntField()
|
||||||
|
|
||||||
IntPair.objects._collection.remove()
|
IntPair.drop_collection()
|
||||||
|
|
||||||
a = IntPair(fielda=1, fieldb=1)
|
a = IntPair(fielda=1, fieldb=1)
|
||||||
b = IntPair(fielda=1, fieldb=2)
|
b = IntPair(fielda=1, fieldb=2)
|
||||||
@@ -4628,14 +4650,12 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
self.assertEqual(doc_objects, Doc.objects.from_json(json_data))
|
self.assertEqual(doc_objects, Doc.objects.from_json(json_data))
|
||||||
|
|
||||||
def test_as_pymongo(self):
|
def test_as_pymongo(self):
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
class LastLogin(EmbeddedDocument):
|
class LastLogin(EmbeddedDocument):
|
||||||
location = StringField()
|
location = StringField()
|
||||||
ip = StringField()
|
ip = StringField()
|
||||||
|
|
||||||
class User(Document):
|
class User(Document):
|
||||||
id = ObjectIdField('_id')
|
id = StringField(primary_key=True)
|
||||||
name = StringField()
|
name = StringField()
|
||||||
age = IntField()
|
age = IntField()
|
||||||
price = DecimalField()
|
price = DecimalField()
|
||||||
@@ -4643,9 +4663,10 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
|
|
||||||
User.drop_collection()
|
User.drop_collection()
|
||||||
|
|
||||||
User.objects.create(name="Bob Dole", age=89, price=Decimal('1.11'))
|
User.objects.create(id='Bob', name="Bob Dole", age=89, price=Decimal('1.11'))
|
||||||
User.objects.create(
|
User.objects.create(
|
||||||
name="Barack Obama",
|
id='Barak',
|
||||||
|
name="Barak Obama",
|
||||||
age=51,
|
age=51,
|
||||||
price=Decimal('2.22'),
|
price=Decimal('2.22'),
|
||||||
last_login=LastLogin(
|
last_login=LastLogin(
|
||||||
@@ -4673,7 +4694,7 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
self.assertIsInstance(results[1], dict)
|
self.assertIsInstance(results[1], dict)
|
||||||
self.assertEqual(results[0]['name'], 'Bob Dole')
|
self.assertEqual(results[0]['name'], 'Bob Dole')
|
||||||
self.assertEqual(results[0]['price'], 1.11)
|
self.assertEqual(results[0]['price'], 1.11)
|
||||||
self.assertEqual(results[1]['name'], 'Barack Obama')
|
self.assertEqual(results[1]['name'], 'Barak Obama')
|
||||||
self.assertEqual(results[1]['price'], 2.22)
|
self.assertEqual(results[1]['price'], 2.22)
|
||||||
|
|
||||||
users = User.objects.only('name', 'last_login').as_pymongo()
|
users = User.objects.only('name', 'last_login').as_pymongo()
|
||||||
@@ -4681,16 +4702,36 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
self.assertIsInstance(results[0], dict)
|
self.assertIsInstance(results[0], dict)
|
||||||
self.assertIsInstance(results[1], dict)
|
self.assertIsInstance(results[1], dict)
|
||||||
self.assertEqual(results[0], {
|
self.assertEqual(results[0], {
|
||||||
|
'_id': 'Bob',
|
||||||
'name': 'Bob Dole'
|
'name': 'Bob Dole'
|
||||||
})
|
})
|
||||||
self.assertEqual(results[1], {
|
self.assertEqual(results[1], {
|
||||||
'name': 'Barack Obama',
|
'_id': 'Barak',
|
||||||
|
'name': 'Barak Obama',
|
||||||
'last_login': {
|
'last_login': {
|
||||||
'location': 'White House',
|
'location': 'White House',
|
||||||
'ip': '104.107.108.116'
|
'ip': '104.107.108.116'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def test_as_pymongo_returns_cls_attribute_when_using_inheritance(self):
|
||||||
|
class User(Document):
|
||||||
|
name = StringField()
|
||||||
|
meta = {'allow_inheritance': True}
|
||||||
|
|
||||||
|
User.drop_collection()
|
||||||
|
|
||||||
|
user = User(name="Bob Dole").save()
|
||||||
|
result = User.objects.as_pymongo().first()
|
||||||
|
self.assertEqual(
|
||||||
|
result,
|
||||||
|
{
|
||||||
|
'_cls': 'User',
|
||||||
|
'_id': user.id,
|
||||||
|
'name': 'Bob Dole'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def test_as_pymongo_json_limit_fields(self):
|
def test_as_pymongo_json_limit_fields(self):
|
||||||
|
|
||||||
class User(Document):
|
class User(Document):
|
||||||
@@ -4706,19 +4747,49 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
|
|
||||||
serialized_user = User.objects.exclude(
|
serialized_user = User.objects.exclude(
|
||||||
'password_salt', 'password_hash').as_pymongo()[0]
|
'password_salt', 'password_hash').as_pymongo()[0]
|
||||||
self.assertEqual(set(['_id', 'email']), set(serialized_user.keys()))
|
self.assertEqual({'_id', 'email'}, set(serialized_user.keys()))
|
||||||
|
|
||||||
serialized_user = User.objects.exclude(
|
serialized_user = User.objects.exclude(
|
||||||
'id', 'password_salt', 'password_hash').to_json()
|
'id', 'password_salt', 'password_hash').to_json()
|
||||||
self.assertEqual('[{"email": "ross@example.com"}]', serialized_user)
|
self.assertEqual('[{"email": "ross@example.com"}]', serialized_user)
|
||||||
|
|
||||||
serialized_user = User.objects.exclude(
|
serialized_user = User.objects.only('email').as_pymongo()[0]
|
||||||
'password_salt').only('email').as_pymongo()[0]
|
self.assertEqual({'_id', 'email'}, set(serialized_user.keys()))
|
||||||
self.assertEqual(set(['email']), set(serialized_user.keys()))
|
|
||||||
|
|
||||||
serialized_user = User.objects.exclude(
|
serialized_user = User.objects.exclude(
|
||||||
'password_salt').only('email').to_json()
|
'password_salt').only('email').as_pymongo()[0]
|
||||||
self.assertEqual('[{"email": "ross@example.com"}]', serialized_user)
|
self.assertEqual({'_id', 'email'}, set(serialized_user.keys()))
|
||||||
|
|
||||||
|
serialized_user = User.objects.exclude(
|
||||||
|
'password_salt', 'id').only('email').as_pymongo()[0]
|
||||||
|
self.assertEqual({'email'}, set(serialized_user.keys()))
|
||||||
|
|
||||||
|
serialized_user = User.objects.exclude(
|
||||||
|
'password_salt', 'id').only('email').to_json()
|
||||||
|
self.assertEqual('[{"email": "ross@example.com"}]',
|
||||||
|
serialized_user)
|
||||||
|
|
||||||
|
def test_only_after_count(self):
|
||||||
|
"""Test that only() works after count()"""
|
||||||
|
|
||||||
|
class User(Document):
|
||||||
|
name = StringField()
|
||||||
|
age = IntField()
|
||||||
|
address = StringField()
|
||||||
|
User.drop_collection()
|
||||||
|
user = User(name="User", age=50,
|
||||||
|
address="Moscow, Russia").save()
|
||||||
|
|
||||||
|
user_queryset = User.objects(age=50)
|
||||||
|
|
||||||
|
result = user_queryset.only("name", "age").as_pymongo().first()
|
||||||
|
self.assertEqual(result, {"_id": user.id, "name": "User", "age": 50})
|
||||||
|
|
||||||
|
result = user_queryset.count()
|
||||||
|
self.assertEqual(result, 1)
|
||||||
|
|
||||||
|
result = user_queryset.only("name", "age").as_pymongo().first()
|
||||||
|
self.assertEqual(result, {"_id": user.id, "name": "User", "age": 50})
|
||||||
|
|
||||||
def test_no_dereference(self):
|
def test_no_dereference(self):
|
||||||
|
|
||||||
@@ -5068,7 +5139,7 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
def test_query_reference_to_custom_pk_doc(self):
|
def test_query_reference_to_custom_pk_doc(self):
|
||||||
|
|
||||||
class A(Document):
|
class A(Document):
|
||||||
id = StringField(unique=True, primary_key=True)
|
id = StringField(primary_key=True)
|
||||||
|
|
||||||
class B(Document):
|
class B(Document):
|
||||||
a = ReferenceField(A)
|
a = ReferenceField(A)
|
||||||
@@ -5173,7 +5244,7 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_bool_with_ordering(self):
|
def test_bool_with_ordering(self):
|
||||||
MONGO_VER = self.mongodb_version
|
MONGO_VER = self.mongodb_version
|
||||||
ORDER_BY_KEY = 'sort' if MONGO_VER == MONGODB_32 else '$orderby'
|
ORDER_BY_KEY = 'sort' if MONGO_VER >= MONGODB_32 else '$orderby'
|
||||||
|
|
||||||
class Person(Document):
|
class Person(Document):
|
||||||
name = StringField()
|
name = StringField()
|
||||||
@@ -5323,7 +5394,7 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
|
|
||||||
Person.drop_collection()
|
Person.drop_collection()
|
||||||
|
|
||||||
Person._get_collection().insert({'name': 'a', 'id': ''})
|
Person._get_collection().insert_one({'name': 'a', 'id': ''})
|
||||||
for p in Person.objects():
|
for p in Person.objects():
|
||||||
self.assertEqual(p.name, 'a')
|
self.assertEqual(p.name, 'a')
|
||||||
|
|
||||||
|
@@ -275,7 +275,6 @@ class QTest(unittest.TestCase):
|
|||||||
with self.assertRaises(InvalidQueryError):
|
with self.assertRaises(InvalidQueryError):
|
||||||
self.Person.objects.filter('user1')
|
self.Person.objects.filter('user1')
|
||||||
|
|
||||||
|
|
||||||
def test_q_regex(self):
|
def test_q_regex(self):
|
||||||
"""Ensure that Q objects can be queried using regexes.
|
"""Ensure that Q objects can be queried using regexes.
|
||||||
"""
|
"""
|
||||||
|
@@ -14,7 +14,7 @@ from mongoengine import (
|
|||||||
connect, register_connection,
|
connect, register_connection,
|
||||||
Document, DateTimeField
|
Document, DateTimeField
|
||||||
)
|
)
|
||||||
from mongoengine.python_support import IS_PYMONGO_3
|
from mongoengine.pymongo_support import IS_PYMONGO_3
|
||||||
import mongoengine.connection
|
import mongoengine.connection
|
||||||
from mongoengine.connection import (MongoEngineConnectionError, get_db,
|
from mongoengine.connection import (MongoEngineConnectionError, get_db,
|
||||||
get_connection)
|
get_connection)
|
||||||
@@ -99,11 +99,11 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertIsInstance(conn, mongomock.MongoClient)
|
||||||
|
|
||||||
connect(host=['mongodb://localhost'], is_mock=True, alias='testdb2')
|
connect(host=['mongodb://localhost'], is_mock=True, alias='testdb2')
|
||||||
conn = get_connection('testdb2')
|
conn = get_connection('testdb2')
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertIsInstance(conn, mongomock.MongoClient)
|
||||||
|
|
||||||
connect(host=['localhost'], is_mock=True, alias='testdb3')
|
connect(host=['localhost'], is_mock=True, alias='testdb3')
|
||||||
conn = get_connection('testdb3')
|
conn = get_connection('testdb3')
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertIsInstance(conn, mongomock.MongoClient)
|
||||||
|
|
||||||
@@ -111,11 +111,11 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
conn = get_connection('testdb4')
|
conn = get_connection('testdb4')
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertIsInstance(conn, mongomock.MongoClient)
|
||||||
|
|
||||||
connect(host=['mongodb://localhost:27017', 'mongodb://localhost:27018'], is_mock=True, alias='testdb5')
|
connect(host=['mongodb://localhost:27017', 'mongodb://localhost:27018'], is_mock=True, alias='testdb5')
|
||||||
conn = get_connection('testdb5')
|
conn = get_connection('testdb5')
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertIsInstance(conn, mongomock.MongoClient)
|
||||||
|
|
||||||
connect(host=['localhost:27017', 'localhost:27018'], is_mock=True, alias='testdb6')
|
connect(host=['localhost:27017', 'localhost:27018'], is_mock=True, alias='testdb6')
|
||||||
conn = get_connection('testdb6')
|
conn = get_connection('testdb6')
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertIsInstance(conn, mongomock.MongoClient)
|
||||||
|
|
||||||
@@ -147,12 +147,12 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
def test_connect_uri(self):
|
def test_connect_uri(self):
|
||||||
"""Ensure that the connect() method works properly with URIs."""
|
"""Ensure that the connect() method works properly with URIs."""
|
||||||
c = connect(db='mongoenginetest', alias='admin')
|
c = connect(db='mongoenginetest', alias='admin')
|
||||||
c.admin.system.users.remove({})
|
c.admin.system.users.delete_many({})
|
||||||
c.mongoenginetest.system.users.remove({})
|
c.mongoenginetest.system.users.delete_many({})
|
||||||
|
|
||||||
c.admin.add_user("admin", "password")
|
c.admin.command("createUser", "admin", pwd="password", roles=["root"])
|
||||||
c.admin.authenticate("admin", "password")
|
c.admin.authenticate("admin", "password")
|
||||||
c.mongoenginetest.add_user("username", "password")
|
c.admin.command("createUser", "username", pwd="password", roles=["dbOwner"])
|
||||||
|
|
||||||
if not IS_PYMONGO_3:
|
if not IS_PYMONGO_3:
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
@@ -169,8 +169,8 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
self.assertIsInstance(db, pymongo.database.Database)
|
self.assertIsInstance(db, pymongo.database.Database)
|
||||||
self.assertEqual(db.name, 'mongoenginetest')
|
self.assertEqual(db.name, 'mongoenginetest')
|
||||||
|
|
||||||
c.admin.system.users.remove({})
|
c.admin.system.users.delete_many({})
|
||||||
c.mongoenginetest.system.users.remove({})
|
c.mongoenginetest.system.users.delete_many({})
|
||||||
|
|
||||||
def test_connect_uri_without_db(self):
|
def test_connect_uri_without_db(self):
|
||||||
"""Ensure connect() method works properly if the URI doesn't
|
"""Ensure connect() method works properly if the URI doesn't
|
||||||
@@ -217,8 +217,9 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
# Create users
|
# Create users
|
||||||
c = connect('mongoenginetest')
|
c = connect('mongoenginetest')
|
||||||
c.admin.system.users.remove({})
|
|
||||||
c.admin.add_user('username2', 'password')
|
c.admin.system.users.delete_many({})
|
||||||
|
c.admin.command("createUser", "username2", pwd="password", roles=["dbOwner"])
|
||||||
|
|
||||||
# Authentication fails without "authSource"
|
# Authentication fails without "authSource"
|
||||||
if IS_PYMONGO_3:
|
if IS_PYMONGO_3:
|
||||||
@@ -246,7 +247,7 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
self.assertEqual(db.name, 'mongoenginetest')
|
self.assertEqual(db.name, 'mongoenginetest')
|
||||||
|
|
||||||
# Clear all users
|
# Clear all users
|
||||||
authd_conn.admin.system.users.remove({})
|
authd_conn.admin.system.users.delete_many({})
|
||||||
|
|
||||||
def test_register_connection(self):
|
def test_register_connection(self):
|
||||||
"""Ensure that connections with different aliases may be registered.
|
"""Ensure that connections with different aliases may be registered.
|
||||||
|
@@ -5,6 +5,7 @@ from mongoengine.connection import get_db
|
|||||||
from mongoengine.context_managers import (switch_db, switch_collection,
|
from mongoengine.context_managers import (switch_db, switch_collection,
|
||||||
no_sub_classes, no_dereference,
|
no_sub_classes, no_dereference,
|
||||||
query_counter)
|
query_counter)
|
||||||
|
from mongoengine.pymongo_support import count_documents
|
||||||
|
|
||||||
|
|
||||||
class ContextManagersTest(unittest.TestCase):
|
class ContextManagersTest(unittest.TestCase):
|
||||||
@@ -240,7 +241,7 @@ class ContextManagersTest(unittest.TestCase):
|
|||||||
collection.drop()
|
collection.drop()
|
||||||
|
|
||||||
def issue_1_count_query():
|
def issue_1_count_query():
|
||||||
collection.find({}).count()
|
count_documents(collection, {})
|
||||||
|
|
||||||
def issue_1_insert_query():
|
def issue_1_insert_query():
|
||||||
collection.insert_one({'test': 'garbage'})
|
collection.insert_one({'test': 'garbage'})
|
||||||
@@ -302,5 +303,6 @@ class ContextManagersTest(unittest.TestCase):
|
|||||||
_ = db.system.indexes.find_one() # queries on db.system.indexes are ignored as well
|
_ = db.system.indexes.find_one() # queries on db.system.indexes are ignored as well
|
||||||
self.assertEqual(q, 1)
|
self.assertEqual(q, 1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -1,22 +1,361 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from mongoengine.base.datastructures import StrictDict, BaseList
|
from mongoengine import Document
|
||||||
|
from mongoengine.base.datastructures import StrictDict, BaseList, BaseDict
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentStub(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._changed_fields = []
|
||||||
|
|
||||||
|
def _mark_as_changed(self, key):
|
||||||
|
self._changed_fields.append(key)
|
||||||
|
|
||||||
|
|
||||||
|
class TestBaseDict(unittest.TestCase):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_basedict(dict_items):
|
||||||
|
"""Get a BaseList bound to a fake document instance"""
|
||||||
|
fake_doc = DocumentStub()
|
||||||
|
base_list = BaseDict(dict_items, instance=None, name='my_name')
|
||||||
|
base_list._instance = fake_doc # hack to inject the mock, it does not work in the constructor
|
||||||
|
return base_list
|
||||||
|
|
||||||
|
def test___init___(self):
|
||||||
|
class MyDoc(Document):
|
||||||
|
pass
|
||||||
|
|
||||||
|
dict_items = {'k': 'v'}
|
||||||
|
doc = MyDoc()
|
||||||
|
base_dict = BaseDict(dict_items, instance=doc, name='my_name')
|
||||||
|
self.assertIsInstance(base_dict._instance, Document)
|
||||||
|
self.assertEqual(base_dict._name, 'my_name')
|
||||||
|
self.assertEqual(base_dict, dict_items)
|
||||||
|
|
||||||
|
def test_setdefault_calls_mark_as_changed(self):
|
||||||
|
base_dict = self._get_basedict({})
|
||||||
|
base_dict.setdefault('k', 'v')
|
||||||
|
self.assertEqual(base_dict._instance._changed_fields, [base_dict._name])
|
||||||
|
|
||||||
|
def test_popitems_calls_mark_as_changed(self):
|
||||||
|
base_dict = self._get_basedict({'k': 'v'})
|
||||||
|
self.assertEqual(base_dict.popitem(), ('k', 'v'))
|
||||||
|
self.assertEqual(base_dict._instance._changed_fields, [base_dict._name])
|
||||||
|
self.assertFalse(base_dict)
|
||||||
|
|
||||||
|
def test_pop_calls_mark_as_changed(self):
|
||||||
|
base_dict = self._get_basedict({'k': 'v'})
|
||||||
|
self.assertEqual(base_dict.pop('k'), 'v')
|
||||||
|
self.assertEqual(base_dict._instance._changed_fields, [base_dict._name])
|
||||||
|
self.assertFalse(base_dict)
|
||||||
|
|
||||||
|
def test_pop_calls_does_not_mark_as_changed_when_it_fails(self):
|
||||||
|
base_dict = self._get_basedict({'k': 'v'})
|
||||||
|
with self.assertRaises(KeyError):
|
||||||
|
base_dict.pop('X')
|
||||||
|
self.assertFalse(base_dict._instance._changed_fields)
|
||||||
|
|
||||||
|
def test_clear_calls_mark_as_changed(self):
|
||||||
|
base_dict = self._get_basedict({'k': 'v'})
|
||||||
|
base_dict.clear()
|
||||||
|
self.assertEqual(base_dict._instance._changed_fields, ['my_name'])
|
||||||
|
self.assertEqual(base_dict, {})
|
||||||
|
|
||||||
|
def test___delitem___calls_mark_as_changed(self):
|
||||||
|
base_dict = self._get_basedict({'k': 'v'})
|
||||||
|
del base_dict['k']
|
||||||
|
self.assertEqual(base_dict._instance._changed_fields, ['my_name.k'])
|
||||||
|
self.assertEqual(base_dict, {})
|
||||||
|
|
||||||
|
def test___getitem____KeyError(self):
|
||||||
|
base_dict = self._get_basedict({})
|
||||||
|
with self.assertRaises(KeyError):
|
||||||
|
base_dict['new']
|
||||||
|
|
||||||
|
def test___getitem____simple_value(self):
|
||||||
|
base_dict = self._get_basedict({'k': 'v'})
|
||||||
|
base_dict['k'] = 'v'
|
||||||
|
|
||||||
|
def test___getitem____sublist_gets_converted_to_BaseList(self):
|
||||||
|
base_dict = self._get_basedict({'k': [0, 1, 2]})
|
||||||
|
sub_list = base_dict['k']
|
||||||
|
self.assertEqual(sub_list, [0, 1, 2])
|
||||||
|
self.assertIsInstance(sub_list, BaseList)
|
||||||
|
self.assertIs(sub_list._instance, base_dict._instance)
|
||||||
|
self.assertEqual(sub_list._name, 'my_name.k')
|
||||||
|
self.assertEqual(base_dict._instance._changed_fields, [])
|
||||||
|
|
||||||
|
# Challenge mark_as_changed from sublist
|
||||||
|
sub_list[1] = None
|
||||||
|
self.assertEqual(base_dict._instance._changed_fields, ['my_name.k.1'])
|
||||||
|
|
||||||
|
def test___getitem____subdict_gets_converted_to_BaseDict(self):
|
||||||
|
base_dict = self._get_basedict({'k': {'subk': 'subv'}})
|
||||||
|
sub_dict = base_dict['k']
|
||||||
|
self.assertEqual(sub_dict, {'subk': 'subv'})
|
||||||
|
self.assertIsInstance(sub_dict, BaseDict)
|
||||||
|
self.assertIs(sub_dict._instance, base_dict._instance)
|
||||||
|
self.assertEqual(sub_dict._name, 'my_name.k')
|
||||||
|
self.assertEqual(base_dict._instance._changed_fields, [])
|
||||||
|
|
||||||
|
# Challenge mark_as_changed from subdict
|
||||||
|
sub_dict['subk'] = None
|
||||||
|
self.assertEqual(base_dict._instance._changed_fields, ['my_name.k.subk'])
|
||||||
|
|
||||||
|
def test_get_sublist_gets_converted_to_BaseList_just_like__getitem__(self):
|
||||||
|
base_dict = self._get_basedict({'k': [0, 1, 2]})
|
||||||
|
sub_list = base_dict.get('k')
|
||||||
|
self.assertEqual(sub_list, [0, 1, 2])
|
||||||
|
self.assertIsInstance(sub_list, BaseList)
|
||||||
|
|
||||||
|
def test_get_returns_the_same_as___getitem__(self):
|
||||||
|
base_dict = self._get_basedict({'k': [0, 1, 2]})
|
||||||
|
get_ = base_dict.get('k')
|
||||||
|
getitem_ = base_dict['k']
|
||||||
|
self.assertEqual(get_, getitem_)
|
||||||
|
|
||||||
|
def test_get_default(self):
|
||||||
|
base_dict = self._get_basedict({})
|
||||||
|
sentinel = object()
|
||||||
|
self.assertEqual(base_dict.get('new'), None)
|
||||||
|
self.assertIs(base_dict.get('new', sentinel), sentinel)
|
||||||
|
|
||||||
|
def test___setitem___calls_mark_as_changed(self):
|
||||||
|
base_dict = self._get_basedict({})
|
||||||
|
base_dict['k'] = 'v'
|
||||||
|
self.assertEqual(base_dict._instance._changed_fields, ['my_name.k'])
|
||||||
|
self.assertEqual(base_dict, {'k': 'v'})
|
||||||
|
|
||||||
|
def test_update_calls_mark_as_changed(self):
|
||||||
|
base_dict = self._get_basedict({})
|
||||||
|
base_dict.update({'k': 'v'})
|
||||||
|
self.assertEqual(base_dict._instance._changed_fields, ['my_name'])
|
||||||
|
|
||||||
|
def test___setattr____not_tracked_by_changes(self):
|
||||||
|
base_dict = self._get_basedict({})
|
||||||
|
base_dict.a_new_attr = 'test'
|
||||||
|
self.assertEqual(base_dict._instance._changed_fields, [])
|
||||||
|
|
||||||
|
def test___delattr____tracked_by_changes(self):
|
||||||
|
# This is probably a bug as __setattr__ is not tracked
|
||||||
|
# This is even bad because it could be that there is an attribute
|
||||||
|
# with the same name as a key
|
||||||
|
base_dict = self._get_basedict({})
|
||||||
|
base_dict.a_new_attr = 'test'
|
||||||
|
del base_dict.a_new_attr
|
||||||
|
self.assertEqual(base_dict._instance._changed_fields, ['my_name.a_new_attr'])
|
||||||
|
|
||||||
|
|
||||||
class TestBaseList(unittest.TestCase):
|
class TestBaseList(unittest.TestCase):
|
||||||
|
|
||||||
def test_iter_simple(self):
|
@staticmethod
|
||||||
|
def _get_baselist(list_items):
|
||||||
|
"""Get a BaseList bound to a fake document instance"""
|
||||||
|
fake_doc = DocumentStub()
|
||||||
|
base_list = BaseList(list_items, instance=None, name='my_name')
|
||||||
|
base_list._instance = fake_doc # hack to inject the mock, it does not work in the constructor
|
||||||
|
return base_list
|
||||||
|
|
||||||
|
def test___init___(self):
|
||||||
|
class MyDoc(Document):
|
||||||
|
pass
|
||||||
|
|
||||||
|
list_items = [True]
|
||||||
|
doc = MyDoc()
|
||||||
|
base_list = BaseList(list_items, instance=doc, name='my_name')
|
||||||
|
self.assertIsInstance(base_list._instance, Document)
|
||||||
|
self.assertEqual(base_list._name, 'my_name')
|
||||||
|
self.assertEqual(base_list, list_items)
|
||||||
|
|
||||||
|
def test___iter__(self):
|
||||||
values = [True, False, True, False]
|
values = [True, False, True, False]
|
||||||
base_list = BaseList(values, instance=None, name='my_name')
|
base_list = BaseList(values, instance=None, name='my_name')
|
||||||
self.assertEqual(values, list(base_list))
|
self.assertEqual(values, list(base_list))
|
||||||
|
|
||||||
def test_iter_allow_modification_while_iterating_withou_error(self):
|
def test___iter___allow_modification_while_iterating_withou_error(self):
|
||||||
# regular list allows for this, thus this subclass must comply to that
|
# regular list allows for this, thus this subclass must comply to that
|
||||||
base_list = BaseList([True, False, True, False], instance=None, name='my_name')
|
base_list = BaseList([True, False, True, False], instance=None, name='my_name')
|
||||||
for idx, val in enumerate(base_list):
|
for idx, val in enumerate(base_list):
|
||||||
if val:
|
if val:
|
||||||
base_list.pop(idx)
|
base_list.pop(idx)
|
||||||
|
|
||||||
|
def test_append_calls_mark_as_changed(self):
|
||||||
|
base_list = self._get_baselist([])
|
||||||
|
self.assertFalse(base_list._instance._changed_fields)
|
||||||
|
base_list.append(True)
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
|
||||||
|
|
||||||
|
def test_subclass_append(self):
|
||||||
|
# Due to the way mark_as_changed_wrapper is implemented
|
||||||
|
# it is good to test subclasses
|
||||||
|
class SubBaseList(BaseList):
|
||||||
|
pass
|
||||||
|
|
||||||
|
base_list = SubBaseList([], instance=None, name='my_name')
|
||||||
|
base_list.append(True)
|
||||||
|
|
||||||
|
def test___getitem__using_simple_index(self):
|
||||||
|
base_list = self._get_baselist([0, 1, 2])
|
||||||
|
self.assertEqual(base_list[0], 0)
|
||||||
|
self.assertEqual(base_list[1], 1)
|
||||||
|
self.assertEqual(base_list[-1], 2)
|
||||||
|
|
||||||
|
def test___getitem__using_slice(self):
|
||||||
|
base_list = self._get_baselist([0, 1, 2])
|
||||||
|
self.assertEqual(base_list[1:3], [1, 2])
|
||||||
|
self.assertEqual(base_list[0:3:2], [0, 2])
|
||||||
|
|
||||||
|
def test___getitem___using_slice_returns_list(self):
|
||||||
|
# Bug: using slice does not properly handles the instance
|
||||||
|
# and mark_as_changed behaviour.
|
||||||
|
base_list = self._get_baselist([0, 1, 2])
|
||||||
|
sliced = base_list[1:3]
|
||||||
|
self.assertEqual(sliced, [1, 2])
|
||||||
|
self.assertIsInstance(sliced, list)
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, [])
|
||||||
|
|
||||||
|
def test___getitem__sublist_returns_BaseList_bound_to_instance(self):
|
||||||
|
base_list = self._get_baselist(
|
||||||
|
[
|
||||||
|
[1, 2],
|
||||||
|
[3, 4]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
sub_list = base_list[0]
|
||||||
|
self.assertEqual(sub_list, [1, 2])
|
||||||
|
self.assertIsInstance(sub_list, BaseList)
|
||||||
|
self.assertIs(sub_list._instance, base_list._instance)
|
||||||
|
self.assertEqual(sub_list._name, 'my_name.0')
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, [])
|
||||||
|
|
||||||
|
# Challenge mark_as_changed from sublist
|
||||||
|
sub_list[1] = None
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name.0.1'])
|
||||||
|
|
||||||
|
def test___getitem__subdict_returns_BaseList_bound_to_instance(self):
|
||||||
|
base_list = self._get_baselist(
|
||||||
|
[
|
||||||
|
{'subk': 'subv'}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
sub_dict = base_list[0]
|
||||||
|
self.assertEqual(sub_dict, {'subk': 'subv'})
|
||||||
|
self.assertIsInstance(sub_dict, BaseDict)
|
||||||
|
self.assertIs(sub_dict._instance, base_list._instance)
|
||||||
|
self.assertEqual(sub_dict._name, 'my_name.0')
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, [])
|
||||||
|
|
||||||
|
# Challenge mark_as_changed from subdict
|
||||||
|
sub_dict['subk'] = None
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name.0.subk'])
|
||||||
|
|
||||||
|
def test_extend_calls_mark_as_changed(self):
|
||||||
|
base_list = self._get_baselist([])
|
||||||
|
base_list.extend([True])
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
|
||||||
|
|
||||||
|
def test_insert_calls_mark_as_changed(self):
|
||||||
|
base_list = self._get_baselist([])
|
||||||
|
base_list.insert(0, True)
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
|
||||||
|
|
||||||
|
def test_remove_calls_mark_as_changed(self):
|
||||||
|
base_list = self._get_baselist([True])
|
||||||
|
base_list.remove(True)
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
|
||||||
|
|
||||||
|
def test_remove_not_mark_as_changed_when_it_fails(self):
|
||||||
|
base_list = self._get_baselist([True])
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
base_list.remove(False)
|
||||||
|
self.assertFalse(base_list._instance._changed_fields)
|
||||||
|
|
||||||
|
def test_pop_calls_mark_as_changed(self):
|
||||||
|
base_list = self._get_baselist([True])
|
||||||
|
base_list.pop()
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
|
||||||
|
|
||||||
|
def test_reverse_calls_mark_as_changed(self):
|
||||||
|
base_list = self._get_baselist([True, False])
|
||||||
|
base_list.reverse()
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
|
||||||
|
|
||||||
|
def test___delitem___calls_mark_as_changed(self):
|
||||||
|
base_list = self._get_baselist([True])
|
||||||
|
del base_list[0]
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
|
||||||
|
|
||||||
|
def test___setitem___calls_with_full_slice_mark_as_changed(self):
|
||||||
|
base_list = self._get_baselist([])
|
||||||
|
base_list[:] = [0, 1] # Will use __setslice__ under py2 and __setitem__ under py3
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
|
||||||
|
self.assertEqual(base_list, [0, 1])
|
||||||
|
|
||||||
|
def test___setitem___calls_with_partial_slice_mark_as_changed(self):
|
||||||
|
base_list = self._get_baselist([0, 1, 2])
|
||||||
|
base_list[0:2] = [1, 0] # Will use __setslice__ under py2 and __setitem__ under py3
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
|
||||||
|
self.assertEqual(base_list, [1, 0, 2])
|
||||||
|
|
||||||
|
def test___setitem___calls_with_step_slice_mark_as_changed(self):
|
||||||
|
base_list = self._get_baselist([0, 1, 2])
|
||||||
|
base_list[0:3:2] = [-1, -2] # uses __setitem__ in both py2 & 3
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
|
||||||
|
self.assertEqual(base_list, [-1, 1, -2])
|
||||||
|
|
||||||
|
def test___setitem___with_slice(self):
|
||||||
|
base_list = self._get_baselist([0, 1, 2, 3, 4, 5])
|
||||||
|
base_list[0:6:2] = [None, None, None]
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
|
||||||
|
self.assertEqual(base_list, [None, 1, None, 3, None, 5])
|
||||||
|
|
||||||
|
def test___setitem___item_0_calls_mark_as_changed(self):
|
||||||
|
base_list = self._get_baselist([True])
|
||||||
|
base_list[0] = False
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
|
||||||
|
self.assertEqual(base_list, [False])
|
||||||
|
|
||||||
|
def test___setitem___item_1_calls_mark_as_changed(self):
|
||||||
|
base_list = self._get_baselist([True, True])
|
||||||
|
base_list[1] = False
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name.1'])
|
||||||
|
self.assertEqual(base_list, [True, False])
|
||||||
|
|
||||||
|
def test___delslice___calls_mark_as_changed(self):
|
||||||
|
base_list = self._get_baselist([0, 1])
|
||||||
|
del base_list[0:1]
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
|
||||||
|
self.assertEqual(base_list, [1])
|
||||||
|
|
||||||
|
def test___iadd___calls_mark_as_changed(self):
|
||||||
|
base_list = self._get_baselist([True])
|
||||||
|
base_list += [False]
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
|
||||||
|
|
||||||
|
def test___imul___calls_mark_as_changed(self):
|
||||||
|
base_list = self._get_baselist([True])
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, [])
|
||||||
|
base_list *= 2
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
|
||||||
|
|
||||||
|
def test_sort_calls_not_marked_as_changed_when_it_fails(self):
|
||||||
|
base_list = self._get_baselist([True])
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
base_list.sort(key=1)
|
||||||
|
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, [])
|
||||||
|
|
||||||
|
def test_sort_calls_mark_as_changed(self):
|
||||||
|
base_list = self._get_baselist([True, False])
|
||||||
|
base_list.sort()
|
||||||
|
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
|
||||||
|
|
||||||
|
def test_sort_calls_with_key(self):
|
||||||
|
base_list = self._get_baselist([1, 2, 11])
|
||||||
|
base_list.sort(key=lambda i: str(i))
|
||||||
|
self.assertEqual(base_list, [1, 11, 2])
|
||||||
|
|
||||||
|
|
||||||
class TestStrictDict(unittest.TestCase):
|
class TestStrictDict(unittest.TestCase):
|
||||||
def strict_dict_class(self, *args, **kwargs):
|
def strict_dict_class(self, *args, **kwargs):
|
||||||
@@ -87,8 +426,8 @@ class TestStrictDict(unittest.TestCase):
|
|||||||
|
|
||||||
def test_mappings_protocol(self):
|
def test_mappings_protocol(self):
|
||||||
d = self.dtype(a=1, b=2)
|
d = self.dtype(a=1, b=2)
|
||||||
assert dict(d) == {'a': 1, 'b': 2}
|
self.assertEqual(dict(d), {'a': 1, 'b': 2})
|
||||||
assert dict(**d) == {'a': 1, 'b': 2}
|
self.assertEqual(dict(**d), {'a': 1, 'b': 2})
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from bson import DBRef, ObjectId
|
from bson import DBRef, ObjectId
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
from mongoengine.connection import get_db
|
from mongoengine.connection import get_db
|
||||||
@@ -632,7 +633,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
[m for m in group_obj.members]
|
[m for m in group_obj.members]
|
||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in iteritems(group_obj.members):
|
||||||
self.assertIsInstance(m, User)
|
self.assertIsInstance(m, User)
|
||||||
|
|
||||||
# Document select_related
|
# Document select_related
|
||||||
@@ -645,7 +646,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
[m for m in group_obj.members]
|
[m for m in group_obj.members]
|
||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in iteritems(group_obj.members):
|
||||||
self.assertIsInstance(m, User)
|
self.assertIsInstance(m, User)
|
||||||
|
|
||||||
# Queryset select_related
|
# Queryset select_related
|
||||||
@@ -659,7 +660,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
[m for m in group_obj.members]
|
[m for m in group_obj.members]
|
||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in iteritems(group_obj.members):
|
||||||
self.assertIsInstance(m, User)
|
self.assertIsInstance(m, User)
|
||||||
|
|
||||||
User.drop_collection()
|
User.drop_collection()
|
||||||
@@ -714,7 +715,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
[m for m in group_obj.members]
|
[m for m in group_obj.members]
|
||||||
self.assertEqual(q, 4)
|
self.assertEqual(q, 4)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in iteritems(group_obj.members):
|
||||||
self.assertIn('User', m.__class__.__name__)
|
self.assertIn('User', m.__class__.__name__)
|
||||||
|
|
||||||
# Document select_related
|
# Document select_related
|
||||||
@@ -730,7 +731,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
[m for m in group_obj.members]
|
[m for m in group_obj.members]
|
||||||
self.assertEqual(q, 4)
|
self.assertEqual(q, 4)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in iteritems(group_obj.members):
|
||||||
self.assertIn('User', m.__class__.__name__)
|
self.assertIn('User', m.__class__.__name__)
|
||||||
|
|
||||||
# Queryset select_related
|
# Queryset select_related
|
||||||
@@ -747,7 +748,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
[m for m in group_obj.members]
|
[m for m in group_obj.members]
|
||||||
self.assertEqual(q, 4)
|
self.assertEqual(q, 4)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in iteritems(group_obj.members):
|
||||||
self.assertIn('User', m.__class__.__name__)
|
self.assertIn('User', m.__class__.__name__)
|
||||||
|
|
||||||
Group.objects.delete()
|
Group.objects.delete()
|
||||||
@@ -805,7 +806,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
[m for m in group_obj.members]
|
[m for m in group_obj.members]
|
||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in iteritems(group_obj.members):
|
||||||
self.assertIsInstance(m, UserA)
|
self.assertIsInstance(m, UserA)
|
||||||
|
|
||||||
# Document select_related
|
# Document select_related
|
||||||
@@ -821,7 +822,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
[m for m in group_obj.members]
|
[m for m in group_obj.members]
|
||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in iteritems(group_obj.members):
|
||||||
self.assertIsInstance(m, UserA)
|
self.assertIsInstance(m, UserA)
|
||||||
|
|
||||||
# Queryset select_related
|
# Queryset select_related
|
||||||
@@ -838,7 +839,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
[m for m in group_obj.members]
|
[m for m in group_obj.members]
|
||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in iteritems(group_obj.members):
|
||||||
self.assertIsInstance(m, UserA)
|
self.assertIsInstance(m, UserA)
|
||||||
|
|
||||||
UserA.drop_collection()
|
UserA.drop_collection()
|
||||||
@@ -893,7 +894,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
[m for m in group_obj.members]
|
[m for m in group_obj.members]
|
||||||
self.assertEqual(q, 4)
|
self.assertEqual(q, 4)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in iteritems(group_obj.members):
|
||||||
self.assertIn('User', m.__class__.__name__)
|
self.assertIn('User', m.__class__.__name__)
|
||||||
|
|
||||||
# Document select_related
|
# Document select_related
|
||||||
@@ -909,7 +910,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
[m for m in group_obj.members]
|
[m for m in group_obj.members]
|
||||||
self.assertEqual(q, 4)
|
self.assertEqual(q, 4)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in iteritems(group_obj.members):
|
||||||
self.assertIn('User', m.__class__.__name__)
|
self.assertIn('User', m.__class__.__name__)
|
||||||
|
|
||||||
# Queryset select_related
|
# Queryset select_related
|
||||||
@@ -926,7 +927,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
[m for m in group_obj.members]
|
[m for m in group_obj.members]
|
||||||
self.assertEqual(q, 4)
|
self.assertEqual(q, 4)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in iteritems(group_obj.members):
|
||||||
self.assertIn('User', m.__class__.__name__)
|
self.assertIn('User', m.__class__.__name__)
|
||||||
|
|
||||||
Group.objects.delete()
|
Group.objects.delete()
|
||||||
@@ -1064,7 +1065,6 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(msg.author, user)
|
self.assertEqual(msg.author, user)
|
||||||
self.assertEqual(msg.author.name, 'new-name')
|
self.assertEqual(msg.author.name, 'new-name')
|
||||||
|
|
||||||
|
|
||||||
def test_list_lookup_not_checked_in_map(self):
|
def test_list_lookup_not_checked_in_map(self):
|
||||||
"""Ensure we dereference list data correctly
|
"""Ensure we dereference list data correctly
|
||||||
"""
|
"""
|
||||||
@@ -1286,5 +1286,6 @@ class FieldTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -2,7 +2,7 @@ import unittest
|
|||||||
|
|
||||||
from pymongo import ReadPreference
|
from pymongo import ReadPreference
|
||||||
|
|
||||||
from mongoengine.python_support import IS_PYMONGO_3
|
from mongoengine.pymongo_support import IS_PYMONGO_3
|
||||||
|
|
||||||
if IS_PYMONGO_3:
|
if IS_PYMONGO_3:
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
@@ -47,5 +47,6 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(conn.read_preference, READ_PREF)
|
self.assertEqual(conn.read_preference, READ_PREF)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -39,7 +39,6 @@ class SignalTests(unittest.TestCase):
|
|||||||
def post_init(cls, sender, document, **kwargs):
|
def post_init(cls, sender, document, **kwargs):
|
||||||
signal_output.append('post_init signal, %s, document._created = %s' % (document, document._created))
|
signal_output.append('post_init signal, %s, document._created = %s' % (document, document._created))
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pre_save(cls, sender, document, **kwargs):
|
def pre_save(cls, sender, document, **kwargs):
|
||||||
signal_output.append('pre_save signal, %s' % document)
|
signal_output.append('pre_save signal, %s' % document)
|
||||||
@@ -247,7 +246,7 @@ class SignalTests(unittest.TestCase):
|
|||||||
def load_existing_author():
|
def load_existing_author():
|
||||||
a = self.Author(name='Bill Shakespeare')
|
a = self.Author(name='Bill Shakespeare')
|
||||||
a.save()
|
a.save()
|
||||||
self.get_signal_output(lambda: None) # eliminate signal output
|
self.get_signal_output(lambda: None) # eliminate signal output
|
||||||
a1 = self.Author.objects(name='Bill Shakespeare')[0]
|
a1 = self.Author.objects(name='Bill Shakespeare')[0]
|
||||||
|
|
||||||
self.assertEqual(self.get_signal_output(create_author), [
|
self.assertEqual(self.get_signal_output(create_author), [
|
||||||
@@ -431,5 +430,6 @@ class SignalTests(unittest.TestCase):
|
|||||||
{}
|
{}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -1,22 +1,17 @@
|
|||||||
|
import operator
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from nose.plugins.skip import SkipTest
|
from nose.plugins.skip import SkipTest
|
||||||
|
|
||||||
from mongoengine import connect
|
from mongoengine import connect
|
||||||
from mongoengine.connection import get_db, get_connection
|
from mongoengine.connection import get_db
|
||||||
from mongoengine.python_support import IS_PYMONGO_3
|
from mongoengine.mongodb_support import get_mongodb_version, MONGODB_26, MONGODB_3, MONGODB_32, MONGODB_34
|
||||||
|
from mongoengine.pymongo_support import IS_PYMONGO_3
|
||||||
|
|
||||||
|
|
||||||
MONGO_TEST_DB = 'mongoenginetest' # standard name for the test database
|
MONGO_TEST_DB = 'mongoenginetest' # standard name for the test database
|
||||||
|
|
||||||
|
|
||||||
# Constant that can be used to compare the version retrieved with
|
|
||||||
# get_mongodb_version()
|
|
||||||
MONGODB_26 = (2, 6)
|
|
||||||
MONGODB_3 = (3,0)
|
|
||||||
MONGODB_32 = (3, 2)
|
|
||||||
|
|
||||||
|
|
||||||
class MongoDBTestCase(unittest.TestCase):
|
class MongoDBTestCase(unittest.TestCase):
|
||||||
"""Base class for tests that need a mongodb connection
|
"""Base class for tests that need a mongodb connection
|
||||||
It ensures that the db is clean at the beginning and dropped at the end automatically
|
It ensures that the db is clean at the beginning and dropped at the end automatically
|
||||||
@@ -33,46 +28,56 @@ class MongoDBTestCase(unittest.TestCase):
|
|||||||
cls._connection.drop_database(MONGO_TEST_DB)
|
cls._connection.drop_database(MONGO_TEST_DB)
|
||||||
|
|
||||||
|
|
||||||
def get_mongodb_version():
|
def get_as_pymongo(doc):
|
||||||
"""Return the version of the connected mongoDB (first 2 digits)
|
"""Fetch the pymongo version of a certain Document"""
|
||||||
|
return doc.__class__.objects.as_pymongo().get(id=doc.id)
|
||||||
:return: tuple(int, int)
|
|
||||||
"""
|
|
||||||
version_list = get_connection().server_info()['versionArray'][:2] # e.g: (3, 2)
|
|
||||||
return tuple(version_list)
|
|
||||||
|
|
||||||
|
|
||||||
def _decorated_with_ver_requirement(func, version):
|
def _decorated_with_ver_requirement(func, mongo_version_req, oper):
|
||||||
"""Return a given function decorated with the version requirement
|
"""Return a given function decorated with the version requirement
|
||||||
for a particular MongoDB version tuple.
|
for a particular MongoDB version tuple.
|
||||||
|
|
||||||
:param version: The version required (tuple(int, int))
|
:param mongo_version_req: The mongodb version requirement (tuple(int, int))
|
||||||
|
:param oper: The operator to apply (e.g: operator.ge)
|
||||||
"""
|
"""
|
||||||
def _inner(*args, **kwargs):
|
def _inner(*args, **kwargs):
|
||||||
MONGODB_V = get_mongodb_version()
|
mongodb_v = get_mongodb_version()
|
||||||
if MONGODB_V >= version:
|
if oper(mongodb_v, mongo_version_req):
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
raise SkipTest('Needs MongoDB v{}+'.format('.'.join(str(n) for n in version)))
|
raise SkipTest('Needs MongoDB v{}+'.format('.'.join(str(n) for n in mongo_version_req)))
|
||||||
|
|
||||||
_inner.__name__ = func.__name__
|
_inner.__name__ = func.__name__
|
||||||
_inner.__doc__ = func.__doc__
|
_inner.__doc__ = func.__doc__
|
||||||
|
|
||||||
return _inner
|
return _inner
|
||||||
|
|
||||||
|
|
||||||
|
def requires_mongodb_gte_34(func):
|
||||||
|
"""Raise a SkipTest exception if we're working with MongoDB version
|
||||||
|
lower than v3.4
|
||||||
|
"""
|
||||||
|
return _decorated_with_ver_requirement(func, MONGODB_34, oper=operator.ge)
|
||||||
|
|
||||||
|
|
||||||
|
def requires_mongodb_lte_32(func):
|
||||||
|
"""Raise a SkipTest exception if we're working with MongoDB version
|
||||||
|
greater than v3.2.
|
||||||
|
"""
|
||||||
|
return _decorated_with_ver_requirement(func, MONGODB_32, oper=operator.le)
|
||||||
|
|
||||||
|
|
||||||
def requires_mongodb_gte_26(func):
|
def requires_mongodb_gte_26(func):
|
||||||
"""Raise a SkipTest exception if we're working with MongoDB version
|
"""Raise a SkipTest exception if we're working with MongoDB version
|
||||||
lower than v2.6.
|
lower than v2.6.
|
||||||
"""
|
"""
|
||||||
return _decorated_with_ver_requirement(func, MONGODB_26)
|
return _decorated_with_ver_requirement(func, MONGODB_26, oper=operator.ge)
|
||||||
|
|
||||||
|
|
||||||
def requires_mongodb_gte_3(func):
|
def requires_mongodb_gte_3(func):
|
||||||
"""Raise a SkipTest exception if we're working with MongoDB version
|
"""Raise a SkipTest exception if we're working with MongoDB version
|
||||||
lower than v3.0.
|
lower than v3.0.
|
||||||
"""
|
"""
|
||||||
return _decorated_with_ver_requirement(func, MONGODB_3)
|
return _decorated_with_ver_requirement(func, MONGODB_3, oper=operator.ge)
|
||||||
|
|
||||||
|
|
||||||
def skip_pymongo3(f):
|
def skip_pymongo3(f):
|
||||||
|
Reference in New Issue
Block a user