Merge branch 'master' into fix_2484
This commit is contained in:
commit
da173cf0e2
15
.github/workflows/github-actions.yml
vendored
15
.github/workflows/github-actions.yml
vendored
@ -11,9 +11,10 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- 'v[0-9]+\.[0-9]+\.[0-9]+*'
|
- 'v[0-9]+\.[0-9]+\.[0-9]+*'
|
||||||
env:
|
env:
|
||||||
MONGODB_3_4: 3.4.19
|
MONGODB_3_6: 3.6.14
|
||||||
MONGODB_3_6: 3.6.13
|
MONGODB_4_0: 4.0.23
|
||||||
MONGODB_4_0: 4.0.13
|
MONGODB_4_2: 4.2
|
||||||
|
MONGODB_4_4: 4.4
|
||||||
|
|
||||||
PYMONGO_3_4: 3.4
|
PYMONGO_3_4: 3.4
|
||||||
PYMONGO_3_6: 3.6
|
PYMONGO_3_6: 3.6
|
||||||
@ -47,14 +48,14 @@ jobs:
|
|||||||
MONGODB: [$MONGODB_4_0]
|
MONGODB: [$MONGODB_4_0]
|
||||||
PYMONGO: [$PYMONGO_3_11]
|
PYMONGO: [$PYMONGO_3_11]
|
||||||
include:
|
include:
|
||||||
- python-version: 3.7
|
|
||||||
MONGODB: $MONGODB_3_4
|
|
||||||
PYMONGO: $PYMONGO_3_6
|
|
||||||
- python-version: 3.7
|
- python-version: 3.7
|
||||||
MONGODB: $MONGODB_3_6
|
MONGODB: $MONGODB_3_6
|
||||||
PYMONGO: $PYMONGO_3_9
|
PYMONGO: $PYMONGO_3_9
|
||||||
- python-version: 3.7
|
- python-version: 3.7
|
||||||
MONGODB: $MONGODB_3_6
|
MONGODB: $MONGODB_4_2
|
||||||
|
PYMONGO: $PYMONGO_3_6
|
||||||
|
- python-version: 3.7
|
||||||
|
MONGODB: $MONGODB_4_4
|
||||||
PYMONGO: $PYMONGO_3_11
|
PYMONGO: $PYMONGO_3_11
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
12
.github/workflows/install_mongo.sh
vendored
12
.github/workflows/install_mongo.sh
vendored
@ -2,7 +2,17 @@
|
|||||||
|
|
||||||
MONGODB=$1
|
MONGODB=$1
|
||||||
|
|
||||||
|
# Mongo > 4.0 follows different name convention for download links
|
||||||
mongo_build=mongodb-linux-x86_64-${MONGODB}
|
mongo_build=mongodb-linux-x86_64-${MONGODB}
|
||||||
|
|
||||||
|
if [[ "$MONGODB" == *"4.2"* ]]; then
|
||||||
|
mongo_build=mongodb-linux-x86_64-ubuntu1804-v${MONGODB}-latest
|
||||||
|
elif [[ "$MONGODB" == *"4.4"* ]]; then
|
||||||
|
mongo_build=mongodb-linux-x86_64-ubuntu1804-v${MONGODB}-latest
|
||||||
|
fi
|
||||||
|
|
||||||
wget http://fastdl.mongodb.org/linux/$mongo_build.tgz
|
wget http://fastdl.mongodb.org/linux/$mongo_build.tgz
|
||||||
tar xzf $mongo_build.tgz
|
tar xzf $mongo_build.tgz
|
||||||
${PWD}/$mongo_build/bin/mongod --version
|
|
||||||
|
mongodb_dir=$(find ${PWD}/ -type d -name "mongodb-linux-x86_64*")
|
||||||
|
$mongodb_dir/bin/mongod --version
|
||||||
|
3
.github/workflows/start_mongo.sh
vendored
3
.github/workflows/start_mongo.sh
vendored
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
MONGODB=$1
|
MONGODB=$1
|
||||||
|
|
||||||
mongodb_dir=${PWD}/mongodb-linux-x86_64-${MONGODB}
|
mongodb_dir=$(find ${PWD}/ -type d -name "mongodb-linux-x86_64*")
|
||||||
|
|
||||||
mkdir $mongodb_dir/data
|
mkdir $mongodb_dir/data
|
||||||
$mongodb_dir/bin/mongod --dbpath $mongodb_dir/data --logpath $mongodb_dir/mongodb.log --fork
|
$mongodb_dir/bin/mongod --dbpath $mongodb_dir/data --logpath $mongodb_dir/mongodb.log --fork
|
||||||
mongo --eval 'db.version();' # Make sure mongo is awake
|
mongo --eval 'db.version();' # Make sure mongo is awake
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
=========
|
=========
|
||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
@ -6,8 +7,14 @@ Changelog
|
|||||||
Development
|
Development
|
||||||
===========
|
===========
|
||||||
- (Fill this out as you fix issues and develop your features).
|
- (Fill this out as you fix issues and develop your features).
|
||||||
- Bugfix: manually setting SequenceField in DynamicDocument doesn't increment the counter #2471
|
|
||||||
- Bug fix: ignore LazyReferenceFields when clearing _changed_fields #2484
|
- Bug fix: ignore LazyReferenceFields when clearing _changed_fields #2484
|
||||||
|
- Improve connection doc #2481
|
||||||
|
|
||||||
|
Changes in 0.23.0
|
||||||
|
=================
|
||||||
|
- Bugfix: manually setting SequenceField in DynamicDocument doesn't increment the counter #2471
|
||||||
|
- Add MongoDB 4.2 and 4.4 to CI
|
||||||
|
- Add support for allowDiskUse on querysets #2468
|
||||||
|
|
||||||
Changes in 0.22.1
|
Changes in 0.22.1
|
||||||
=================
|
=================
|
||||||
|
@ -5,7 +5,7 @@ Connecting to MongoDB
|
|||||||
=====================
|
=====================
|
||||||
|
|
||||||
Connections in MongoEngine are registered globally and are identified with aliases.
|
Connections in MongoEngine are registered globally and are identified with aliases.
|
||||||
If no `alias` is provided during the connection, it will use "default" as alias.
|
If no ``alias`` is provided during the connection, it will use "default" as alias.
|
||||||
|
|
||||||
To connect to a running instance of :program:`mongod`, use the :func:`~mongoengine.connect`
|
To connect to a running instance of :program:`mongod`, use the :func:`~mongoengine.connect`
|
||||||
function. The first argument is the name of the database to connect to::
|
function. The first argument is the name of the database to connect to::
|
||||||
@ -14,27 +14,66 @@ function. The first argument is the name of the database to connect to::
|
|||||||
connect('project1')
|
connect('project1')
|
||||||
|
|
||||||
By default, MongoEngine assumes that the :program:`mongod` instance is running
|
By default, MongoEngine assumes that the :program:`mongod` instance is running
|
||||||
on **localhost** on port **27017**. If MongoDB is running elsewhere, you should
|
on **localhost** on port **27017**.
|
||||||
provide the :attr:`host` and :attr:`port` arguments to
|
|
||||||
:func:`~mongoengine.connect`::
|
|
||||||
|
|
||||||
connect('project1', host='192.168.1.35', port=12345)
|
If MongoDB is running elsewhere, you need to provide details on how to connect. There are two ways of
|
||||||
|
doing this. Using a connection string in URI format (**this is the preferred method**) or individual attributes
|
||||||
|
provided as keyword arguments.
|
||||||
|
|
||||||
|
Connect with URI string
|
||||||
|
=======================
|
||||||
|
|
||||||
|
When using a connection string in URI format you should specify the connection details
|
||||||
|
as the :attr:`host` to :func:`~mongoengine.connect`. In a web application context for instance, the URI
|
||||||
|
is typically read from the config file::
|
||||||
|
|
||||||
|
connect(host="mongodb://127.0.0.1:27017/my_db")
|
||||||
|
|
||||||
|
If the database requires authentication, you can specify it in the
|
||||||
|
URI. As each database can have its own users configured, you need to tell MongoDB
|
||||||
|
where to look for the user you are working with, that's what the ``?authSource=admin`` bit
|
||||||
|
of the MongoDB connection string is for::
|
||||||
|
|
||||||
|
# Connects to 'my_db' database by authenticating
|
||||||
|
# with given credentials against the 'admin' database (by default as authSource isn't provided)
|
||||||
|
connect(host="mongodb://my_user:my_password@127.0.0.1:27017/my_db")
|
||||||
|
|
||||||
|
# Equivalent to previous connection but explicitly states that
|
||||||
|
# it should use admin as the authentication source database
|
||||||
|
connect(host="mongodb://my_user:my_password@hostname:port/my_db?authSource=admin")
|
||||||
|
|
||||||
|
# Connects to 'my_db' database by authenticating
|
||||||
|
# with given credentials against that same database
|
||||||
|
connect(host="mongodb://my_user:my_password@127.0.0.1:27017/my_db?authSource=my_db")
|
||||||
|
|
||||||
|
The URI string can also be used to configure advanced parameters like ssl, replicaSet, etc. For more
|
||||||
|
information or example about URI string, you can refer to the `official doc <https://docs.mongodb.com/manual/reference/connection-string/>`_::
|
||||||
|
|
||||||
|
connect(host="mongodb://my_user:my_password@127.0.0.1:27017/my_db?authSource=admin&ssl=true&replicaSet=globaldb")
|
||||||
|
|
||||||
|
.. note:: URI containing SRV records (e.g "mongodb+srv://server.example.com/") can be used as well
|
||||||
|
|
||||||
|
Connect with keyword attributes
|
||||||
|
===============================
|
||||||
|
|
||||||
|
The second option for specifying the connection details is to provide the information as keyword
|
||||||
|
attributes to :func:`~mongoengine.connect`::
|
||||||
|
|
||||||
|
connect('my_db', host='127.0.0.1', port=27017)
|
||||||
|
|
||||||
If the database requires authentication, :attr:`username`, :attr:`password`
|
If the database requires authentication, :attr:`username`, :attr:`password`
|
||||||
and :attr:`authentication_source` arguments should be provided::
|
and :attr:`authentication_source` arguments should be provided::
|
||||||
|
|
||||||
connect('project1', username='webapp', password='pwd123', authentication_source='admin')
|
connect('my_db', username='my_user', password='my_password', authentication_source='admin')
|
||||||
|
|
||||||
URI style connections are also supported -- just supply the URI as
|
The set of attributes that :func:`~mongoengine.connect` recognizes includes but is not limited to:
|
||||||
the :attr:`host` to
|
:attr:`host`, :attr:`port`, :attr:`read_preference`, :attr:`username`, :attr:`password`, :attr:`authentication_source`, :attr:`authentication_mechanism`,
|
||||||
:func:`~mongoengine.connect`::
|
:attr:`replicaset`, :attr:`tls`, etc. Most of the parameters accepted by `pymongo.MongoClient <https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient>`_
|
||||||
|
can be used with :func:`~mongoengine.connect` and will simply be forwarded when instantiating the `pymongo.MongoClient`.
|
||||||
connect('project1', host='mongodb://localhost/database_name')
|
|
||||||
|
|
||||||
.. note:: URI containing SRV records (e.g mongodb+srv://server.example.com/) can be used as well as the :attr:`host`
|
|
||||||
|
|
||||||
.. note:: Database, username and password from URI string overrides
|
.. note:: Database, username and password from URI string overrides
|
||||||
corresponding parameters in :func:`~mongoengine.connect`: ::
|
corresponding parameters in :func:`~mongoengine.connect`, this should
|
||||||
|
obviously be avoided: ::
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
db='test',
|
db='test',
|
||||||
@ -43,28 +82,19 @@ the :attr:`host` to
|
|||||||
host='mongodb://admin:qwerty@localhost/production'
|
host='mongodb://admin:qwerty@localhost/production'
|
||||||
)
|
)
|
||||||
|
|
||||||
will establish connection to ``production`` database using
|
will establish connection to ``production`` database using ``admin`` username and ``qwerty`` password.
|
||||||
``admin`` username and ``12345`` password.
|
|
||||||
|
|
||||||
.. note:: Calling :func:`~mongoengine.connect` without argument will establish
|
.. note:: Calling :func:`~mongoengine.connect` without argument will establish
|
||||||
a connection to the "test" database by default
|
a connection to the "test" database by default
|
||||||
|
|
||||||
Replica Sets
|
Read Preferences
|
||||||
============
|
================
|
||||||
|
|
||||||
MongoEngine supports connecting to replica sets::
|
As stated above, Read preferences are supported through the connection but also via individual
|
||||||
|
|
||||||
from mongoengine import connect
|
|
||||||
|
|
||||||
# Regular connect
|
|
||||||
connect('dbname', replicaset='rs-name')
|
|
||||||
|
|
||||||
# MongoDB URI-style connect
|
|
||||||
connect(host='mongodb://localhost/dbname?replicaSet=rs-name')
|
|
||||||
|
|
||||||
Read preferences are supported through the connection or via individual
|
|
||||||
queries by passing the read_preference ::
|
queries by passing the read_preference ::
|
||||||
|
|
||||||
|
from pymongo import ReadPreference
|
||||||
|
|
||||||
Bar.objects().read_preference(ReadPreference.PRIMARY)
|
Bar.objects().read_preference(ReadPreference.PRIMARY)
|
||||||
Bar.objects(read_preference=ReadPreference.PRIMARY)
|
Bar.objects(read_preference=ReadPreference.PRIMARY)
|
||||||
|
|
||||||
|
@ -290,12 +290,12 @@ as the constructor's argument::
|
|||||||
content = StringField()
|
content = StringField()
|
||||||
|
|
||||||
|
|
||||||
.. _one-to-many-with-listfields:
|
.. _many-to-many-with-listfields:
|
||||||
|
|
||||||
One to Many with ListFields
|
Many to Many with ListFields
|
||||||
'''''''''''''''''''''''''''
|
'''''''''''''''''''''''''''
|
||||||
|
|
||||||
If you are implementing a one to many relationship via a list of references,
|
If you are implementing a many to many relationship via a list of references,
|
||||||
then the references are stored as DBRefs and to query you need to pass an
|
then the references are stored as DBRefs and to query you need to pass an
|
||||||
instance of the object to the query::
|
instance of the object to the query::
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
VERSION = (0, 22, 1)
|
VERSION = (0, 23, 0)
|
||||||
|
|
||||||
|
|
||||||
def get_version():
|
def get_version():
|
||||||
|
@ -267,6 +267,17 @@ class ComplexBaseField(BaseField):
|
|||||||
self.field = field
|
self.field = field
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _lazy_load_refs(instance, name, ref_values, *, max_depth):
|
||||||
|
_dereference = _import_class("DeReference")()
|
||||||
|
documents = _dereference(
|
||||||
|
ref_values,
|
||||||
|
max_depth=max_depth,
|
||||||
|
instance=instance,
|
||||||
|
name=name,
|
||||||
|
)
|
||||||
|
return documents
|
||||||
|
|
||||||
def __get__(self, instance, owner):
|
def __get__(self, instance, owner):
|
||||||
"""Descriptor to automatically dereference references."""
|
"""Descriptor to automatically dereference references."""
|
||||||
if instance is None:
|
if instance is None:
|
||||||
@ -284,19 +295,15 @@ class ComplexBaseField(BaseField):
|
|||||||
or isinstance(self.field, (GenericReferenceField, ReferenceField))
|
or isinstance(self.field, (GenericReferenceField, ReferenceField))
|
||||||
)
|
)
|
||||||
|
|
||||||
_dereference = _import_class("DeReference")()
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
instance._initialised
|
instance._initialised
|
||||||
and dereference
|
and dereference
|
||||||
and instance._data.get(self.name)
|
and instance._data.get(self.name)
|
||||||
and not getattr(instance._data[self.name], "_dereferenced", False)
|
and not getattr(instance._data[self.name], "_dereferenced", False)
|
||||||
):
|
):
|
||||||
instance._data[self.name] = _dereference(
|
ref_values = instance._data.get(self.name)
|
||||||
instance._data.get(self.name),
|
instance._data[self.name] = self._lazy_load_refs(
|
||||||
max_depth=1,
|
ref_values=ref_values, instance=instance, name=self.name, max_depth=1
|
||||||
instance=instance,
|
|
||||||
name=self.name,
|
|
||||||
)
|
)
|
||||||
if hasattr(instance._data[self.name], "_dereferenced"):
|
if hasattr(instance._data[self.name], "_dereferenced"):
|
||||||
instance._data[self.name]._dereferenced = True
|
instance._data[self.name]._dereferenced = True
|
||||||
@ -322,7 +329,9 @@ class ComplexBaseField(BaseField):
|
|||||||
and isinstance(value, (BaseList, BaseDict))
|
and isinstance(value, (BaseList, BaseDict))
|
||||||
and not value._dereferenced
|
and not value._dereferenced
|
||||||
):
|
):
|
||||||
value = _dereference(value, max_depth=1, instance=instance, name=self.name)
|
value = self._lazy_load_refs(
|
||||||
|
ref_values=value, instance=instance, name=self.name, max_depth=1
|
||||||
|
)
|
||||||
value._dereferenced = True
|
value._dereferenced = True
|
||||||
instance._data[self.name] = value
|
instance._data[self.name] = value
|
||||||
|
|
||||||
|
@ -915,7 +915,7 @@ class ListField(ComplexBaseField):
|
|||||||
"""A list field that wraps a standard field, allowing multiple instances
|
"""A list field that wraps a standard field, allowing multiple instances
|
||||||
of the field to be used as a list in the database.
|
of the field to be used as a list in the database.
|
||||||
|
|
||||||
If using with ReferenceFields see: :ref:`one-to-many-with-listfields`
|
If using with ReferenceFields see: :ref:`many-to-many-with-listfields`
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
Required means it cannot be empty - as the default for ListFields is []
|
Required means it cannot be empty - as the default for ListFields is []
|
||||||
@ -1194,6 +1194,14 @@ class ReferenceField(BaseField):
|
|||||||
self.document_type_obj = get_document(self.document_type_obj)
|
self.document_type_obj = get_document(self.document_type_obj)
|
||||||
return self.document_type_obj
|
return self.document_type_obj
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _lazy_load_ref(ref_cls, dbref):
|
||||||
|
dereferenced_son = ref_cls._get_db().dereference(dbref)
|
||||||
|
if dereferenced_son is None:
|
||||||
|
raise DoesNotExist(f"Trying to dereference unknown document {dbref}")
|
||||||
|
|
||||||
|
return ref_cls._from_son(dereferenced_son)
|
||||||
|
|
||||||
def __get__(self, instance, owner):
|
def __get__(self, instance, owner):
|
||||||
"""Descriptor to allow lazy dereferencing."""
|
"""Descriptor to allow lazy dereferencing."""
|
||||||
if instance is None:
|
if instance is None:
|
||||||
@ -1201,20 +1209,17 @@ class ReferenceField(BaseField):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
# Get value from document instance if available
|
# Get value from document instance if available
|
||||||
value = instance._data.get(self.name)
|
ref_value = instance._data.get(self.name)
|
||||||
auto_dereference = instance._fields[self.name]._auto_dereference
|
auto_dereference = instance._fields[self.name]._auto_dereference
|
||||||
# Dereference DBRefs
|
# Dereference DBRefs
|
||||||
if auto_dereference and isinstance(value, DBRef):
|
if auto_dereference and isinstance(ref_value, DBRef):
|
||||||
if hasattr(value, "cls"):
|
if hasattr(ref_value, "cls"):
|
||||||
# Dereference using the class type specified in the reference
|
# Dereference using the class type specified in the reference
|
||||||
cls = get_document(value.cls)
|
cls = get_document(ref_value.cls)
|
||||||
else:
|
else:
|
||||||
cls = self.document_type
|
cls = self.document_type
|
||||||
dereferenced = cls._get_db().dereference(value)
|
|
||||||
if dereferenced is None:
|
instance._data[self.name] = self._lazy_load_ref(cls, ref_value)
|
||||||
raise DoesNotExist("Trying to dereference unknown document %s" % value)
|
|
||||||
else:
|
|
||||||
instance._data[self.name] = cls._from_son(dereferenced)
|
|
||||||
|
|
||||||
return super().__get__(instance, owner)
|
return super().__get__(instance, owner)
|
||||||
|
|
||||||
@ -1353,6 +1358,14 @@ class CachedReferenceField(BaseField):
|
|||||||
self.document_type_obj = get_document(self.document_type_obj)
|
self.document_type_obj = get_document(self.document_type_obj)
|
||||||
return self.document_type_obj
|
return self.document_type_obj
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _lazy_load_ref(ref_cls, dbref):
|
||||||
|
dereferenced_son = ref_cls._get_db().dereference(dbref)
|
||||||
|
if dereferenced_son is None:
|
||||||
|
raise DoesNotExist(f"Trying to dereference unknown document {dbref}")
|
||||||
|
|
||||||
|
return ref_cls._from_son(dereferenced_son)
|
||||||
|
|
||||||
def __get__(self, instance, owner):
|
def __get__(self, instance, owner):
|
||||||
if instance is None:
|
if instance is None:
|
||||||
# Document class being used rather than a document object
|
# Document class being used rather than a document object
|
||||||
@ -1364,11 +1377,7 @@ class CachedReferenceField(BaseField):
|
|||||||
|
|
||||||
# Dereference DBRefs
|
# Dereference DBRefs
|
||||||
if auto_dereference and isinstance(value, DBRef):
|
if auto_dereference and isinstance(value, DBRef):
|
||||||
dereferenced = self.document_type._get_db().dereference(value)
|
instance._data[self.name] = self._lazy_load_ref(self.document_type, value)
|
||||||
if dereferenced is None:
|
|
||||||
raise DoesNotExist("Trying to dereference unknown document %s" % value)
|
|
||||||
else:
|
|
||||||
instance._data[self.name] = self.document_type._from_son(dereferenced)
|
|
||||||
|
|
||||||
return super().__get__(instance, owner)
|
return super().__get__(instance, owner)
|
||||||
|
|
||||||
@ -1493,6 +1502,14 @@ class GenericReferenceField(BaseField):
|
|||||||
value = value._class_name
|
value = value._class_name
|
||||||
super()._validate_choices(value)
|
super()._validate_choices(value)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _lazy_load_ref(ref_cls, dbref):
|
||||||
|
dereferenced_son = ref_cls._get_db().dereference(dbref)
|
||||||
|
if dereferenced_son is None:
|
||||||
|
raise DoesNotExist(f"Trying to dereference unknown document {dbref}")
|
||||||
|
|
||||||
|
return ref_cls._from_son(dereferenced_son)
|
||||||
|
|
||||||
def __get__(self, instance, owner):
|
def __get__(self, instance, owner):
|
||||||
if instance is None:
|
if instance is None:
|
||||||
return self
|
return self
|
||||||
@ -1500,12 +1517,9 @@ class GenericReferenceField(BaseField):
|
|||||||
value = instance._data.get(self.name)
|
value = instance._data.get(self.name)
|
||||||
|
|
||||||
auto_dereference = instance._fields[self.name]._auto_dereference
|
auto_dereference = instance._fields[self.name]._auto_dereference
|
||||||
if auto_dereference and isinstance(value, (dict, SON)):
|
if auto_dereference and isinstance(value, dict):
|
||||||
dereferenced = self.dereference(value)
|
doc_cls = get_document(value["_cls"])
|
||||||
if dereferenced is None:
|
instance._data[self.name] = self._lazy_load_ref(doc_cls, value["_ref"])
|
||||||
raise DoesNotExist("Trying to dereference unknown document %s" % value)
|
|
||||||
else:
|
|
||||||
instance._data[self.name] = dereferenced
|
|
||||||
|
|
||||||
return super().__get__(instance, owner)
|
return super().__get__(instance, owner)
|
||||||
|
|
||||||
@ -1524,14 +1538,6 @@ class GenericReferenceField(BaseField):
|
|||||||
" saved to the database"
|
" saved to the database"
|
||||||
)
|
)
|
||||||
|
|
||||||
def dereference(self, value):
|
|
||||||
doc_cls = get_document(value["_cls"])
|
|
||||||
reference = value["_ref"]
|
|
||||||
doc = doc_cls._get_db().dereference(reference)
|
|
||||||
if doc is not None:
|
|
||||||
doc = doc_cls._from_son(doc)
|
|
||||||
return doc
|
|
||||||
|
|
||||||
def to_mongo(self, document):
|
def to_mongo(self, document):
|
||||||
if document is None:
|
if document is None:
|
||||||
return None
|
return None
|
||||||
|
@ -8,6 +8,8 @@ from mongoengine.connection import get_connection
|
|||||||
# get_mongodb_version()
|
# get_mongodb_version()
|
||||||
MONGODB_34 = (3, 4)
|
MONGODB_34 = (3, 4)
|
||||||
MONGODB_36 = (3, 6)
|
MONGODB_36 = (3, 6)
|
||||||
|
MONGODB_42 = (4, 2)
|
||||||
|
MONGODB_44 = (4, 4)
|
||||||
|
|
||||||
|
|
||||||
def get_mongodb_version():
|
def get_mongodb_version():
|
||||||
|
@ -1355,21 +1355,18 @@ class BaseQuerySet:
|
|||||||
|
|
||||||
MapReduceDocument = _import_class("MapReduceDocument")
|
MapReduceDocument = _import_class("MapReduceDocument")
|
||||||
|
|
||||||
if not hasattr(self._collection, "map_reduce"):
|
|
||||||
raise NotImplementedError("Requires MongoDB >= 1.7.1")
|
|
||||||
|
|
||||||
map_f_scope = {}
|
map_f_scope = {}
|
||||||
if isinstance(map_f, Code):
|
if isinstance(map_f, Code):
|
||||||
map_f_scope = map_f.scope
|
map_f_scope = map_f.scope
|
||||||
map_f = str(map_f)
|
map_f = str(map_f)
|
||||||
map_f = Code(queryset._sub_js_fields(map_f), map_f_scope)
|
map_f = Code(queryset._sub_js_fields(map_f), map_f_scope or None)
|
||||||
|
|
||||||
reduce_f_scope = {}
|
reduce_f_scope = {}
|
||||||
if isinstance(reduce_f, Code):
|
if isinstance(reduce_f, Code):
|
||||||
reduce_f_scope = reduce_f.scope
|
reduce_f_scope = reduce_f.scope
|
||||||
reduce_f = str(reduce_f)
|
reduce_f = str(reduce_f)
|
||||||
reduce_f_code = queryset._sub_js_fields(reduce_f)
|
reduce_f_code = queryset._sub_js_fields(reduce_f)
|
||||||
reduce_f = Code(reduce_f_code, reduce_f_scope)
|
reduce_f = Code(reduce_f_code, reduce_f_scope or None)
|
||||||
|
|
||||||
mr_args = {"query": queryset._query}
|
mr_args = {"query": queryset._query}
|
||||||
|
|
||||||
@ -1379,7 +1376,7 @@ class BaseQuerySet:
|
|||||||
finalize_f_scope = finalize_f.scope
|
finalize_f_scope = finalize_f.scope
|
||||||
finalize_f = str(finalize_f)
|
finalize_f = str(finalize_f)
|
||||||
finalize_f_code = queryset._sub_js_fields(finalize_f)
|
finalize_f_code = queryset._sub_js_fields(finalize_f)
|
||||||
finalize_f = Code(finalize_f_code, finalize_f_scope)
|
finalize_f = Code(finalize_f_code, finalize_f_scope or None)
|
||||||
mr_args["finalize"] = finalize_f
|
mr_args["finalize"] = finalize_f
|
||||||
|
|
||||||
if scope:
|
if scope:
|
||||||
|
@ -7,6 +7,7 @@ import pytest
|
|||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
from mongoengine.connection import get_db
|
from mongoengine.connection import get_db
|
||||||
|
from mongoengine.mongodb_support import MONGODB_42, get_mongodb_version
|
||||||
|
|
||||||
|
|
||||||
class TestIndexes(unittest.TestCase):
|
class TestIndexes(unittest.TestCase):
|
||||||
@ -452,9 +453,11 @@ class TestIndexes(unittest.TestCase):
|
|||||||
.get("stage")
|
.get("stage")
|
||||||
== "IXSCAN"
|
== "IXSCAN"
|
||||||
)
|
)
|
||||||
|
mongo_db = get_mongodb_version()
|
||||||
|
PROJECTION_STR = "PROJECTION" if mongo_db < MONGODB_42 else "PROJECTION_COVERED"
|
||||||
assert (
|
assert (
|
||||||
query_plan.get("queryPlanner").get("winningPlan").get("stage")
|
query_plan.get("queryPlanner").get("winningPlan").get("stage")
|
||||||
== "PROJECTION"
|
== PROJECTION_STR
|
||||||
)
|
)
|
||||||
|
|
||||||
query_plan = Test.objects(a=1).explain()
|
query_plan = Test.objects(a=1).explain()
|
||||||
|
@ -21,7 +21,10 @@ from mongoengine.queryset import (
|
|||||||
QuerySetManager,
|
QuerySetManager,
|
||||||
queryset_manager,
|
queryset_manager,
|
||||||
)
|
)
|
||||||
from tests.utils import requires_mongodb_gte_44
|
from tests.utils import (
|
||||||
|
requires_mongodb_gte_44,
|
||||||
|
requires_mongodb_lt_42,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class db_ops_tracker(query_counter):
|
class db_ops_tracker(query_counter):
|
||||||
@ -1490,6 +1493,7 @@ class TestQueryset(unittest.TestCase):
|
|||||||
|
|
||||||
BlogPost.drop_collection()
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
|
@requires_mongodb_lt_42
|
||||||
def test_exec_js_query(self):
|
def test_exec_js_query(self):
|
||||||
"""Ensure that queries are properly formed for use in exec_js."""
|
"""Ensure that queries are properly formed for use in exec_js."""
|
||||||
|
|
||||||
@ -1527,6 +1531,7 @@ class TestQueryset(unittest.TestCase):
|
|||||||
|
|
||||||
BlogPost.drop_collection()
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
|
@requires_mongodb_lt_42
|
||||||
def test_exec_js_field_sub(self):
|
def test_exec_js_field_sub(self):
|
||||||
"""Ensure that field substitutions occur properly in exec_js functions."""
|
"""Ensure that field substitutions occur properly in exec_js functions."""
|
||||||
|
|
||||||
@ -2660,6 +2665,8 @@ class TestQueryset(unittest.TestCase):
|
|||||||
title = StringField(primary_key=True)
|
title = StringField(primary_key=True)
|
||||||
tags = ListField(StringField())
|
tags = ListField(StringField())
|
||||||
|
|
||||||
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
post1 = BlogPost(title="Post #1", tags=["mongodb", "mongoengine"])
|
post1 = BlogPost(title="Post #1", tags=["mongodb", "mongoengine"])
|
||||||
post2 = BlogPost(title="Post #2", tags=["django", "mongodb"])
|
post2 = BlogPost(title="Post #2", tags=["django", "mongodb"])
|
||||||
post3 = BlogPost(title="Post #3", tags=["hitchcock films"])
|
post3 = BlogPost(title="Post #3", tags=["hitchcock films"])
|
||||||
@ -2688,12 +2695,15 @@ class TestQueryset(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
results = BlogPost.objects.map_reduce(map_f, reduce_f, "myresults")
|
results = BlogPost.objects.order_by("_id").map_reduce(
|
||||||
|
map_f, reduce_f, "myresults2"
|
||||||
|
)
|
||||||
results = list(results)
|
results = list(results)
|
||||||
|
|
||||||
assert results[0].object == post1
|
assert len(results) == 3
|
||||||
assert results[1].object == post2
|
assert results[0].object.id == post1.id
|
||||||
assert results[2].object == post3
|
assert results[1].object.id == post2.id
|
||||||
|
assert results[2].object.id == post3.id
|
||||||
|
|
||||||
BlogPost.drop_collection()
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
@ -2701,7 +2711,6 @@ class TestQueryset(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
Test map/reduce custom output
|
Test map/reduce custom output
|
||||||
"""
|
"""
|
||||||
register_connection("test2", "mongoenginetest2")
|
|
||||||
|
|
||||||
class Family(Document):
|
class Family(Document):
|
||||||
id = IntField(primary_key=True)
|
id = IntField(primary_key=True)
|
||||||
@ -2774,6 +2783,7 @@ class TestQueryset(unittest.TestCase):
|
|||||||
family.persons.push(person);
|
family.persons.push(person);
|
||||||
family.totalAge += person.age;
|
family.totalAge += person.age;
|
||||||
});
|
});
|
||||||
|
family.persons.sort((a, b) => (a.age > b.age))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2802,10 +2812,10 @@ class TestQueryset(unittest.TestCase):
|
|||||||
"_id": 1,
|
"_id": 1,
|
||||||
"value": {
|
"value": {
|
||||||
"persons": [
|
"persons": [
|
||||||
{"age": 21, "name": "Wilson Jr"},
|
|
||||||
{"age": 45, "name": "Wilson Father"},
|
|
||||||
{"age": 40, "name": "Eliana Costa"},
|
|
||||||
{"age": 17, "name": "Tayza Mariana"},
|
{"age": 17, "name": "Tayza Mariana"},
|
||||||
|
{"age": 21, "name": "Wilson Jr"},
|
||||||
|
{"age": 40, "name": "Eliana Costa"},
|
||||||
|
{"age": 45, "name": "Wilson Father"},
|
||||||
],
|
],
|
||||||
"totalAge": 123,
|
"totalAge": 123,
|
||||||
},
|
},
|
||||||
@ -2815,9 +2825,9 @@ class TestQueryset(unittest.TestCase):
|
|||||||
"_id": 2,
|
"_id": 2,
|
||||||
"value": {
|
"value": {
|
||||||
"persons": [
|
"persons": [
|
||||||
|
{"age": 10, "name": "Igor Gabriel"},
|
||||||
{"age": 16, "name": "Isabella Luanna"},
|
{"age": 16, "name": "Isabella Luanna"},
|
||||||
{"age": 36, "name": "Sandra Mara"},
|
{"age": 36, "name": "Sandra Mara"},
|
||||||
{"age": 10, "name": "Igor Gabriel"},
|
|
||||||
],
|
],
|
||||||
"totalAge": 62,
|
"totalAge": 62,
|
||||||
},
|
},
|
||||||
@ -2827,8 +2837,8 @@ class TestQueryset(unittest.TestCase):
|
|||||||
"_id": 3,
|
"_id": 3,
|
||||||
"value": {
|
"value": {
|
||||||
"persons": [
|
"persons": [
|
||||||
{"age": 30, "name": "Arthur WA"},
|
|
||||||
{"age": 25, "name": "Paula Leonel"},
|
{"age": 25, "name": "Paula Leonel"},
|
||||||
|
{"age": 30, "name": "Arthur WA"},
|
||||||
],
|
],
|
||||||
"totalAge": 55,
|
"totalAge": 55,
|
||||||
},
|
},
|
||||||
@ -3109,6 +3119,7 @@ class TestQueryset(unittest.TestCase):
|
|||||||
freq = Person.objects.item_frequencies("city", normalize=True, map_reduce=True)
|
freq = Person.objects.item_frequencies("city", normalize=True, map_reduce=True)
|
||||||
assert freq == {"CRB": 0.5, None: 0.5}
|
assert freq == {"CRB": 0.5, None: 0.5}
|
||||||
|
|
||||||
|
@requires_mongodb_lt_42
|
||||||
def test_item_frequencies_with_null_embedded(self):
|
def test_item_frequencies_with_null_embedded(self):
|
||||||
class Data(EmbeddedDocument):
|
class Data(EmbeddedDocument):
|
||||||
name = StringField()
|
name = StringField()
|
||||||
@ -3137,6 +3148,7 @@ class TestQueryset(unittest.TestCase):
|
|||||||
ot = Person.objects.item_frequencies("extra.tag", map_reduce=True)
|
ot = Person.objects.item_frequencies("extra.tag", map_reduce=True)
|
||||||
assert ot == {None: 1.0, "friend": 1.0}
|
assert ot == {None: 1.0, "friend": 1.0}
|
||||||
|
|
||||||
|
@requires_mongodb_lt_42
|
||||||
def test_item_frequencies_with_0_values(self):
|
def test_item_frequencies_with_0_values(self):
|
||||||
class Test(Document):
|
class Test(Document):
|
||||||
val = IntField()
|
val = IntField()
|
||||||
@ -3151,6 +3163,7 @@ class TestQueryset(unittest.TestCase):
|
|||||||
ot = Test.objects.item_frequencies("val", map_reduce=False)
|
ot = Test.objects.item_frequencies("val", map_reduce=False)
|
||||||
assert ot == {0: 1}
|
assert ot == {0: 1}
|
||||||
|
|
||||||
|
@requires_mongodb_lt_42
|
||||||
def test_item_frequencies_with_False_values(self):
|
def test_item_frequencies_with_False_values(self):
|
||||||
class Test(Document):
|
class Test(Document):
|
||||||
val = BooleanField()
|
val = BooleanField()
|
||||||
@ -3165,6 +3178,7 @@ class TestQueryset(unittest.TestCase):
|
|||||||
ot = Test.objects.item_frequencies("val", map_reduce=False)
|
ot = Test.objects.item_frequencies("val", map_reduce=False)
|
||||||
assert ot == {False: 1}
|
assert ot == {False: 1}
|
||||||
|
|
||||||
|
@requires_mongodb_lt_42
|
||||||
def test_item_frequencies_normalize(self):
|
def test_item_frequencies_normalize(self):
|
||||||
class Test(Document):
|
class Test(Document):
|
||||||
val = IntField()
|
val = IntField()
|
||||||
@ -3551,7 +3565,8 @@ class TestQueryset(unittest.TestCase):
|
|||||||
Book.objects.create(title="The Stories", authors=[mark_twain, john_tolkien])
|
Book.objects.create(title="The Stories", authors=[mark_twain, john_tolkien])
|
||||||
|
|
||||||
authors = Book.objects.distinct("authors")
|
authors = Book.objects.distinct("authors")
|
||||||
assert authors == [mark_twain, john_tolkien]
|
authors_names = {author.name for author in authors}
|
||||||
|
assert authors_names == {mark_twain.name, john_tolkien.name}
|
||||||
|
|
||||||
def test_distinct_ListField_EmbeddedDocumentField_EmbeddedDocumentField(self):
|
def test_distinct_ListField_EmbeddedDocumentField_EmbeddedDocumentField(self):
|
||||||
class Continent(EmbeddedDocument):
|
class Continent(EmbeddedDocument):
|
||||||
@ -3588,7 +3603,8 @@ class TestQueryset(unittest.TestCase):
|
|||||||
assert country_list == [scotland, tibet]
|
assert country_list == [scotland, tibet]
|
||||||
|
|
||||||
continent_list = Book.objects.distinct("authors.country.continent")
|
continent_list = Book.objects.distinct("authors.country.continent")
|
||||||
assert continent_list == [europe, asia]
|
continent_list_names = {c.continent_name for c in continent_list}
|
||||||
|
assert continent_list_names == {europe.continent_name, asia.continent_name}
|
||||||
|
|
||||||
def test_distinct_ListField_ReferenceField(self):
|
def test_distinct_ListField_ReferenceField(self):
|
||||||
class Bar(Document):
|
class Bar(Document):
|
||||||
|
@ -34,6 +34,10 @@ def get_as_pymongo(doc):
|
|||||||
return doc.__class__.objects.as_pymongo().get(id=doc.id)
|
return doc.__class__.objects.as_pymongo().get(id=doc.id)
|
||||||
|
|
||||||
|
|
||||||
|
def requires_mongodb_lt_42(func):
|
||||||
|
return _decorated_with_ver_requirement(func, (4, 2), oper=operator.lt)
|
||||||
|
|
||||||
|
|
||||||
def requires_mongodb_gte_44(func):
|
def requires_mongodb_gte_44(func):
|
||||||
return _decorated_with_ver_requirement(func, (4, 4), oper=operator.ge)
|
return _decorated_with_ver_requirement(func, (4, 4), oper=operator.ge)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user