Compare commits
49 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2f4b2aadb4 | ||
|
f4a87bff1d | ||
|
3a32c195d5 | ||
|
880a8efc63 | ||
|
9a414edb25 | ||
|
4b643ad01f | ||
|
811dc12db5 | ||
|
dbd72282bc | ||
|
686b42c29b | ||
|
4210ea06c3 | ||
|
8c3e2b340b | ||
|
7a0a58c163 | ||
|
0af1a1151f | ||
|
28226f81a8 | ||
|
558e3299fe | ||
|
cb0035f87a | ||
|
85b0c1c945 | ||
|
3d6b650592 | ||
|
6bc1b83695 | ||
|
7e10bb2894 | ||
|
8428da368d | ||
|
5fe9436ea7 | ||
|
1b249e336e | ||
|
533700583d | ||
|
639124c311 | ||
|
8b5cf9e2be | ||
|
0af96b1323 | ||
|
f6d864b6d1 | ||
|
bb9ba73a7b | ||
|
66978aea67 | ||
|
5a41998eda | ||
|
c32b308730 | ||
|
88642eb021 | ||
|
3b10236b5e | ||
|
bffe058726 | ||
|
09c415a416 | ||
|
4670508a1c | ||
|
e7f2d77a75 | ||
|
7bf7153e4f | ||
|
0df0585d07 | ||
|
6ae722d04b | ||
|
cc85165b0c | ||
|
2fd4169656 | ||
|
5897b1d042 | ||
|
756bffe868 | ||
|
5209547a89 | ||
|
ecb5dda281 | ||
|
34245fe258 | ||
|
ecf84cbd5d |
2
.github/workflows/github-actions.yml
vendored
2
.github/workflows/github-actions.yml
vendored
@ -44,7 +44,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.6, 3.7, 3.8, 3.9, pypy3]
|
python-version: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3]
|
||||||
MONGODB: [$MONGODB_4_0]
|
MONGODB: [$MONGODB_4_0]
|
||||||
PYMONGO: [$PYMONGO_3_11]
|
PYMONGO: [$PYMONGO_3_11]
|
||||||
include:
|
include:
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
fail_fast: false
|
fail_fast: false
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v3.4.0
|
rev: v4.0.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
- repo: https://github.com/ambv/black
|
- repo: https://github.com/ambv/black
|
||||||
rev: 21.4b2
|
rev: 21.5b2
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
- repo: https://gitlab.com/pycqa/flake8
|
- repo: https://gitlab.com/pycqa/flake8
|
||||||
rev: 3.9.1
|
rev: 3.9.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v2.14.0
|
rev: v2.19.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--py36-plus]
|
args: [--py36-plus]
|
||||||
|
108
.travis_.yml
108
.travis_.yml
@ -1,108 +0,0 @@
|
|||||||
## For full coverage, we'd have to test all supported Python, MongoDB, and
|
|
||||||
## PyMongo combinations. However, that would result in an overly long build
|
|
||||||
## with a very large number of jobs, hence we only test a subset of all the
|
|
||||||
## combinations.
|
|
||||||
## * Python3.7, MongoDB v3.4 & the latest PyMongo v3.x is currently the "main" setup,
|
|
||||||
## Other combinations are tested. See below for the details or check the travis jobs
|
|
||||||
#
|
|
||||||
## We should periodically check MongoDB Server versions supported by MongoDB
|
|
||||||
## Inc., add newly released versions to the test matrix, and remove versions
|
|
||||||
## which have reached their End of Life. See:
|
|
||||||
## 1. https://www.mongodb.com/support-policy.
|
|
||||||
## 2. https://docs.mongodb.com/ecosystem/drivers/driver-compatibility-reference/#python-driver-compatibility
|
|
||||||
##
|
|
||||||
## Reminder: Update README.rst if you change MongoDB versions we test.
|
|
||||||
#
|
|
||||||
#language: python
|
|
||||||
#dist: xenial
|
|
||||||
#python:
|
|
||||||
# - 3.6
|
|
||||||
# - 3.7
|
|
||||||
# - 3.8
|
|
||||||
# - 3.9
|
|
||||||
# - pypy3
|
|
||||||
#
|
|
||||||
#env:
|
|
||||||
# global:
|
|
||||||
# - MONGODB_3_4=3.4.19
|
|
||||||
# - MONGODB_3_6=3.6.13
|
|
||||||
# - MONGODB_4_0=4.0.13
|
|
||||||
#
|
|
||||||
# - PYMONGO_3_4=3.4
|
|
||||||
# - PYMONGO_3_6=3.6
|
|
||||||
# - PYMONGO_3_9=3.9
|
|
||||||
# - PYMONGO_3_11=3.11
|
|
||||||
#
|
|
||||||
# - MAIN_PYTHON_VERSION=3.7
|
|
||||||
# matrix:
|
|
||||||
# - MONGODB=${MONGODB_3_4} PYMONGO=${PYMONGO_3_11}
|
|
||||||
#
|
|
||||||
#matrix:
|
|
||||||
# # Finish the build as soon as one job fails
|
|
||||||
# fast_finish: true
|
|
||||||
#
|
|
||||||
# include:
|
|
||||||
# - python: 3.7
|
|
||||||
# env: MONGODB=${MONGODB_3_6} PYMONGO=${PYMONGO_3_6}
|
|
||||||
# - python: 3.7
|
|
||||||
# env: MONGODB=${MONGODB_3_6} PYMONGO=${PYMONGO_3_9}
|
|
||||||
# - python: 3.7
|
|
||||||
# env: MONGODB=${MONGODB_3_6} PYMONGO=${PYMONGO_3_11}
|
|
||||||
# - python: 3.8
|
|
||||||
# env: MONGODB=${MONGODB_4_0} PYMONGO=${PYMONGO_3_11}
|
|
||||||
#
|
|
||||||
#install:
|
|
||||||
# # Install Mongo
|
|
||||||
# - wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-${MONGODB}.tgz
|
|
||||||
# - tar xzf mongodb-linux-x86_64-${MONGODB}.tgz
|
|
||||||
# - ${PWD}/mongodb-linux-x86_64-${MONGODB}/bin/mongod --version
|
|
||||||
# # Install Python dependencies.
|
|
||||||
# - pip install --upgrade pip
|
|
||||||
# - pip install coveralls
|
|
||||||
# - pip install pre-commit
|
|
||||||
# - pip install tox
|
|
||||||
# # tox dryrun to setup the tox venv (we run a mock test).
|
|
||||||
# - tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- -a "-k=test_ci_placeholder"
|
|
||||||
#
|
|
||||||
#before_script:
|
|
||||||
# - mkdir ${PWD}/mongodb-linux-x86_64-${MONGODB}/data
|
|
||||||
# - ${PWD}/mongodb-linux-x86_64-${MONGODB}/bin/mongod --dbpath ${PWD}/mongodb-linux-x86_64-${MONGODB}/data --logpath ${PWD}/mongodb-linux-x86_64-${MONGODB}/mongodb.log --fork
|
|
||||||
# # Run pre-commit hooks (black, flake8, etc) on entire codebase
|
|
||||||
# - if [[ $TRAVIS_PYTHON_VERSION == $MAIN_PYTHON_VERSION ]]; then pre-commit run -a; else echo "pre-commit checks only runs on py37"; fi
|
|
||||||
# - mongo --eval 'db.version();' # Make sure mongo is awake
|
|
||||||
#
|
|
||||||
#script:
|
|
||||||
# - tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- -a "--cov=mongoengine"
|
|
||||||
#
|
|
||||||
#after_success:
|
|
||||||
# - if [[ $TRAVIS_PYTHON_VERSION == $MAIN_PYTHON_VERSION ]]; then coveralls --verbose; else echo "coveralls only sent for py37"; fi
|
|
||||||
#
|
|
||||||
#notifications:
|
|
||||||
# irc: irc.freenode.org#mongoengine
|
|
||||||
#
|
|
||||||
## Only run builds on the master branch and GitHub releases (tagged as vX.Y.Z)
|
|
||||||
#branches:
|
|
||||||
# # Only run builds on the master branch and GitHub releases (tagged as vX.Y.Z)
|
|
||||||
# only:
|
|
||||||
# - master
|
|
||||||
# - /^v.*$/
|
|
||||||
#
|
|
||||||
## Whenever a new release is created via GitHub, publish it on PyPI.
|
|
||||||
#deploy:
|
|
||||||
# provider: pypi
|
|
||||||
# user: the_drow
|
|
||||||
# password:
|
|
||||||
# secure: QMyatmWBnC6ZN3XLW2+fTBDU4LQcp1m/LjR2/0uamyeUzWKdlOoh/Wx5elOgLwt/8N9ppdPeG83ose1jOz69l5G0MUMjv8n/RIcMFSpCT59tGYqn3kh55b0cIZXFT9ar+5cxlif6a5rS72IHm5li7QQyxexJIII6Uxp0kpvUmek=
|
|
||||||
#
|
|
||||||
# # Create a source distribution and a pure python wheel for faster installs.
|
|
||||||
# distributions: "sdist bdist_wheel"
|
|
||||||
#
|
|
||||||
# # Only deploy on tagged commits (aka GitHub releases) and only for the parent
|
|
||||||
# # repo's builds running Python v3.7 along with PyMongo v3.x and MongoDB v3.4.
|
|
||||||
# # We run Travis against many different Python, PyMongo, and MongoDB versions
|
|
||||||
# # and we don't want the deploy to occur multiple times).
|
|
||||||
# on:
|
|
||||||
# tags: true
|
|
||||||
# repo: MongoEngine/mongoengine
|
|
||||||
# condition: ($PYMONGO = ${PYMONGO_3_11}) && ($MONGODB = ${MONGODB_3_4})
|
|
||||||
# python: 3.7
|
|
3
AUTHORS
3
AUTHORS
@ -260,3 +260,6 @@ that much better:
|
|||||||
* Stankiewicz Mateusz (https://github.com/mas15)
|
* Stankiewicz Mateusz (https://github.com/mas15)
|
||||||
* Felix Schultheiß (https://github.com/felix-smashdocs)
|
* Felix Schultheiß (https://github.com/felix-smashdocs)
|
||||||
* Jan Stein (https://github.com/janste63)
|
* Jan Stein (https://github.com/janste63)
|
||||||
|
* Timothé Perez (https://github.com/AchilleAsh)
|
||||||
|
* oleksandr-l5 (https://github.com/oleksandr-l5)
|
||||||
|
* Ido Shraga (https://github.com/idoshr)
|
||||||
|
@ -75,6 +75,7 @@ Fields
|
|||||||
.. autoclass:: mongoengine.fields.StringField
|
.. autoclass:: mongoengine.fields.StringField
|
||||||
.. autoclass:: mongoengine.fields.URLField
|
.. autoclass:: mongoengine.fields.URLField
|
||||||
.. autoclass:: mongoengine.fields.EmailField
|
.. autoclass:: mongoengine.fields.EmailField
|
||||||
|
.. autoclass:: mongoengine.fields.EnumField
|
||||||
.. autoclass:: mongoengine.fields.IntField
|
.. autoclass:: mongoengine.fields.IntField
|
||||||
.. autoclass:: mongoengine.fields.LongField
|
.. autoclass:: mongoengine.fields.LongField
|
||||||
.. autoclass:: mongoengine.fields.FloatField
|
.. autoclass:: mongoengine.fields.FloatField
|
||||||
|
@ -7,6 +7,10 @@ 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).
|
||||||
|
- EnumField improvements: now `choices` limits the values of an enum to allow
|
||||||
|
- Fix deepcopy of EmbeddedDocument #2202
|
||||||
|
- Fix error when using precision=0 with DecimalField #2535
|
||||||
|
- Add support for regex and whole word text search query #2568
|
||||||
|
|
||||||
Changes in 0.23.1
|
Changes in 0.23.1
|
||||||
===========
|
===========
|
||||||
|
@ -27,6 +27,8 @@ objects** as class attributes to the document class::
|
|||||||
As BSON (the binary format for storing data in mongodb) is order dependent,
|
As BSON (the binary format for storing data in mongodb) is order dependent,
|
||||||
documents are serialized based on their field order.
|
documents are serialized based on their field order.
|
||||||
|
|
||||||
|
.. _dynamic-document-schemas:
|
||||||
|
|
||||||
Dynamic document schemas
|
Dynamic document schemas
|
||||||
========================
|
========================
|
||||||
One of the benefits of MongoDB is dynamic schemas for a collection, whilst data
|
One of the benefits of MongoDB is dynamic schemas for a collection, whilst data
|
||||||
@ -231,6 +233,9 @@ document class as the first argument::
|
|||||||
comment2 = Comment(content='Nice article!')
|
comment2 = Comment(content='Nice article!')
|
||||||
page = Page(comments=[comment1, comment2])
|
page = Page(comments=[comment1, comment2])
|
||||||
|
|
||||||
|
Embedded documents can also leverage the flexibility of :ref:`dynamic-document-schemas:`
|
||||||
|
by inheriting :class:`~mongoengine.DynamicEmbeddedDocument`.
|
||||||
|
|
||||||
Dictionary Fields
|
Dictionary Fields
|
||||||
-----------------
|
-----------------
|
||||||
Often, an embedded document may be used instead of a dictionary – generally
|
Often, an embedded document may be used instead of a dictionary – generally
|
||||||
@ -336,7 +341,6 @@ supplying the :attr:`reverse_delete_rule` attributes on the
|
|||||||
:class:`ReferenceField` definition, like this::
|
:class:`ReferenceField` definition, like this::
|
||||||
|
|
||||||
class ProfilePage(Document):
|
class ProfilePage(Document):
|
||||||
...
|
|
||||||
employee = ReferenceField('Employee', reverse_delete_rule=mongoengine.CASCADE)
|
employee = ReferenceField('Employee', reverse_delete_rule=mongoengine.CASCADE)
|
||||||
|
|
||||||
The declaration in this example means that when an :class:`Employee` object is
|
The declaration in this example means that when an :class:`Employee` object is
|
||||||
@ -473,7 +477,7 @@ dictionary containing a full index definition.
|
|||||||
|
|
||||||
A direction may be specified on fields by prefixing the field name with a
|
A direction may be specified on fields by prefixing the field name with a
|
||||||
**+** (for ascending) or a **-** sign (for descending). Note that direction
|
**+** (for ascending) or a **-** sign (for descending). Note that direction
|
||||||
only matters on multi-field indexes. Text indexes may be specified by prefixing
|
only matters on compound indexes. Text indexes may be specified by prefixing
|
||||||
the field name with a **$**. Hashed indexes may be specified by prefixing
|
the field name with a **$**. Hashed indexes may be specified by prefixing
|
||||||
the field name with a **#**::
|
the field name with a **#**::
|
||||||
|
|
||||||
@ -484,14 +488,14 @@ the field name with a **#**::
|
|||||||
created = DateTimeField()
|
created = DateTimeField()
|
||||||
meta = {
|
meta = {
|
||||||
'indexes': [
|
'indexes': [
|
||||||
'title',
|
'title', # single-field index
|
||||||
'$title', # text index
|
'$title', # text index
|
||||||
'#title', # hashed index
|
'#title', # hashed index
|
||||||
('title', '-rating'),
|
('title', '-rating'), # compound index
|
||||||
('category', '_cls'),
|
('category', '_cls'), # compound index
|
||||||
{
|
{
|
||||||
'fields': ['created'],
|
'fields': ['created'],
|
||||||
'expireAfterSeconds': 3600
|
'expireAfterSeconds': 3600 # ttl index
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -624,8 +628,8 @@ point. To create a geospatial index you must prefix the field with the
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
Time To Live indexes
|
Time To Live (TTL) indexes
|
||||||
--------------------
|
--------------------------
|
||||||
|
|
||||||
A special index type that allows you to automatically expire data from a
|
A special index type that allows you to automatically expire data from a
|
||||||
collection after a given period. See the official
|
collection after a given period. See the official
|
||||||
|
@ -223,6 +223,47 @@ it is often useful for complex migrations of Document models.
|
|||||||
|
|
||||||
.. warning:: Be aware of this `flaw <https://groups.google.com/g/mongodb-user/c/AFC1ia7MHzk>`_ if you modify documents while iterating
|
.. warning:: Be aware of this `flaw <https://groups.google.com/g/mongodb-user/c/AFC1ia7MHzk>`_ if you modify documents while iterating
|
||||||
|
|
||||||
|
Example 4: Index removal
|
||||||
|
========================
|
||||||
|
|
||||||
|
If you remove an index from your Document class, or remove an indexed Field from your Document class,
|
||||||
|
you'll need to manually drop the corresponding index. MongoEngine will not do that for you.
|
||||||
|
|
||||||
|
The way to deal with this case is to identify the name of the index to drop with `index_information()`, and then drop
|
||||||
|
it with `drop_index()`
|
||||||
|
|
||||||
|
Let's for instance assume that you start with the following Document class
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class User(Document):
|
||||||
|
name = StringField(index=True)
|
||||||
|
|
||||||
|
meta = {"indexes": ["name"]}
|
||||||
|
|
||||||
|
User(name="John Doe").save()
|
||||||
|
|
||||||
|
As soon as you start interacting with the Document collection (when `.save()` is called in this case),
|
||||||
|
it would create the following indexes:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
print(User._get_collection().index_information())
|
||||||
|
# {
|
||||||
|
# '_id_': {'key': [('_id', 1)], 'v': 2},
|
||||||
|
# 'name_1': {'background': False, 'key': [('name', 1)], 'v': 2},
|
||||||
|
# }
|
||||||
|
|
||||||
|
Thus: '_id' which is the default index and 'name_1' which is our custom index.
|
||||||
|
If you would remove the 'name' field or its index, you would have to call:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
User._get_collection().drop_index('name_1')
|
||||||
|
|
||||||
|
.. note:: When adding new fields or new indexes, MongoEngine will take care of creating them
|
||||||
|
(unless `auto_create_index` is disabled) ::
|
||||||
|
|
||||||
Recommendations
|
Recommendations
|
||||||
===============
|
===============
|
||||||
|
|
||||||
|
@ -86,6 +86,10 @@ expressions:
|
|||||||
* ``istartswith`` -- string field starts with value (case insensitive)
|
* ``istartswith`` -- string field starts with value (case insensitive)
|
||||||
* ``endswith`` -- string field ends with value
|
* ``endswith`` -- string field ends with value
|
||||||
* ``iendswith`` -- string field ends with value (case insensitive)
|
* ``iendswith`` -- string field ends with value (case insensitive)
|
||||||
|
* ``wholeword`` -- string field contains whole word
|
||||||
|
* ``iwholeword`` -- string field contains whole word (case insensitive)
|
||||||
|
* ``regex`` -- string field match by regex
|
||||||
|
* ``iregex`` -- string field match by regex (case insensitive)
|
||||||
* ``match`` -- performs an $elemMatch so you can match an entire document within an array
|
* ``match`` -- performs an $elemMatch so you can match an entire document within an array
|
||||||
|
|
||||||
|
|
||||||
@ -239,7 +243,7 @@ Limiting and skipping results
|
|||||||
Just as with traditional ORMs, you may limit the number of results returned or
|
Just as with traditional ORMs, you may limit the number of results returned or
|
||||||
skip a number or results in you query.
|
skip a number or results in you query.
|
||||||
:meth:`~mongoengine.queryset.QuerySet.limit` and
|
:meth:`~mongoengine.queryset.QuerySet.limit` and
|
||||||
:meth:`~mongoengine.queryset.QuerySet.skip` and methods are available on
|
:meth:`~mongoengine.queryset.QuerySet.skip` methods are available on
|
||||||
:class:`~mongoengine.queryset.QuerySet` objects, but the `array-slicing` syntax
|
:class:`~mongoengine.queryset.QuerySet` objects, but the `array-slicing` syntax
|
||||||
is preferred for achieving this::
|
is preferred for achieving this::
|
||||||
|
|
||||||
@ -543,7 +547,10 @@ Documents may be updated atomically by using the
|
|||||||
There are several different "modifiers" that you may use with these methods:
|
There are several different "modifiers" that you may use with these methods:
|
||||||
|
|
||||||
* ``set`` -- set a particular value
|
* ``set`` -- set a particular value
|
||||||
|
* ``set_on_insert`` -- set only if this is new document `need to add upsert=True`_
|
||||||
* ``unset`` -- delete a particular value (since MongoDB v1.3)
|
* ``unset`` -- delete a particular value (since MongoDB v1.3)
|
||||||
|
* ``max`` -- update only if value is bigger
|
||||||
|
* ``min`` -- update only if value is smaller
|
||||||
* ``inc`` -- increment a value by a given amount
|
* ``inc`` -- increment a value by a given amount
|
||||||
* ``dec`` -- decrement a value by a given amount
|
* ``dec`` -- decrement a value by a given amount
|
||||||
* ``push`` -- append a value to a list
|
* ``push`` -- append a value to a list
|
||||||
@ -552,6 +559,7 @@ There are several different "modifiers" that you may use with these methods:
|
|||||||
* ``pull`` -- remove a value from a list
|
* ``pull`` -- remove a value from a list
|
||||||
* ``pull_all`` -- remove several values from a list
|
* ``pull_all`` -- remove several values from a list
|
||||||
* ``add_to_set`` -- add value to a list only if its not in the list already
|
* ``add_to_set`` -- add value to a list only if its not in the list already
|
||||||
|
* ``rename`` -- rename the key name
|
||||||
|
|
||||||
.. _depending on the value: http://docs.mongodb.org/manual/reference/operator/update/pop/
|
.. _depending on the value: http://docs.mongodb.org/manual/reference/operator/update/pop/
|
||||||
|
|
||||||
|
@ -58,20 +58,20 @@ def _get_connection_settings(
|
|||||||
):
|
):
|
||||||
"""Get the connection settings as a dict
|
"""Get the connection settings as a dict
|
||||||
|
|
||||||
: param db: the name of the database to use, for compatibility with connect
|
:param db: the name of the database to use, for compatibility with connect
|
||||||
: param name: the name of the specific database to use
|
:param name: the name of the specific database to use
|
||||||
: param host: the host name of the: program: `mongod` instance to connect to
|
:param host: the host name of the: program: `mongod` instance to connect to
|
||||||
: param port: the port that the: program: `mongod` instance is running on
|
:param port: the port that the: program: `mongod` instance is running on
|
||||||
: param read_preference: The read preference for the collection
|
:param read_preference: The read preference for the collection
|
||||||
: param username: username to authenticate with
|
:param username: username to authenticate with
|
||||||
: param password: password to authenticate with
|
:param password: password to authenticate with
|
||||||
: param authentication_source: database to authenticate against
|
:param authentication_source: database to authenticate against
|
||||||
: param authentication_mechanism: database authentication mechanisms.
|
:param authentication_mechanism: database authentication mechanisms.
|
||||||
By default, use SCRAM-SHA-1 with MongoDB 3.0 and later,
|
By default, use SCRAM-SHA-1 with MongoDB 3.0 and later,
|
||||||
MONGODB-CR (MongoDB Challenge Response protocol) for older servers.
|
MONGODB-CR (MongoDB Challenge Response protocol) for older servers.
|
||||||
: param is_mock: explicitly use mongomock for this connection
|
:param is_mock: explicitly use mongomock for this connection
|
||||||
(can also be done by using `mongomock: // ` as db host prefix)
|
(can also be done by using `mongomock: // ` as db host prefix)
|
||||||
: param kwargs: ad-hoc parameters to be passed into the pymongo driver,
|
:param kwargs: ad-hoc parameters to be passed into the pymongo driver,
|
||||||
for example maxpoolsize, tz_aware, etc. See the documentation
|
for example maxpoolsize, tz_aware, etc. See the documentation
|
||||||
for pymongo's `MongoClient` for a full list.
|
for pymongo's `MongoClient` for a full list.
|
||||||
"""
|
"""
|
||||||
@ -181,22 +181,21 @@ def register_connection(
|
|||||||
):
|
):
|
||||||
"""Register the connection settings.
|
"""Register the connection settings.
|
||||||
|
|
||||||
: param alias: the name that will be used to refer to this connection
|
:param alias: the name that will be used to refer to this connection throughout MongoEngine
|
||||||
throughout MongoEngine
|
:param db: the name of the database to use, for compatibility with connect
|
||||||
: param db: the name of the database to use, for compatibility with connect
|
:param name: the name of the specific database to use
|
||||||
: param name: the name of the specific database to use
|
:param host: the host name of the: program: `mongod` instance to connect to
|
||||||
: param host: the host name of the: program: `mongod` instance to connect to
|
:param port: the port that the: program: `mongod` instance is running on
|
||||||
: param port: the port that the: program: `mongod` instance is running on
|
:param read_preference: The read preference for the collection
|
||||||
: param read_preference: The read preference for the collection
|
:param username: username to authenticate with
|
||||||
: param username: username to authenticate with
|
:param password: password to authenticate with
|
||||||
: param password: password to authenticate with
|
:param authentication_source: database to authenticate against
|
||||||
: param authentication_source: database to authenticate against
|
:param authentication_mechanism: database authentication mechanisms.
|
||||||
: param authentication_mechanism: database authentication mechanisms.
|
|
||||||
By default, use SCRAM-SHA-1 with MongoDB 3.0 and later,
|
By default, use SCRAM-SHA-1 with MongoDB 3.0 and later,
|
||||||
MONGODB-CR (MongoDB Challenge Response protocol) for older servers.
|
MONGODB-CR (MongoDB Challenge Response protocol) for older servers.
|
||||||
: param is_mock: explicitly use mongomock for this connection
|
:param is_mock: explicitly use mongomock for this connection
|
||||||
(can also be done by using `mongomock: // ` as db host prefix)
|
(can also be done by using `mongomock: // ` as db host prefix)
|
||||||
: param kwargs: ad-hoc parameters to be passed into the pymongo driver,
|
:param kwargs: ad-hoc parameters to be passed into the pymongo driver,
|
||||||
for example maxpoolsize, tz_aware, etc. See the documentation
|
for example maxpoolsize, tz_aware, etc. See the documentation
|
||||||
for pymongo's `MongoClient` for a full list.
|
for pymongo's `MongoClient` for a full list.
|
||||||
"""
|
"""
|
||||||
|
@ -177,14 +177,28 @@ class query_counter:
|
|||||||
This was designed for debugging purpose. In fact it is a global counter so queries issued by other threads/processes
|
This was designed for debugging purpose. In fact it is a global counter so queries issued by other threads/processes
|
||||||
can interfere with it
|
can interfere with it
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class User(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
with query_counter() as q:
|
||||||
|
user = User(name='Bob')
|
||||||
|
assert q == 0 # no query fired yet
|
||||||
|
user.save()
|
||||||
|
assert q == 1 # 1 query was fired, an 'insert'
|
||||||
|
user_bis = User.objects().first()
|
||||||
|
assert q == 2 # a 2nd query was fired, a 'find_one'
|
||||||
|
|
||||||
Be aware that:
|
Be aware that:
|
||||||
- Iterating over large amount of documents (>101) makes pymongo issue `getmore` queries to fetch the next batch of
|
|
||||||
documents (https://docs.mongodb.com/manual/tutorial/iterate-a-cursor/#cursor-batches)
|
- Iterating over large amount of documents (>101) makes pymongo issue `getmore` queries to fetch the next batch of documents (https://docs.mongodb.com/manual/tutorial/iterate-a-cursor/#cursor-batches)
|
||||||
- Some queries are ignored by default by the counter (killcursors, db.system.indexes)
|
- Some queries are ignored by default by the counter (killcursors, db.system.indexes)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, alias=DEFAULT_CONNECTION_NAME):
|
def __init__(self, alias=DEFAULT_CONNECTION_NAME):
|
||||||
"""Construct the query_counter"""
|
|
||||||
self.db = get_db(alias=alias)
|
self.db = get_db(alias=alias)
|
||||||
self.initial_profiling_level = None
|
self.initial_profiling_level = None
|
||||||
self._ctx_query_counter = 0 # number of queries issued by the context
|
self._ctx_query_counter = 0 # number of queries issued by the context
|
||||||
|
@ -99,6 +99,15 @@ class EmbeddedDocument(BaseDocument, metaclass=DocumentMetaclass):
|
|||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
data = super().__getstate__()
|
||||||
|
data["_instance"] = None
|
||||||
|
return data
|
||||||
|
|
||||||
|
def __setstate__(self, state):
|
||||||
|
super().__setstate__(state)
|
||||||
|
self._instance = state["_instance"]
|
||||||
|
|
||||||
def to_mongo(self, *args, **kwargs):
|
def to_mongo(self, *args, **kwargs):
|
||||||
data = super().to_mongo(*args, **kwargs)
|
data = super().to_mongo(*args, **kwargs)
|
||||||
|
|
||||||
@ -126,7 +135,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
|
|||||||
create a specialised version of the document that will be stored in the
|
create a specialised version of the document that will be stored in the
|
||||||
same collection. To facilitate this behaviour a `_cls`
|
same collection. To facilitate this behaviour a `_cls`
|
||||||
field is added to documents (hidden though the MongoEngine interface).
|
field is added to documents (hidden though the MongoEngine interface).
|
||||||
To enable this behaviourset :attr:`allow_inheritance` to ``True`` in the
|
To enable this behaviour set :attr:`allow_inheritance` to ``True`` in the
|
||||||
:attr:`meta` dictionary.
|
:attr:`meta` dictionary.
|
||||||
|
|
||||||
A :class:`~mongoengine.Document` may use a **Capped Collection** by
|
A :class:`~mongoengine.Document` may use a **Capped Collection** by
|
||||||
@ -574,7 +583,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
|
|||||||
def _qs(self):
|
def _qs(self):
|
||||||
"""Return the default queryset corresponding to this document."""
|
"""Return the default queryset corresponding to this document."""
|
||||||
if not hasattr(self, "__objects"):
|
if not hasattr(self, "__objects"):
|
||||||
self.__objects = QuerySet(self, self._get_collection())
|
self.__objects = QuerySet(self.__class__, self._get_collection())
|
||||||
return self.__objects
|
return self.__objects
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -866,6 +875,10 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
|
|||||||
|
|
||||||
Global defaults can be set in the meta - see :doc:`guide/defining-documents`
|
Global defaults can be set in the meta - see :doc:`guide/defining-documents`
|
||||||
|
|
||||||
|
By default, this will get called automatically upon first interaction with the
|
||||||
|
Document collection (query, save, etc) so unless you disabled `auto_create_index`, you
|
||||||
|
shouldn't have to call this manually.
|
||||||
|
|
||||||
.. note:: You can disable automatic index creation by setting
|
.. note:: You can disable automatic index creation by setting
|
||||||
`auto_create_index` to False in the documents meta data
|
`auto_create_index` to False in the documents meta data
|
||||||
"""
|
"""
|
||||||
@ -915,8 +928,10 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def list_indexes(cls):
|
def list_indexes(cls):
|
||||||
"""Lists all of the indexes that should be created for given
|
"""Lists all indexes that should be created for the Document collection.
|
||||||
collection. It includes all the indexes from super- and sub-classes.
|
It includes all the indexes from super- and sub-classes.
|
||||||
|
|
||||||
|
Note that it will only return the indexes' fields, not the indexes' options
|
||||||
"""
|
"""
|
||||||
if cls._meta.get("abstract"):
|
if cls._meta.get("abstract"):
|
||||||
return []
|
return []
|
||||||
|
@ -17,11 +17,15 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class NotRegistered(Exception):
|
class MongoEngineException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidDocumentError(Exception):
|
class NotRegistered(MongoEngineException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidDocumentError(MongoEngineException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -29,19 +33,19 @@ class LookUpError(AttributeError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DoesNotExist(Exception):
|
class DoesNotExist(MongoEngineException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MultipleObjectsReturned(Exception):
|
class MultipleObjectsReturned(MongoEngineException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidQueryError(Exception):
|
class InvalidQueryError(MongoEngineException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class OperationError(Exception):
|
class OperationError(MongoEngineException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -57,7 +61,7 @@ class SaveConditionError(OperationError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FieldDoesNotExist(Exception):
|
class FieldDoesNotExist(MongoEngineException):
|
||||||
"""Raised when trying to set a field
|
"""Raised when trying to set a field
|
||||||
not declared in a :class:`~mongoengine.Document`
|
not declared in a :class:`~mongoengine.Document`
|
||||||
or an :class:`~mongoengine.EmbeddedDocument`.
|
or an :class:`~mongoengine.EmbeddedDocument`.
|
||||||
@ -155,7 +159,7 @@ class ValidationError(AssertionError):
|
|||||||
return " ".join([f"{k}: {v}" for k, v in error_dict.items()])
|
return " ".join([f"{k}: {v}" for k, v in error_dict.items()])
|
||||||
|
|
||||||
|
|
||||||
class DeprecatedError(Exception):
|
class DeprecatedError(MongoEngineException):
|
||||||
"""Raise when a user uses a feature that has been Deprecated"""
|
"""Raise when a user uses a feature that has been Deprecated"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
@ -157,10 +157,17 @@ class StringField(BaseField):
|
|||||||
regex = r"%s$"
|
regex = r"%s$"
|
||||||
elif op == "exact":
|
elif op == "exact":
|
||||||
regex = r"^%s$"
|
regex = r"^%s$"
|
||||||
|
elif op == "wholeword":
|
||||||
|
regex = r"\b%s\b"
|
||||||
|
elif op == "regex":
|
||||||
|
regex = value
|
||||||
|
|
||||||
# escape unsafe characters which could lead to a re.error
|
if op == "regex":
|
||||||
value = re.escape(value)
|
value = re.compile(regex, flags)
|
||||||
value = re.compile(regex % value, flags)
|
else:
|
||||||
|
# escape unsafe characters which could lead to a re.error
|
||||||
|
value = re.escape(value)
|
||||||
|
value = re.compile(regex % value, flags)
|
||||||
return super().prepare_query_value(op, value)
|
return super().prepare_query_value(op, value)
|
||||||
|
|
||||||
|
|
||||||
@ -468,6 +475,10 @@ class DecimalField(BaseField):
|
|||||||
self.min_value = min_value
|
self.min_value = min_value
|
||||||
self.max_value = max_value
|
self.max_value = max_value
|
||||||
self.force_string = force_string
|
self.force_string = force_string
|
||||||
|
|
||||||
|
if precision < 0 or not isinstance(precision, int):
|
||||||
|
self.error("precision must be a positive integer")
|
||||||
|
|
||||||
self.precision = precision
|
self.precision = precision
|
||||||
self.rounding = rounding
|
self.rounding = rounding
|
||||||
|
|
||||||
@ -482,9 +493,12 @@ class DecimalField(BaseField):
|
|||||||
value = decimal.Decimal("%s" % value)
|
value = decimal.Decimal("%s" % value)
|
||||||
except (TypeError, ValueError, decimal.InvalidOperation):
|
except (TypeError, ValueError, decimal.InvalidOperation):
|
||||||
return value
|
return value
|
||||||
return value.quantize(
|
if self.precision > 0:
|
||||||
decimal.Decimal(".%s" % ("0" * self.precision)), rounding=self.rounding
|
return value.quantize(
|
||||||
)
|
decimal.Decimal(".%s" % ("0" * self.precision)), rounding=self.rounding
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return value.quantize(decimal.Decimal(), rounding=self.rounding)
|
||||||
|
|
||||||
def to_mongo(self, value):
|
def to_mongo(self, value):
|
||||||
if value is None:
|
if value is None:
|
||||||
@ -1079,16 +1093,7 @@ class DictField(ComplexBaseField):
|
|||||||
return DictField(db_field=member_name)
|
return DictField(db_field=member_name)
|
||||||
|
|
||||||
def prepare_query_value(self, op, value):
|
def prepare_query_value(self, op, value):
|
||||||
match_operators = [
|
match_operators = [*STRING_OPERATORS]
|
||||||
"contains",
|
|
||||||
"icontains",
|
|
||||||
"startswith",
|
|
||||||
"istartswith",
|
|
||||||
"endswith",
|
|
||||||
"iendswith",
|
|
||||||
"exact",
|
|
||||||
"iexact",
|
|
||||||
]
|
|
||||||
|
|
||||||
if op in match_operators and isinstance(value, str):
|
if op in match_operators and isinstance(value, str):
|
||||||
return StringField().prepare_query_value(op, value)
|
return StringField().prepare_query_value(op, value)
|
||||||
@ -1611,11 +1616,14 @@ class EnumField(BaseField):
|
|||||||
"""Enumeration Field. Values are stored underneath as is,
|
"""Enumeration Field. Values are stored underneath as is,
|
||||||
so it will only work with simple types (str, int, etc) that
|
so it will only work with simple types (str, int, etc) that
|
||||||
are bson encodable
|
are bson encodable
|
||||||
Example usage:
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
class Status(Enum):
|
class Status(Enum):
|
||||||
NEW = 'new'
|
NEW = 'new'
|
||||||
|
ONGOING = 'ongoing'
|
||||||
DONE = 'done'
|
DONE = 'done'
|
||||||
|
|
||||||
class ModelWithEnum(Document):
|
class ModelWithEnum(Document):
|
||||||
@ -1625,23 +1633,31 @@ class EnumField(BaseField):
|
|||||||
ModelWithEnum(status=Status.DONE)
|
ModelWithEnum(status=Status.DONE)
|
||||||
|
|
||||||
Enum fields can be searched using enum or its value:
|
Enum fields can be searched using enum or its value:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
ModelWithEnum.objects(status='new').count()
|
ModelWithEnum.objects(status='new').count()
|
||||||
ModelWithEnum.objects(status=Status.NEW).count()
|
ModelWithEnum.objects(status=Status.NEW).count()
|
||||||
|
|
||||||
Note that choices cannot be set explicitly, they are derived
|
The values can be restricted to a subset of the enum by using the ``choices`` parameter:
|
||||||
from the provided enum class.
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class ModelWithEnum(Document):
|
||||||
|
status = EnumField(Status, choices=[Status.NEW, Status.DONE])
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, enum, **kwargs):
|
def __init__(self, enum, **kwargs):
|
||||||
self._enum_cls = enum
|
self._enum_cls = enum
|
||||||
if "choices" in kwargs:
|
if kwargs.get("choices"):
|
||||||
raise ValueError(
|
invalid_choices = []
|
||||||
"'choices' can't be set on EnumField, "
|
for choice in kwargs["choices"]:
|
||||||
"it is implicitly set as the enum class"
|
if not isinstance(choice, enum):
|
||||||
)
|
invalid_choices.append(choice)
|
||||||
kwargs["choices"] = list(self._enum_cls) # Implicit validator
|
if invalid_choices:
|
||||||
|
raise ValueError("Invalid choices: %r" % invalid_choices)
|
||||||
|
else:
|
||||||
|
kwargs["choices"] = list(self._enum_cls) # Implicit validator
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
def __set__(self, instance, value):
|
def __set__(self, instance, value):
|
||||||
|
@ -720,7 +720,7 @@ class BaseQuerySet:
|
|||||||
return queryset.filter(pk=object_id).first()
|
return queryset.filter(pk=object_id).first()
|
||||||
|
|
||||||
def in_bulk(self, object_ids):
|
def in_bulk(self, object_ids):
|
||||||
""" "Retrieve a set of documents by their ids.
|
"""Retrieve a set of documents by their ids.
|
||||||
|
|
||||||
:param object_ids: a list or tuple of ObjectId's
|
:param object_ids: a list or tuple of ObjectId's
|
||||||
:rtype: dict of ObjectId's as keys and collection-specific
|
:rtype: dict of ObjectId's as keys and collection-specific
|
||||||
|
@ -51,6 +51,10 @@ STRING_OPERATORS = (
|
|||||||
"iendswith",
|
"iendswith",
|
||||||
"exact",
|
"exact",
|
||||||
"iexact",
|
"iexact",
|
||||||
|
"regex",
|
||||||
|
"iregex",
|
||||||
|
"wholeword",
|
||||||
|
"iwholeword",
|
||||||
)
|
)
|
||||||
CUSTOM_OPERATORS = ("match",)
|
CUSTOM_OPERATORS = ("match",)
|
||||||
MATCH_OPERATORS = (
|
MATCH_OPERATORS = (
|
||||||
|
4
setup.py
4
setup.py
@ -101,6 +101,8 @@ CLASSIFIERS = [
|
|||||||
"Programming Language :: Python :: 3.6",
|
"Programming Language :: Python :: 3.6",
|
||||||
"Programming Language :: Python :: 3.7",
|
"Programming Language :: Python :: 3.7",
|
||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"Programming Language :: Python :: Implementation :: PyPy",
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
"Topic :: Database",
|
"Topic :: Database",
|
||||||
@ -110,7 +112,7 @@ CLASSIFIERS = [
|
|||||||
extra_opts = {
|
extra_opts = {
|
||||||
"packages": find_packages(exclude=["tests", "tests.*"]),
|
"packages": find_packages(exclude=["tests", "tests.*"]),
|
||||||
"tests_require": [
|
"tests_require": [
|
||||||
"pytest<5.0",
|
"pytest",
|
||||||
"pytest-cov",
|
"pytest-cov",
|
||||||
"coverage",
|
"coverage",
|
||||||
"blinker",
|
"blinker",
|
||||||
|
@ -65,12 +65,12 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
for collection in list_collection_names(self.db):
|
for collection in list_collection_names(self.db):
|
||||||
self.db.drop_collection(collection)
|
self.db.drop_collection(collection)
|
||||||
|
|
||||||
def assertDbEqual(self, docs):
|
def _assert_db_equal(self, docs):
|
||||||
assert list(self.Person._get_collection().find().sort("id")) == sorted(
|
assert list(self.Person._get_collection().find().sort("id")) == sorted(
|
||||||
docs, key=lambda doc: doc["_id"]
|
docs, key=lambda doc: doc["_id"]
|
||||||
)
|
)
|
||||||
|
|
||||||
def assertHasInstance(self, field, instance):
|
def _assert_has_instance(self, field, instance):
|
||||||
assert hasattr(field, "_instance")
|
assert hasattr(field, "_instance")
|
||||||
assert field._instance is not None
|
assert field._instance is not None
|
||||||
if isinstance(field._instance, weakref.ProxyType):
|
if isinstance(field._instance, weakref.ProxyType):
|
||||||
@ -407,6 +407,16 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
assert person.name == "Test User"
|
assert person.name == "Test User"
|
||||||
assert person.age == 30
|
assert person.age == 30
|
||||||
|
|
||||||
|
def test__qs_property_does_not_raise(self):
|
||||||
|
# ensures no regression of #2500
|
||||||
|
class MyDocument(Document):
|
||||||
|
pass
|
||||||
|
|
||||||
|
MyDocument.drop_collection()
|
||||||
|
object = MyDocument()
|
||||||
|
object._qs().insert([MyDocument()])
|
||||||
|
assert MyDocument.objects.count() == 1
|
||||||
|
|
||||||
def test_to_dbref(self):
|
def test_to_dbref(self):
|
||||||
"""Ensure that you can get a dbref of a document."""
|
"""Ensure that you can get a dbref of a document."""
|
||||||
person = self.Person(name="Test User", age=30)
|
person = self.Person(name="Test User", age=30)
|
||||||
@ -730,11 +740,11 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
Doc.drop_collection()
|
Doc.drop_collection()
|
||||||
|
|
||||||
doc = Doc(embedded_field=Embedded(string="Hi"))
|
doc = Doc(embedded_field=Embedded(string="Hi"))
|
||||||
self.assertHasInstance(doc.embedded_field, doc)
|
self._assert_has_instance(doc.embedded_field, doc)
|
||||||
|
|
||||||
doc.save()
|
doc.save()
|
||||||
doc = Doc.objects.get()
|
doc = Doc.objects.get()
|
||||||
self.assertHasInstance(doc.embedded_field, doc)
|
self._assert_has_instance(doc.embedded_field, doc)
|
||||||
|
|
||||||
def test_embedded_document_complex_instance(self):
|
def test_embedded_document_complex_instance(self):
|
||||||
"""Ensure that embedded documents in complex fields can reference
|
"""Ensure that embedded documents in complex fields can reference
|
||||||
@ -749,11 +759,11 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
|
|
||||||
Doc.drop_collection()
|
Doc.drop_collection()
|
||||||
doc = Doc(embedded_field=[Embedded(string="Hi")])
|
doc = Doc(embedded_field=[Embedded(string="Hi")])
|
||||||
self.assertHasInstance(doc.embedded_field[0], doc)
|
self._assert_has_instance(doc.embedded_field[0], doc)
|
||||||
|
|
||||||
doc.save()
|
doc.save()
|
||||||
doc = Doc.objects.get()
|
doc = Doc.objects.get()
|
||||||
self.assertHasInstance(doc.embedded_field[0], doc)
|
self._assert_has_instance(doc.embedded_field[0], doc)
|
||||||
|
|
||||||
def test_embedded_document_complex_instance_no_use_db_field(self):
|
def test_embedded_document_complex_instance_no_use_db_field(self):
|
||||||
"""Ensure that use_db_field is propagated to list of Emb Docs."""
|
"""Ensure that use_db_field is propagated to list of Emb Docs."""
|
||||||
@ -782,11 +792,11 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
|
|
||||||
acc = Account()
|
acc = Account()
|
||||||
acc.email = Email(email="test@example.com")
|
acc.email = Email(email="test@example.com")
|
||||||
self.assertHasInstance(acc._data["email"], acc)
|
self._assert_has_instance(acc._data["email"], acc)
|
||||||
acc.save()
|
acc.save()
|
||||||
|
|
||||||
acc1 = Account.objects.first()
|
acc1 = Account.objects.first()
|
||||||
self.assertHasInstance(acc1._data["email"], acc1)
|
self._assert_has_instance(acc1._data["email"], acc1)
|
||||||
|
|
||||||
def test_instance_is_set_on_setattr_on_embedded_document_list(self):
|
def test_instance_is_set_on_setattr_on_embedded_document_list(self):
|
||||||
class Email(EmbeddedDocument):
|
class Email(EmbeddedDocument):
|
||||||
@ -798,11 +808,11 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
Account.drop_collection()
|
Account.drop_collection()
|
||||||
acc = Account()
|
acc = Account()
|
||||||
acc.emails = [Email(email="test@example.com")]
|
acc.emails = [Email(email="test@example.com")]
|
||||||
self.assertHasInstance(acc._data["emails"][0], acc)
|
self._assert_has_instance(acc._data["emails"][0], acc)
|
||||||
acc.save()
|
acc.save()
|
||||||
|
|
||||||
acc1 = Account.objects.first()
|
acc1 = Account.objects.first()
|
||||||
self.assertHasInstance(acc1._data["emails"][0], acc1)
|
self._assert_has_instance(acc1._data["emails"][0], acc1)
|
||||||
|
|
||||||
def test_save_checks_that_clean_is_called(self):
|
def test_save_checks_that_clean_is_called(self):
|
||||||
class CustomError(Exception):
|
class CustomError(Exception):
|
||||||
@ -911,7 +921,7 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
with pytest.raises(InvalidDocumentError):
|
with pytest.raises(InvalidDocumentError):
|
||||||
self.Person().modify(set__age=10)
|
self.Person().modify(set__age=10)
|
||||||
|
|
||||||
self.assertDbEqual([dict(doc.to_mongo())])
|
self._assert_db_equal([dict(doc.to_mongo())])
|
||||||
|
|
||||||
def test_modify_invalid_query(self):
|
def test_modify_invalid_query(self):
|
||||||
doc1 = self.Person(name="bob", age=10).save()
|
doc1 = self.Person(name="bob", age=10).save()
|
||||||
@ -921,7 +931,7 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
with pytest.raises(InvalidQueryError):
|
with pytest.raises(InvalidQueryError):
|
||||||
doc1.modify({"id": doc2.id}, set__value=20)
|
doc1.modify({"id": doc2.id}, set__value=20)
|
||||||
|
|
||||||
self.assertDbEqual(docs)
|
self._assert_db_equal(docs)
|
||||||
|
|
||||||
def test_modify_match_another_document(self):
|
def test_modify_match_another_document(self):
|
||||||
doc1 = self.Person(name="bob", age=10).save()
|
doc1 = self.Person(name="bob", age=10).save()
|
||||||
@ -931,7 +941,7 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
n_modified = doc1.modify({"name": doc2.name}, set__age=100)
|
n_modified = doc1.modify({"name": doc2.name}, set__age=100)
|
||||||
assert n_modified == 0
|
assert n_modified == 0
|
||||||
|
|
||||||
self.assertDbEqual(docs)
|
self._assert_db_equal(docs)
|
||||||
|
|
||||||
def test_modify_not_exists(self):
|
def test_modify_not_exists(self):
|
||||||
doc1 = self.Person(name="bob", age=10).save()
|
doc1 = self.Person(name="bob", age=10).save()
|
||||||
@ -941,7 +951,7 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
n_modified = doc2.modify({"name": doc2.name}, set__age=100)
|
n_modified = doc2.modify({"name": doc2.name}, set__age=100)
|
||||||
assert n_modified == 0
|
assert n_modified == 0
|
||||||
|
|
||||||
self.assertDbEqual(docs)
|
self._assert_db_equal(docs)
|
||||||
|
|
||||||
def test_modify_update(self):
|
def test_modify_update(self):
|
||||||
other_doc = self.Person(name="bob", age=10).save()
|
other_doc = self.Person(name="bob", age=10).save()
|
||||||
@ -967,7 +977,7 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
assert doc.to_json() == doc_copy.to_json()
|
assert doc.to_json() == doc_copy.to_json()
|
||||||
assert doc._get_changed_fields() == []
|
assert doc._get_changed_fields() == []
|
||||||
|
|
||||||
self.assertDbEqual([dict(other_doc.to_mongo()), dict(doc.to_mongo())])
|
self._assert_db_equal([dict(other_doc.to_mongo()), dict(doc.to_mongo())])
|
||||||
|
|
||||||
def test_modify_with_positional_push(self):
|
def test_modify_with_positional_push(self):
|
||||||
class Content(EmbeddedDocument):
|
class Content(EmbeddedDocument):
|
||||||
|
@ -118,3 +118,23 @@ class TestDecimalField(MongoDBTestCase):
|
|||||||
assert 2 == Person.objects(money__gt="7").count()
|
assert 2 == Person.objects(money__gt="7").count()
|
||||||
|
|
||||||
assert 3 == Person.objects(money__gte="7").count()
|
assert 3 == Person.objects(money__gte="7").count()
|
||||||
|
|
||||||
|
def test_precision_0(self):
|
||||||
|
"""prevent regression of a bug that was raising an exception when using precision=0"""
|
||||||
|
|
||||||
|
class TestDoc(Document):
|
||||||
|
d = DecimalField(precision=0)
|
||||||
|
|
||||||
|
TestDoc.drop_collection()
|
||||||
|
|
||||||
|
td = TestDoc(d=Decimal("12.00032678131263"))
|
||||||
|
assert td.d == Decimal("12")
|
||||||
|
|
||||||
|
def test_precision_negative_raise(self):
|
||||||
|
"""prevent regression of a bug that was raising an exception when using precision=0"""
|
||||||
|
with pytest.raises(
|
||||||
|
ValidationError, match="precision must be a positive integer"
|
||||||
|
):
|
||||||
|
|
||||||
|
class TestDoc(Document):
|
||||||
|
dneg = DecimalField(precision=-1)
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from bson import ObjectId
|
||||||
|
|
||||||
from mongoengine import (
|
from mongoengine import (
|
||||||
Document,
|
Document,
|
||||||
@ -9,6 +12,7 @@ from mongoengine import (
|
|||||||
InvalidQueryError,
|
InvalidQueryError,
|
||||||
ListField,
|
ListField,
|
||||||
LookUpError,
|
LookUpError,
|
||||||
|
MapField,
|
||||||
StringField,
|
StringField,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
)
|
)
|
||||||
@ -350,3 +354,30 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
|
|||||||
# Test existing attribute
|
# Test existing attribute
|
||||||
assert Person.objects(settings__base_foo="basefoo").first().id == p.id
|
assert Person.objects(settings__base_foo="basefoo").first().id == p.id
|
||||||
assert Person.objects(settings__sub_foo="subfoo").first().id == p.id
|
assert Person.objects(settings__sub_foo="subfoo").first().id == p.id
|
||||||
|
|
||||||
|
def test_deepcopy_set__instance(self):
|
||||||
|
"""Ensure that the _instance attribute on EmbeddedDocument exists after a deepcopy"""
|
||||||
|
|
||||||
|
class Wallet(EmbeddedDocument):
|
||||||
|
money = IntField()
|
||||||
|
|
||||||
|
class Person(Document):
|
||||||
|
wallet = EmbeddedDocumentField(Wallet)
|
||||||
|
wallet_map = MapField(EmbeddedDocumentField(Wallet))
|
||||||
|
|
||||||
|
# Test on fresh EmbeddedDoc
|
||||||
|
emb_doc = Wallet(money=1)
|
||||||
|
assert emb_doc._instance is None
|
||||||
|
copied_emb_doc = deepcopy(emb_doc)
|
||||||
|
assert copied_emb_doc._instance is None
|
||||||
|
|
||||||
|
# Test on attached EmbeddedDoc
|
||||||
|
doc = Person(
|
||||||
|
id=ObjectId(), wallet=Wallet(money=2), wallet_map={"test": Wallet(money=2)}
|
||||||
|
)
|
||||||
|
assert doc.wallet._instance == doc
|
||||||
|
copied_emb_doc = deepcopy(doc.wallet)
|
||||||
|
assert copied_emb_doc._instance is None
|
||||||
|
|
||||||
|
copied_map_emb_doc = deepcopy(doc.wallet_map)
|
||||||
|
assert copied_map_emb_doc["test"]._instance is None
|
||||||
|
@ -12,6 +12,11 @@ class Status(Enum):
|
|||||||
DONE = "done"
|
DONE = "done"
|
||||||
|
|
||||||
|
|
||||||
|
class Color(Enum):
|
||||||
|
RED = 1
|
||||||
|
BLUE = 2
|
||||||
|
|
||||||
|
|
||||||
class ModelWithEnum(Document):
|
class ModelWithEnum(Document):
|
||||||
status = EnumField(Status)
|
status = EnumField(Status)
|
||||||
|
|
||||||
@ -74,14 +79,27 @@ class TestStringEnumField(MongoDBTestCase):
|
|||||||
with pytest.raises(ValidationError):
|
with pytest.raises(ValidationError):
|
||||||
m.validate()
|
m.validate()
|
||||||
|
|
||||||
def test_user_is_informed_when_tries_to_set_choices(self):
|
def test_partial_choices(self):
|
||||||
with pytest.raises(ValueError, match="'choices' can't be set on EnumField"):
|
partial = [Status.DONE]
|
||||||
|
enum_field = EnumField(Status, choices=partial)
|
||||||
|
assert enum_field.choices == partial
|
||||||
|
|
||||||
|
class FancyDoc(Document):
|
||||||
|
z = enum_field
|
||||||
|
|
||||||
|
FancyDoc(z=Status.DONE).validate()
|
||||||
|
with pytest.raises(
|
||||||
|
ValidationError, match=r"Value must be one of .*Status.DONE"
|
||||||
|
):
|
||||||
|
FancyDoc(z=Status.NEW).validate()
|
||||||
|
|
||||||
|
def test_wrong_choices(self):
|
||||||
|
with pytest.raises(ValueError, match="Invalid choices"):
|
||||||
EnumField(Status, choices=["my", "custom", "options"])
|
EnumField(Status, choices=["my", "custom", "options"])
|
||||||
|
with pytest.raises(ValueError, match="Invalid choices"):
|
||||||
|
EnumField(Status, choices=[Color.RED])
|
||||||
class Color(Enum):
|
with pytest.raises(ValueError, match="Invalid choices"):
|
||||||
RED = 1
|
EnumField(Status, choices=[Status.DONE, Color.RED])
|
||||||
BLUE = 2
|
|
||||||
|
|
||||||
|
|
||||||
class ModelWithColor(Document):
|
class ModelWithColor(Document):
|
||||||
|
@ -2077,7 +2077,7 @@ class TestField(MongoDBTestCase):
|
|||||||
a ComplexBaseField.
|
a ComplexBaseField.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class EnumField(BaseField):
|
class SomeField(BaseField):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
@ -2088,7 +2088,7 @@ class TestField(MongoDBTestCase):
|
|||||||
return tuple(value)
|
return tuple(value)
|
||||||
|
|
||||||
class TestDoc(Document):
|
class TestDoc(Document):
|
||||||
items = ListField(EnumField())
|
items = ListField(SomeField())
|
||||||
|
|
||||||
TestDoc.drop_collection()
|
TestDoc.drop_collection()
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ class TestFindAndModify(unittest.TestCase):
|
|||||||
connect(db="mongoenginetest")
|
connect(db="mongoenginetest")
|
||||||
Doc.drop_collection()
|
Doc.drop_collection()
|
||||||
|
|
||||||
def assertDbEqual(self, docs):
|
def _assert_db_equal(self, docs):
|
||||||
assert list(Doc._collection.find().sort("id")) == docs
|
assert list(Doc._collection.find().sort("id")) == docs
|
||||||
|
|
||||||
def test_modify(self):
|
def test_modify(self):
|
||||||
@ -28,7 +28,7 @@ class TestFindAndModify(unittest.TestCase):
|
|||||||
|
|
||||||
old_doc = Doc.objects(id=1).modify(set__value=-1)
|
old_doc = Doc.objects(id=1).modify(set__value=-1)
|
||||||
assert old_doc.to_json() == doc.to_json()
|
assert old_doc.to_json() == doc.to_json()
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
||||||
|
|
||||||
def test_modify_with_new(self):
|
def test_modify_with_new(self):
|
||||||
Doc(id=0, value=0).save()
|
Doc(id=0, value=0).save()
|
||||||
@ -37,18 +37,18 @@ class TestFindAndModify(unittest.TestCase):
|
|||||||
new_doc = Doc.objects(id=1).modify(set__value=-1, new=True)
|
new_doc = Doc.objects(id=1).modify(set__value=-1, new=True)
|
||||||
doc.value = -1
|
doc.value = -1
|
||||||
assert new_doc.to_json() == doc.to_json()
|
assert new_doc.to_json() == doc.to_json()
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
||||||
|
|
||||||
def test_modify_not_existing(self):
|
def test_modify_not_existing(self):
|
||||||
Doc(id=0, value=0).save()
|
Doc(id=0, value=0).save()
|
||||||
assert Doc.objects(id=1).modify(set__value=-1) is None
|
assert Doc.objects(id=1).modify(set__value=-1) is None
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}])
|
self._assert_db_equal([{"_id": 0, "value": 0}])
|
||||||
|
|
||||||
def test_modify_with_upsert(self):
|
def test_modify_with_upsert(self):
|
||||||
Doc(id=0, value=0).save()
|
Doc(id=0, value=0).save()
|
||||||
old_doc = Doc.objects(id=1).modify(set__value=1, upsert=True)
|
old_doc = Doc.objects(id=1).modify(set__value=1, upsert=True)
|
||||||
assert old_doc is None
|
assert old_doc is None
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
|
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
|
||||||
|
|
||||||
def test_modify_with_upsert_existing(self):
|
def test_modify_with_upsert_existing(self):
|
||||||
Doc(id=0, value=0).save()
|
Doc(id=0, value=0).save()
|
||||||
@ -56,13 +56,13 @@ class TestFindAndModify(unittest.TestCase):
|
|||||||
|
|
||||||
old_doc = Doc.objects(id=1).modify(set__value=-1, upsert=True)
|
old_doc = Doc.objects(id=1).modify(set__value=-1, upsert=True)
|
||||||
assert old_doc.to_json() == doc.to_json()
|
assert old_doc.to_json() == doc.to_json()
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
||||||
|
|
||||||
def test_modify_with_upsert_with_new(self):
|
def test_modify_with_upsert_with_new(self):
|
||||||
Doc(id=0, value=0).save()
|
Doc(id=0, value=0).save()
|
||||||
new_doc = Doc.objects(id=1).modify(upsert=True, new=True, set__value=1)
|
new_doc = Doc.objects(id=1).modify(upsert=True, new=True, set__value=1)
|
||||||
assert new_doc.to_mongo() == {"_id": 1, "value": 1}
|
assert new_doc.to_mongo() == {"_id": 1, "value": 1}
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
|
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
|
||||||
|
|
||||||
def test_modify_with_remove(self):
|
def test_modify_with_remove(self):
|
||||||
Doc(id=0, value=0).save()
|
Doc(id=0, value=0).save()
|
||||||
@ -70,12 +70,12 @@ class TestFindAndModify(unittest.TestCase):
|
|||||||
|
|
||||||
old_doc = Doc.objects(id=1).modify(remove=True)
|
old_doc = Doc.objects(id=1).modify(remove=True)
|
||||||
assert old_doc.to_json() == doc.to_json()
|
assert old_doc.to_json() == doc.to_json()
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}])
|
self._assert_db_equal([{"_id": 0, "value": 0}])
|
||||||
|
|
||||||
def test_find_and_modify_with_remove_not_existing(self):
|
def test_find_and_modify_with_remove_not_existing(self):
|
||||||
Doc(id=0, value=0).save()
|
Doc(id=0, value=0).save()
|
||||||
assert Doc.objects(id=1).modify(remove=True) is None
|
assert Doc.objects(id=1).modify(remove=True) is None
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}])
|
self._assert_db_equal([{"_id": 0, "value": 0}])
|
||||||
|
|
||||||
def test_modify_with_order_by(self):
|
def test_modify_with_order_by(self):
|
||||||
Doc(id=0, value=3).save()
|
Doc(id=0, value=3).save()
|
||||||
@ -85,7 +85,7 @@ class TestFindAndModify(unittest.TestCase):
|
|||||||
|
|
||||||
old_doc = Doc.objects().order_by("-id").modify(set__value=-1)
|
old_doc = Doc.objects().order_by("-id").modify(set__value=-1)
|
||||||
assert old_doc.to_json() == doc.to_json()
|
assert old_doc.to_json() == doc.to_json()
|
||||||
self.assertDbEqual(
|
self._assert_db_equal(
|
||||||
[
|
[
|
||||||
{"_id": 0, "value": 3},
|
{"_id": 0, "value": 3},
|
||||||
{"_id": 1, "value": 2},
|
{"_id": 1, "value": 2},
|
||||||
@ -100,7 +100,7 @@ class TestFindAndModify(unittest.TestCase):
|
|||||||
|
|
||||||
old_doc = Doc.objects(id=1).only("id").modify(set__value=-1)
|
old_doc = Doc.objects(id=1).only("id").modify(set__value=-1)
|
||||||
assert old_doc.to_mongo() == {"_id": 1}
|
assert old_doc.to_mongo() == {"_id": 1}
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
||||||
|
|
||||||
def test_modify_with_push(self):
|
def test_modify_with_push(self):
|
||||||
class BlogPost(Document):
|
class BlogPost(Document):
|
||||||
|
@ -867,6 +867,21 @@ class TestQueryset(unittest.TestCase):
|
|||||||
assert "Bob" == bob.name
|
assert "Bob" == bob.name
|
||||||
assert 30 == bob.age
|
assert 30 == bob.age
|
||||||
|
|
||||||
|
def test_rename(self):
|
||||||
|
self.Person.drop_collection()
|
||||||
|
self.Person.objects.create(name="Foo", age=11)
|
||||||
|
|
||||||
|
bob = self.Person.objects.as_pymongo().first()
|
||||||
|
assert "age" in bob
|
||||||
|
assert bob["age"] == 11
|
||||||
|
|
||||||
|
self.Person.objects(name="Foo").update(rename__age="person_age")
|
||||||
|
|
||||||
|
bob = self.Person.objects.as_pymongo().first()
|
||||||
|
assert "age" not in bob
|
||||||
|
assert "person_age" in bob
|
||||||
|
assert bob["person_age"] == 11
|
||||||
|
|
||||||
def test_save_and_only_on_fields_with_default(self):
|
def test_save_and_only_on_fields_with_default(self):
|
||||||
class Embed(EmbeddedDocument):
|
class Embed(EmbeddedDocument):
|
||||||
field = IntField()
|
field = IntField()
|
||||||
@ -1240,6 +1255,34 @@ class TestQueryset(unittest.TestCase):
|
|||||||
obj = self.Person.objects(name__iexact="gUIDO VAN rOSSU").first()
|
obj = self.Person.objects(name__iexact="gUIDO VAN rOSSU").first()
|
||||||
assert obj is None
|
assert obj is None
|
||||||
|
|
||||||
|
# Test wholeword
|
||||||
|
obj = self.Person.objects(name__wholeword="Guido").first()
|
||||||
|
assert obj == person
|
||||||
|
obj = self.Person.objects(name__wholeword="rossum").first()
|
||||||
|
assert obj is None
|
||||||
|
obj = self.Person.objects(name__wholeword="Rossu").first()
|
||||||
|
assert obj is None
|
||||||
|
|
||||||
|
# Test iwholeword
|
||||||
|
obj = self.Person.objects(name__iwholeword="rOSSUM").first()
|
||||||
|
assert obj == person
|
||||||
|
obj = self.Person.objects(name__iwholeword="rOSSU").first()
|
||||||
|
assert obj is None
|
||||||
|
|
||||||
|
# Test regex
|
||||||
|
obj = self.Person.objects(name__regex="^[Guido].*[Rossum]$").first()
|
||||||
|
assert obj == person
|
||||||
|
obj = self.Person.objects(name__regex="^[guido].*[rossum]$").first()
|
||||||
|
assert obj is None
|
||||||
|
obj = self.Person.objects(name__regex="^[uido].*[Rossum]$").first()
|
||||||
|
assert obj is None
|
||||||
|
|
||||||
|
# Test iregex
|
||||||
|
obj = self.Person.objects(name__iregex="^[guido].*[rossum]$").first()
|
||||||
|
assert obj == person
|
||||||
|
obj = self.Person.objects(name__iregex="^[Uido].*[Rossum]$").first()
|
||||||
|
assert obj is None
|
||||||
|
|
||||||
# Test unsafe expressions
|
# Test unsafe expressions
|
||||||
person = self.Person(name="Guido van Rossum [.'Geek']")
|
person = self.Person(name="Guido van Rossum [.'Geek']")
|
||||||
person.save()
|
person.save()
|
||||||
@ -1324,7 +1367,14 @@ class TestQueryset(unittest.TestCase):
|
|||||||
person.save()
|
person.save()
|
||||||
|
|
||||||
people = self.Person.objects
|
people = self.Person.objects
|
||||||
people = people.filter(name__startswith="Gui").filter(name__not__endswith="tum")
|
people = (
|
||||||
|
people.filter(name__startswith="Gui")
|
||||||
|
.filter(name__not__endswith="tum")
|
||||||
|
.filter(name__icontains="VAN")
|
||||||
|
.filter(name__regex="^Guido")
|
||||||
|
.filter(name__wholeword="Guido")
|
||||||
|
.filter(name__wholeword="van")
|
||||||
|
)
|
||||||
assert people.count() == 1
|
assert people.count() == 1
|
||||||
|
|
||||||
def assertSequence(self, qs, expected):
|
def assertSequence(self, qs, expected):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user