Compare commits

..

3 Commits

Author SHA1 Message Date
Wilson Júnior
1f7272d139 fix simple typo 2014-09-03 15:37:29 -03:00
Wilson Júnior
f6ba1ad788 Merge remote-tracking branch 'origin' into async 2014-09-03 00:50:01 -03:00
Wilson Júnior
294d59c9bb register a possible async database 2014-09-03 00:45:02 -03:00
45 changed files with 450 additions and 1935 deletions

View File

@@ -1,72 +1,62 @@
# http://travis-ci.org/#!/MongoEngine/mongoengine
language: python
python:
- '2.6'
- '2.7'
- '3.2'
- '3.3'
- '3.4'
- pypy
- pypy3
- "2.6"
- "2.7"
- "3.2"
- "3.3"
- "3.4"
- "pypy"
- "pypy3"
env:
- PYMONGO=2.7.2 DJANGO=dev
- PYMONGO=2.7.2 DJANGO=1.7.1
- PYMONGO=2.7.2 DJANGO=1.6.8
- PYMONGO=2.7.2 DJANGO=1.5.11
- PYMONGO=2.8 DJANGO=dev
- PYMONGO=2.8 DJANGO=1.7.1
- PYMONGO=2.8 DJANGO=1.6.8
- PYMONGO=2.8 DJANGO=1.5.11
- PYMONGO=dev DJANGO=dev
- PYMONGO=dev DJANGO=1.6.5
- PYMONGO=dev DJANGO=1.5.8
- PYMONGO=2.7.1 DJANGO=dev
- PYMONGO=2.7.1 DJANGO=1.6.5
- PYMONGO=2.7.1 DJANGO=1.5.8
- PYMONGO=2.7.2 DJANGO=dev
- PYMONGO=2.7.2 DJANGO=1.6.5
- PYMONGO=2.7.2 DJANGO=1.5.8
matrix:
exclude:
- python: '2.6'
env: PYMONGO=2.7.2 DJANGO=dev
- python: '2.6'
env: PYMONGO=2.8 DJANGO=dev
- python: '2.6'
env: PYMONGO=2.7.2 DJANGO=1.7.1
- python: '2.6'
env: PYMONGO=2.8 DJANGO=1.7.1
allow_failures:
- python: pypy3
fast_finish: true
exclude:
- python: "2.6"
env: PYMONGO=dev DJANGO=dev
- python: "2.6"
env: PYMONGO=2.7.1 DJANGO=dev
- python: "2.6"
env: PYMONGO=2.7.2 DJANGO=dev
allow_failures:
- python: "pypy3"
fast_finish: true
before_install:
- travis_retry sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
- echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' |
sudo tee /etc/apt/sources.list.d/mongodb.list
- travis_retry sudo apt-get update
- travis_retry sudo apt-get install mongodb-org-server
- "travis_retry sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10"
- "echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list"
- "travis_retry sudo apt-get update"
- "travis_retry sudo apt-get install mongodb-org-server"
install:
- sudo apt-get install python-dev python3-dev libopenjpeg-dev zlib1g-dev libjpeg-turbo8-dev
libtiff4-dev libjpeg8-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev
python-tk
- if [[ $PYMONGO == 'dev' ]]; then travis_retry pip install https://github.com/mongodb/mongo-python-driver/tarball/master;
true; fi
- if [[ $PYMONGO != 'dev' ]]; then travis_retry pip install pymongo==$PYMONGO; true;
fi
- if [[ $DJANGO == 'dev' ]]; then travis_retry pip install git+https://github.com/django/django.git;
fi
- if [[ $DJANGO != 'dev' ]]; then travis_retry pip install Django==$DJANGO; fi
- travis_retry pip install https://pypi.python.org/packages/source/p/python-dateutil/python-dateutil-2.1.tar.gz#md5=1534bb15cf311f07afaa3aacba1c028b
- travis_retry pip install coveralls
- travis_retry python setup.py install
- sudo apt-get install python-dev python3-dev libopenjpeg-dev zlib1g-dev libjpeg-turbo8-dev libtiff4-dev libjpeg8-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk
- if [[ $PYMONGO == 'dev' ]]; then travis_retry pip install https://github.com/mongodb/mongo-python-driver/tarball/master; true; fi
- if [[ $PYMONGO != 'dev' ]]; then travis_retry pip install pymongo==$PYMONGO; true; fi
- if [[ $DJANGO == 'dev' ]]; then travis_retry pip install https://www.djangoproject.com/download/1.7c2/tarball/; fi
- if [[ $DJANGO != 'dev' ]]; then travis_retry pip install Django==$DJANGO; fi
- travis_retry pip install https://pypi.python.org/packages/source/p/python-dateutil/python-dateutil-2.1.tar.gz#md5=1534bb15cf311f07afaa3aacba1c028b
- travis_retry pip install coveralls
- travis_retry python setup.py install
script:
- travis_retry python setup.py test
- if [[ $TRAVIS_PYTHON_VERSION == '3.'* ]]; then 2to3 . -w; fi;
- coverage run --source=mongoengine setup.py test
- coverage report -m
- python benchmark.py
after_script: coveralls --verbose
- travis_retry python setup.py test
- if [[ $TRAVIS_PYTHON_VERSION == '3.'* ]]; then 2to3 . -w; fi;
- coverage run --source=mongoengine setup.py test
- coverage report -m
- python benchmark.py
after_script:
coveralls --verbose
notifications:
irc: irc.freenode.org#mongoengine
irc: "irc.freenode.org#mongoengine"
branches:
only:
- master
- /^v.*$/
deploy:
provider: pypi
user: the_drow
password:
secure: QMyatmWBnC6ZN3XLW2+fTBDU4LQcp1m/LjR2/0uamyeUzWKdlOoh/Wx5elOgLwt/8N9ppdPeG83ose1jOz69l5G0MUMjv8n/RIcMFSpCT59tGYqn3kh55b0cIZXFT9ar+5cxlif6a5rS72IHm5li7QQyxexJIII6Uxp0kpvUmek=
on:
tags: true
repo: MongoEngine/mongoengine
- master

10
AUTHORS
View File

@@ -208,13 +208,5 @@ that much better:
* Norberto Leite (https://github.com/nleite)
* Bob Cribbs (https://github.com/bocribbz)
* Jay Shirley (https://github.com/jshirley)
* David Bordeynik (https://github.com/DavidBord)
* DavidBord (https://github.com/DavidBord)
* Axel Haustant (https://github.com/noirbizarre)
* David Czarnecki (https://github.com/czarneckid)
* Vyacheslav Murashkin (https://github.com/a4tunado)
* André Ericson https://github.com/aericson)
* Mikhail Moshnogorsky (https://github.com/mikhailmoshnogorsky)
* Diego Berrocal (https://github.com/cestdiego)
* Matthew Ellison (https://github.com/seglberg)
* Jimmy Shen (https://github.com/jimmyshen)
* J. Fernando Sánchez (https://github.com/balkian)

View File

@@ -79,7 +79,6 @@ Fields
.. autoclass:: mongoengine.fields.GenericEmbeddedDocumentField
.. autoclass:: mongoengine.fields.DynamicField
.. autoclass:: mongoengine.fields.ListField
.. autoclass:: mongoengine.fields.EmbeddedDocumentListField
.. autoclass:: mongoengine.fields.SortedListField
.. autoclass:: mongoengine.fields.DictField
.. autoclass:: mongoengine.fields.MapField
@@ -104,21 +103,6 @@ Fields
.. autoclass:: mongoengine.fields.ImageGridFsProxy
.. autoclass:: mongoengine.fields.ImproperlyConfigured
Embedded Document Querying
==========================
.. versionadded:: 0.9
Additional queries for Embedded Documents are available when using the
:class:`~mongoengine.EmbeddedDocumentListField` to store a list of embedded
documents.
A list of embedded documents is returned as a special list with the
following methods:
.. autoclass:: mongoengine.base.datastructures.EmbeddedDocumentList
:members:
Misc
====

View File

@@ -5,27 +5,6 @@ Changelog
Changes in 0.9.X - DEV
======================
- Update FileField when creating a new file #714
- Added `EmbeddedDocumentListField` for Lists of Embedded Documents. #826
- ComplexDateTimeField should fall back to None when null=True #864
- Request Support for $min, $max Field update operators #863
- `BaseDict` does not follow `setdefault` #866
- Add support for $type operator # 766
- Fix tests for pymongo 2.8+ #877
- No module named 'django.utils.importlib' (Django dev) #872
- Field Choices Now Accept Subclasses of Documents
- Ensure Indexes before Each Save #812
- Generate Unique Indices for Lists of EmbeddedDocuments #358
- Sparse fields #515
- write_concern not in params of Collection#remove #801
- Better BaseDocument equality check when not saved #798
- OperationError: Shard Keys are immutable. Tried to update id even though the document is not yet saved #771
- with_limit_and_skip for count should default like in pymongo #759
- Fix storing value of precision attribute in DecimalField #787
- Set attribute to None does not work (at least for fields with default values) #734
- Querying by a field defined in a subclass raises InvalidQueryError #744
- Add Support For MongoDB 2.6.X's maxTimeMS #778
- abstract shouldn't be inherited in EmbeddedDocument # 789
- Allow specifying the '_cls' as a field for indexes #397
- Stop ensure_indexes running on a secondaries unless connection is through mongos #746
- Not overriding default values when loading a subset of fields #399
@@ -56,7 +35,7 @@ Changes in 0.9.X - DEV
- Removing support for Django 1.4.x, pymongo 2.5.x, pymongo 2.6.x.
- Removing support for Python < 2.6.6
- Fixed $maxDistance location for geoJSON $near queries with MongoDB 2.6+ #664
- QuerySet.modify() and Document.modify() methods to provide find_and_modify() like behaviour #677 #773
- QuerySet.modify() method to provide find_and_modify() like behaviour #677
- Added support for the using() method on a queryset #676
- PYPY support #673
- Connection pooling #674
@@ -75,13 +54,10 @@ Changes in 0.9.X - DEV
- Allow atomic update for the entire `DictField` #742
- Added MultiPointField, MultiLineField, MultiPolygonField
- Fix multiple connections aliases being rewritten #748
- Fixed a few instances where reverse_delete_rule was written as reverse_delete_rules. #791
- Make `in_bulk()` respect `no_dereference()` #775
- Handle None from model __str__; Fixes #753 #754
Changes in 0.8.7
================
- Calling reload on deleted / nonexistent documents raises DoesNotExist (#538)
- Calling reload on deleted / nonexistant documents raises DoesNotExist (#538)
- Stop ensure_indexes running on a secondaries (#555)
- Fix circular import issue with django auth (#531) (#545)
@@ -94,7 +70,7 @@ Changes in 0.8.5
- Fix multi level nested fields getting marked as changed (#523)
- Django 1.6 login fix (#522) (#527)
- Django 1.6 session fix (#509)
- EmbeddedDocument._instance is now set when setting the attribute (#506)
- EmbeddedDocument._instance is now set when settng the attribute (#506)
- Fixed EmbeddedDocument with ReferenceField equality issue (#502)
- Fixed GenericReferenceField serialization order (#499)
- Fixed count and none bug (#498)
@@ -184,7 +160,7 @@ Changes in 0.8.0
- Added `get_next_value` preview for SequenceFields (#319)
- Added no_sub_classes context manager and queryset helper (#312)
- Querysets now utilises a local cache
- Changed __len__ behaviour in the queryset (#247, #311)
- Changed __len__ behavour in the queryset (#247, #311)
- Fixed querying string versions of ObjectIds issue with ReferenceField (#307)
- Added $setOnInsert support for upserts (#308)
- Upserts now possible with just query parameters (#309)
@@ -235,7 +211,7 @@ Changes in 0.8.0
- Uses getlasterror to test created on updated saves (#163)
- Fixed inheritance and unique index creation (#140)
- Fixed reverse delete rule with inheritance (#197)
- Fixed validation for GenericReferences which haven't been dereferenced
- Fixed validation for GenericReferences which havent been dereferenced
- Added switch_db context manager (#106)
- Added switch_db method to document instances (#106)
- Added no_dereference context manager (#82) (#61)
@@ -317,11 +293,11 @@ Changes in 0.7.2
- Update index spec generation so its not destructive (#113)
Changes in 0.7.1
================
=================
- Fixed index spec inheritance (#111)
Changes in 0.7.0
================
=================
- Updated queryset.delete so you can use with skip / limit (#107)
- Updated index creation allows kwargs to be passed through refs (#104)
- Fixed Q object merge edge case (#109)
@@ -402,7 +378,7 @@ Changes in 0.6.12
- Fixes error with _delta handling DBRefs
Changes in 0.6.11
=================
==================
- Fixed inconsistency handling None values field attrs
- Fixed map_field embedded db_field issue
- Fixed .save() _delta issue with DbRefs
@@ -482,7 +458,7 @@ Changes in 0.6.1
- Fix for replicaSet connections
Changes in 0.6
==============
================
- Added FutureWarning to inherited classes not declaring 'allow_inheritance' as the default will change in 0.7
- Added support for covered indexes when inheritance is off
@@ -570,8 +546,8 @@ Changes in v0.5
- Updated default collection naming convention
- Added Document Mixin support
- Fixed queryet __repr__ mid iteration
- Added hint() support, so can tell Mongo the proper index to use for the query
- Fixed issue with inconsistent setting of _cls breaking inherited referencing
- Added hint() support, so cantell Mongo the proper index to use for the query
- Fixed issue with inconsitent setting of _cls breaking inherited referencing
- Added help_text and verbose_name to fields to help with some form libs
- Updated item_frequencies to handle embedded document lookups
- Added delta tracking now only sets / unsets explicitly changed fields

View File

@@ -23,32 +23,21 @@ arguments should be provided::
connect('project1', username='webapp', password='pwd123')
URI style connections are also supported -- just supply the URI as
Uri style connections are also supported - just supply the uri as
the :attr:`host` to
:func:`~mongoengine.connect`::
connect('project1', host='mongodb://localhost/database_name')
.. note:: Database, username and password from URI string overrides
corresponding parameters in :func:`~mongoengine.connect`: ::
connect(
name='test',
username='user',
password='12345',
host='mongodb://admin:qwerty@localhost/production'
)
will establish connection to ``production`` database using
``admin`` username and ``qwerty`` password.
Note that database name from uri has priority over name
in ::func:`~mongoengine.connect`
ReplicaSets
===========
MongoEngine supports
:class:`~pymongo.mongo_replica_set_client.MongoReplicaSetClient`. To use them,
please use an URI style connection and provide the ``replicaSet`` name
in the connection kwargs.
MongoEngine supports :class:`~pymongo.mongo_replica_set_client.MongoReplicaSetClient`.
To use them, please use a URI style connection and provide the `replicaSet` name in the
connection kwargs.
Read preferences are supported through the connection or via individual
queries by passing the read_preference ::
@@ -88,38 +77,36 @@ to point across databases and collections. Below is an example schema, using
meta = {"db_alias": "users-books-db"}
Context Managers
================
Sometimes you may want to switch the database or collection to query against
for a class.
For example, archiving older data into a separate database for performance
reasons or writing functions that dynamically choose collections to write
document to.
Switch Database Context Manager
===============================
Sometimes you may want to switch the database to query against for a class
for example, archiving older data into a separate database for performance
reasons.
Switch Database
---------------
The :class:`~mongoengine.context_managers.switch_db` context manager allows
you to change the database alias for a given class allowing quick and easy
access the same User document across databases::
access to the same User document across databases::
from mongoengine.context_managers import switch_db
from mongoengine.context_managers import switch_db
class User(Document):
name = StringField()
class User(Document):
name = StringField()
meta = {"db_alias": "user-db"}
meta = {"db_alias": "user-db"}
with switch_db(User, 'archive-user-db') as User:
User(name="Ross").save() # Saves the 'archive-user-db'
with switch_db(User, 'archive-user-db') as User:
User(name="Ross").save() # Saves the 'archive-user-db'
.. note:: Make sure any aliases have been registered with
:func:`~mongoengine.register_connection` before using the context manager.
Switch Collection
-----------------
The :class:`~mongoengine.context_managers.switch_collection` context manager
allows you to change the collection for a given class allowing quick and easy
access the same Group document across collection::
There is also a switch collection context manager as well. The
:class:`~mongoengine.context_managers.switch_collection` context manager allows
you to change the collection for a given class allowing quick and easy
access to the same Group document across collection::
from mongoengine.context_managers import switch_collection
from mongoengine.context_managers import switch_db
class Group(Document):
name = StringField()
@@ -128,9 +115,3 @@ access the same Group document across collection::
with switch_collection(Group, 'group2000') as Group:
Group(name="hello Group 2000 collection!").save() # Saves in group2000 collection
.. note:: Make sure any aliases have been registered with
:func:`~mongoengine.register_connection` or :func:`~mongoengine.connect`
before using the context manager.

View File

@@ -4,7 +4,7 @@ Defining documents
In MongoDB, a **document** is roughly equivalent to a **row** in an RDBMS. When
working with relational databases, rows are stored in **tables**, which have a
strict **schema** that the rows follow. MongoDB stores documents in
**collections** rather than tables --- the principal difference is that no schema
**collections** rather than tables - the principal difference is that no schema
is enforced at a database level.
Defining a document's schema
@@ -171,15 +171,15 @@ arguments can be set on all fields:
size = StringField(max_length=3, choices=SIZE)
:attr:`help_text` (Default: None)
Optional help text to output with the field -- used by form libraries
Optional help text to output with the field - used by form libraries
:attr:`verbose_name` (Default: None)
Optional human-readable name for the field -- used by form libraries
Optional human-readable name for the field - used by form libraries
List fields
-----------
MongoDB allows storing lists of items. To add a list of items to a
MongoDB allows the storage of lists of items. To add a list of items to a
:class:`~mongoengine.Document`, use the :class:`~mongoengine.fields.ListField` field
type. :class:`~mongoengine.fields.ListField` takes another field object as its first
argument, which specifies which type elements may be stored within the list::
@@ -334,7 +334,7 @@ Its value can take any of the following constants:
Any object's fields still referring to the object being deleted are removed
(using MongoDB's "unset" operation), effectively nullifying the relationship.
:const:`mongoengine.CASCADE`
Any object containing fields that are referring to the object being deleted
Any object containing fields that are refererring to the object being deleted
are deleted first.
:const:`mongoengine.PULL`
Removes the reference to the object (using MongoDB's "pull" operation)
@@ -428,7 +428,7 @@ Document collections
====================
Document classes that inherit **directly** from :class:`~mongoengine.Document`
will have their own **collection** in the database. The name of the collection
is by default the name of the class, converted to lowercase (so in the example
is by default the name of the class, coverted to lowercase (so in the example
above, the collection would be called `page`). If you need to change the name
of the collection (e.g. to use MongoEngine with an existing database), then
create a class dictionary attribute called :attr:`meta` on your document, and
@@ -471,16 +471,8 @@ Text indexes may be specified by prefixing the field name with a **$**. ::
class Page(Document):
title = StringField()
rating = StringField()
created = DateTimeField()
meta = {
'indexes': [
'title',
('title', '-rating'),
{
'fields': ['created'],
'expireAfterSeconds': 3600
}
]
'indexes': ['title', ('title', '-rating')]
}
If a dictionary is passed then the following options are available:
@@ -672,11 +664,11 @@ Shard keys
==========
If your collection is sharded, then you need to specify the shard key as a tuple,
using the :attr:`shard_key` attribute of :attr:`~mongoengine.Document.meta`.
using the :attr:`shard_key` attribute of :attr:`-mongoengine.Document.meta`.
This ensures that the shard key is sent with the query when calling the
:meth:`~mongoengine.document.Document.save` or
:meth:`~mongoengine.document.Document.update` method on an existing
:class:`~mongoengine.Document` instance::
:class:`-mongoengine.Document` instance::
class LogEntry(Document):
machine = StringField()
@@ -698,7 +690,7 @@ defined, you may subclass it and add any extra fields or methods you may need.
As this is new class is not a direct subclass of
:class:`~mongoengine.Document`, it will not be stored in its own collection; it
will use the same collection as its superclass uses. This allows for more
convenient and efficient retrieval of related documents -- all you need do is
convenient and efficient retrieval of related documents - all you need do is
set :attr:`allow_inheritance` to True in the :attr:`meta` data for a
document.::
@@ -712,12 +704,12 @@ document.::
class DatedPage(Page):
date = DateTimeField()
.. note:: From 0.8 onwards :attr:`allow_inheritance` defaults
.. note:: From 0.8 onwards you must declare :attr:`allow_inheritance` defaults
to False, meaning you must set it to True to use inheritance.
Working with existing data
--------------------------
As MongoEngine no longer defaults to needing :attr:`_cls`, you can quickly and
As MongoEngine no longer defaults to needing :attr:`_cls` you can quickly and
easily get working with existing data. Just define the document to match
the expected schema in your database ::
@@ -740,7 +732,7 @@ Abstract classes
If you want to add some extra functionality to a group of Document classes but
you don't need or want the overhead of inheritance you can use the
:attr:`abstract` attribute of :attr:`~mongoengine.Document.meta`.
:attr:`abstract` attribute of :attr:`-mongoengine.Document.meta`.
This won't turn on :ref:`document-inheritance` but will allow you to keep your
code DRY::

View File

@@ -2,7 +2,7 @@
Documents instances
===================
To create a new document object, create an instance of the relevant document
class, providing values for its fields as constructor keyword arguments.
class, providing values for its fields as its constructor keyword arguments.
You may provide values for any of the fields on the document::
>>> page = Page(title="Test Page")
@@ -32,11 +32,11 @@ already exist, then any changes will be updated atomically. For example::
Changes to documents are tracked and on the whole perform ``set`` operations.
* ``list_field.push(0)`` --- *sets* the resulting list
* ``del(list_field)`` --- *unsets* whole list
* ``list_field.push(0)`` - *sets* the resulting list
* ``del(list_field)`` - *unsets* whole list
With lists its preferable to use ``Doc.update(push__list_field=0)`` as
this stops the whole list being updated --- stopping any race conditions.
this stops the whole list being updated - stopping any race conditions.
.. seealso::
:ref:`guide-atomic-updates`
@@ -74,7 +74,7 @@ Cascading Saves
If your document contains :class:`~mongoengine.fields.ReferenceField` or
:class:`~mongoengine.fields.GenericReferenceField` objects, then by default the
:meth:`~mongoengine.Document.save` method will not save any changes to
those objects. If you want all references to be saved also, noting each
those objects. If you want all references to also be saved also, noting each
save is a separate query, then passing :attr:`cascade` as True
to the save method will cascade any saves.
@@ -113,13 +113,12 @@ you may still use :attr:`id` to access the primary key if you want::
>>> bob.id == bob.email == 'bob@example.com'
True
You can also access the document's "primary key" using the :attr:`pk` field,
it's an alias to :attr:`id`::
You can also access the document's "primary key" using the :attr:`pk` field; in
is an alias to :attr:`id`::
>>> page = Page(title="Another Test Page")
>>> page.save()
>>> page.id == page.pk
True
.. note::

View File

@@ -17,7 +17,7 @@ fetch documents from the database::
As of MongoEngine 0.8 the querysets utilise a local cache. So iterating
it multiple times will only cause a single query. If this is not the
desired behaviour you can call :class:`~mongoengine.QuerySet.no_cache`
desired behavour you can call :class:`~mongoengine.QuerySet.no_cache`
(version **0.8.3+**) to return a non-caching queryset.
Filtering queries
@@ -42,7 +42,7 @@ syntax::
Query operators
===============
Operators other than equality may also be used in queries --- just attach the
Operators other than equality may also be used in queries; just attach the
operator name to a key with a double-underscore::
# Only find users whose age is 18 or less
@@ -84,20 +84,19 @@ expressions:
Geo queries
-----------
There are a few special operators for performing geographical queries.
The following were added in MongoEngine 0.8 for
:class:`~mongoengine.fields.PointField`,
There are a few special operators for performing geographical queries. The following
were added in 0.8 for: :class:`~mongoengine.fields.PointField`,
:class:`~mongoengine.fields.LineStringField` and
:class:`~mongoengine.fields.PolygonField`:
* ``geo_within`` -- check if a geometry is within a polygon. For ease of use
it accepts either a geojson geometry or just the polygon coordinates eg::
* ``geo_within`` -- Check if a geometry is within a polygon. For ease of use
it accepts either a geojson geometry or just the polygon coordinates eg::
loc.objects(point__geo_within=[[[40, 5], [40, 6], [41, 6], [40, 5]]])
loc.objects(point__geo_within={"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]})
* ``geo_within_box`` -- simplified geo_within searching with a box eg::
* ``geo_within_box`` - simplified geo_within searching with a box eg::
loc.objects(point__geo_within_box=[(-125.0, 35.0), (-100.0, 40.0)])
loc.objects(point__geo_within_box=[<bottom left coordinates>, <upper right coordinates>])
@@ -133,21 +132,23 @@ The following were added in MongoEngine 0.8 for
loc.objects(poly__geo_intersects={"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]]})
* ``near`` -- find all the locations near a given point::
* ``near`` -- Find all the locations near a given point::
loc.objects(point__near=[40, 5])
loc.objects(point__near={"type": "Point", "coordinates": [40, 5]})
You can also set the maximum distance in meters as well::
You can also set the maximum distance in meters as well::
loc.objects(point__near=[40, 5], point__max_distance=1000)
The older 2D indexes are still supported with the
:class:`~mongoengine.fields.GeoPointField`:
* ``within_distance`` -- provide a list containing a point and a maximum
distance (e.g. [(41.342, -87.653), 5])
* ``within_spherical_distance`` -- same as above but using the spherical geo model
* ``within_spherical_distance`` -- Same as above but using the spherical geo model
(e.g. [(41.342, -87.653), 5/earth_radius])
* ``near`` -- order the documents by how close they are to a given point
* ``near_sphere`` -- Same as above but using the spherical geo model
@@ -197,14 +198,12 @@ However, this doesn't map well to the syntax so you can also use a capital S ins
Post.objects(comments__by="joe").update(inc__comments__S__votes=1)
.. note::
Due to :program:`Mongo`, currently the $ operator only applies to the
first matched item in the query.
.. note:: Due to Mongo currently the $ operator only applies to the first matched item in the query.
Raw queries
-----------
It is possible to provide a raw :mod:`PyMongo` query as a query parameter, which will
It is possible to provide a raw PyMongo query as a query parameter, which will
be integrated directly into the query. This is done using the ``__raw__``
keyword argument::
@@ -214,12 +213,12 @@ keyword argument::
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.
:meth:`~mongoengine.queryset.QuerySet.limit` and
:meth:`~mongoengine.queryset.QuerySet.skip` and methods are available on
:class:`~mongoengine.queryset.QuerySet` objects, but the `array-slicing` syntax
is preferred for achieving this::
:class:`~mongoengine.queryset.QuerySet` objects, but the prefered syntax for
achieving this is using array-slicing syntax::
# Only the first 5 people
users = User.objects[:5]
@@ -253,10 +252,10 @@ To retrieve a result that should be unique in the collection, use
no document matches the query, and
:class:`~mongoengine.queryset.MultipleObjectsReturned`
if more than one document matched the query. These exceptions are merged into
your document definitions eg: `MyDoc.DoesNotExist`
your document defintions eg: `MyDoc.DoesNotExist`
A variation of this method exists,
:meth:`~mongoengine.queryset.QuerySet.get_or_create`, that will create a new
:meth:`~mongoengine.queryset.Queryset.get_or_create`, that will create a new
document with the query arguments if no documents match the query. An
additional keyword argument, :attr:`defaults` may be provided, which will be
used as default values for the new document, in the case that it should need
@@ -267,13 +266,9 @@ to be created::
>>> a.name == b.name and a.age == b.age
True
.. warning::
:meth:`~mongoengine.queryset.QuerySet.get_or_create` method is deprecated
since :mod:`mongoengine` 0.8.
Default Document queries
========================
By default, the objects :attr:`~Document.objects` attribute on a
By default, the objects :attr:`~mongoengine.Document.objects` attribute on a
document returns a :class:`~mongoengine.queryset.QuerySet` that doesn't filter
the collection -- it returns all objects. This may be changed by defining a
method on a document that modifies a queryset. The method should accept two
@@ -316,7 +311,7 @@ Should you want to add custom methods for interacting with or filtering
documents, extending the :class:`~mongoengine.queryset.QuerySet` class may be
the way to go. To use a custom :class:`~mongoengine.queryset.QuerySet` class on
a document, set ``queryset_class`` to the custom class in a
:class:`~mongoengine.Document`'s ``meta`` dictionary::
:class:`~mongoengine.Document`\ s ``meta`` dictionary::
class AwesomerQuerySet(QuerySet):
@@ -496,14 +491,11 @@ Documents may be updated atomically by using the
:meth:`~mongoengine.queryset.QuerySet.update_one`,
:meth:`~mongoengine.queryset.QuerySet.update` and
:meth:`~mongoengine.queryset.QuerySet.modify` methods on a
:class:`~mongoengine.queryset.QuerySet` or
:meth:`~mongoengine.Document.modify` and
:meth:`~mongoengine.Document.save` (with :attr:`save_condition` argument) on a
:class:`~mongoengine.Document`.
There are several different "modifiers" that you may use with these methods:
:meth:`~mongoengine.queryset.QuerySet`. There are several different "modifiers"
that you may use with these methods:
* ``set`` -- set a particular value
* ``unset`` -- delete a particular value (since MongoDB v1.3)
* ``unset`` -- delete a particular value (since MongoDB v1.3+)
* ``inc`` -- increment a value by a given amount
* ``dec`` -- decrement a value by a given amount
* ``push`` -- append a value to a list
@@ -663,4 +655,3 @@ following example shows how the substitutions are made::
return comments;
}
""")

View File

@@ -35,25 +35,25 @@ Available signals include:
:class:`~mongoengine.EmbeddedDocument` instance has been completed.
`pre_save`
Called within :meth:`~mongoengine.Document.save` prior to performing
Called within :meth:`~mongoengine.document.Document.save` prior to performing
any actions.
`pre_save_post_validation`
Called within :meth:`~mongoengine.Document.save` after validation
Called within :meth:`~mongoengine.document.Document.save` after validation
has taken place but before saving.
`post_save`
Called within :meth:`~mongoengine.Document.save` after all actions
Called within :meth:`~mongoengine.document.Document.save` after all actions
(validation, insert/update, cascades, clearing dirty flags) have completed
successfully. Passed the additional boolean keyword argument `created` to
indicate if the save was an insert or an update.
`pre_delete`
Called within :meth:`~mongoengine.Document.delete` prior to
Called within :meth:`~mongoengine.document.Document.delete` prior to
attempting the delete operation.
`post_delete`
Called within :meth:`~mongoengine.Document.delete` upon successful
Called within :meth:`~mongoengine.document.Document.delete` upon successful
deletion of the record.
`pre_bulk_insert`
@@ -145,7 +145,7 @@ cleaner looking while still allowing manual execution of the callback::
ReferenceFields and Signals
---------------------------
Currently `reverse_delete_rule` does not trigger signals on the other part of
Currently `reverse_delete_rules` do not trigger signals on the other part of
the relationship. If this is required you must manually handle the
reverse deletion.

View File

@@ -46,6 +46,4 @@ Next, start a text search using :attr:`QuerySet.search_text` method::
Ordering by text score
======================
::
objects = News.objects.search('mongo').order_by('$text_score')

View File

@@ -14,7 +14,7 @@ MongoDB. To install it, simply run
MongoEngine.
:doc:`guide/index`
The Full guide to MongoEngine --- from modeling documents to storing files,
The Full guide to MongoEngine - from modeling documents to storing files,
from querying for data to firing signals and *everything* between.
:doc:`apireference`

View File

@@ -65,7 +65,7 @@ which fields a :class:`User` may have, and what types of data they might store::
first_name = StringField(max_length=50)
last_name = StringField(max_length=50)
This looks similar to how the structure of a table would be defined in a
This looks similar to how a the structure of a table would be defined in a
regular ORM. The key difference is that this schema will never be passed on to
MongoDB --- this will only be enforced at the application level, making future
changes easy to manage. Also, the User documents will be stored in a

View File

@@ -5,7 +5,7 @@ Upgrading
0.8.7
*****
Calling reload on deleted / nonexistent documents now raises a DoesNotExist
Calling reload on deleted / nonexistant documents now raises a DoesNotExist
exception.
@@ -263,7 +263,7 @@ update your code like so: ::
[m for m in mammals] # This will return all carnivores
Len iterates the queryset
-------------------------
--------------------------
If you ever did `len(queryset)` it previously did a `count()` under the covers,
this caused some unusual issues. As `len(queryset)` is most often used by

View File

@@ -15,7 +15,7 @@ import django
__all__ = (list(document.__all__) + fields.__all__ + connection.__all__ +
list(queryset.__all__) + signals.__all__ + list(errors.__all__))
VERSION = (0, 9, 0)
VERSION = (0, 8, 7)
def get_version():

View File

@@ -2,9 +2,8 @@ import weakref
import functools
import itertools
from mongoengine.common import _import_class
from mongoengine.errors import DoesNotExist, MultipleObjectsReturned
__all__ = ("BaseDict", "BaseList", "EmbeddedDocumentList")
__all__ = ("BaseDict", "BaseList")
class BaseDict(dict):
@@ -76,10 +75,6 @@ class BaseDict(dict):
self._mark_as_changed()
return super(BaseDict, self).popitem(*args, **kwargs)
def setdefault(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).setdefault(*args, **kwargs)
def update(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).update(*args, **kwargs)
@@ -107,7 +102,7 @@ class BaseList(list):
if isinstance(instance, (Document, EmbeddedDocument)):
self._instance = weakref.proxy(instance)
self._name = name
super(BaseList, self).__init__(list_items)
return super(BaseList, self).__init__(list_items)
def __getitem__(self, key, *args, **kwargs):
value = super(BaseList, self).__getitem__(key)
@@ -192,167 +187,6 @@ class BaseList(list):
self._instance._mark_as_changed(self._name)
class EmbeddedDocumentList(BaseList):
@classmethod
def __match_all(cls, i, kwargs):
items = kwargs.items()
return all([
getattr(i, k) == v or str(getattr(i, k)) == v for k, v in items
])
@classmethod
def __only_matches(cls, obj, kwargs):
if not kwargs:
return obj
return filter(lambda i: cls.__match_all(i, kwargs), obj)
def __init__(self, list_items, instance, name):
super(EmbeddedDocumentList, self).__init__(list_items, instance, name)
self._instance = instance
def filter(self, **kwargs):
"""
Filters the list by only including embedded documents with the
given keyword arguments.
:param kwargs: The keyword arguments corresponding to the fields to
filter on. *Multiple arguments are treated as if they are ANDed
together.*
:return: A new ``EmbeddedDocumentList`` containing the matching
embedded documents.
Raises ``AttributeError`` if a given keyword is not a valid field for
the embedded document class.
"""
values = self.__only_matches(self, kwargs)
return EmbeddedDocumentList(values, self._instance, self._name)
def exclude(self, **kwargs):
"""
Filters the list by excluding embedded documents with the given
keyword arguments.
:param kwargs: The keyword arguments corresponding to the fields to
exclude on. *Multiple arguments are treated as if they are ANDed
together.*
:return: A new ``EmbeddedDocumentList`` containing the non-matching
embedded documents.
Raises ``AttributeError`` if a given keyword is not a valid field for
the embedded document class.
"""
exclude = self.__only_matches(self, kwargs)
values = [item for item in self if item not in exclude]
return EmbeddedDocumentList(values, self._instance, self._name)
def count(self):
"""
The number of embedded documents in the list.
:return: The length of the list, equivalent to the result of ``len()``.
"""
return len(self)
def get(self, **kwargs):
"""
Retrieves an embedded document determined by the given keyword
arguments.
:param kwargs: The keyword arguments corresponding to the fields to
search on. *Multiple arguments are treated as if they are ANDed
together.*
:return: The embedded document matched by the given keyword arguments.
Raises ``DoesNotExist`` if the arguments used to query an embedded
document returns no results. ``MultipleObjectsReturned`` if more
than one result is returned.
"""
values = self.__only_matches(self, kwargs)
if len(values) == 0:
raise DoesNotExist(
"%s matching query does not exist." % self._name
)
elif len(values) > 1:
raise MultipleObjectsReturned(
"%d items returned, instead of 1" % len(values)
)
return values[0]
def first(self):
"""
Returns the first embedded document in the list, or ``None`` if empty.
"""
if len(self) > 0:
return self[0]
def create(self, **values):
"""
Creates a new embedded document and saves it to the database.
.. note::
The embedded document changes are not automatically saved
to the database after calling this method.
:param values: A dictionary of values for the embedded document.
:return: The new embedded document instance.
"""
name = self._name
EmbeddedClass = self._instance._fields[name].field.document_type_obj
self._instance[self._name].append(EmbeddedClass(**values))
return self._instance[self._name][-1]
def save(self, *args, **kwargs):
"""
Saves the ancestor document.
:param args: Arguments passed up to the ancestor Document's save
method.
:param kwargs: Keyword arguments passed up to the ancestor Document's
save method.
"""
self._instance.save(*args, **kwargs)
def delete(self):
"""
Deletes the embedded documents from the database.
.. note::
The embedded document changes are not automatically saved
to the database after calling this method.
:return: The number of entries deleted.
"""
values = list(self)
for item in values:
self._instance[self._name].remove(item)
return len(values)
def update(self, **update):
"""
Updates the embedded documents with the given update values.
.. note::
The embedded document changes are not automatically saved
to the database after calling this method.
:param update: A dictionary of update values to apply to each
embedded document.
:return: The number of entries updated.
"""
if len(update) == 0:
return 0
values = list(self)
for item in values:
for k, v in update.items():
setattr(item, k, v)
return len(values)
class StrictDict(object):
__slots__ = ()
_special_fields = set(['get', 'pop', 'iteritems', 'items', 'keys', 'create'])

View File

@@ -12,17 +12,11 @@ from bson.son import SON
from mongoengine import signals
from mongoengine.common import _import_class
from mongoengine.errors import (ValidationError, InvalidDocumentError,
LookUpError, FieldDoesNotExist)
LookUpError)
from mongoengine.python_support import PY3, txt_type
from mongoengine.base.common import get_document, ALLOW_INHERITANCE
from mongoengine.base.datastructures import (
BaseDict,
BaseList,
EmbeddedDocumentList,
StrictDict,
SemiStrictDict
)
from mongoengine.base.datastructures import BaseDict, BaseList, StrictDict, SemiStrictDict
from mongoengine.base.fields import ComplexBaseField
__all__ = ('BaseDocument', 'NON_FIELD_ERRORS')
@@ -60,32 +54,20 @@ class BaseDocument(object):
raise TypeError(
"Multiple values for keyword argument '" + name + "'")
values[name] = value
__auto_convert = values.pop("__auto_convert", True)
# 399: set default values only to fields loaded from DB
__only_fields = set(values.pop("__only_fields", values))
_created = values.pop("_created", True)
signals.pre_init.send(self.__class__, document=self, values=values)
# Check if there are undefined fields supplied, if so raise an
# Exception.
if not self._dynamic:
for var in values.keys():
if var not in self._fields.keys() + ['id', 'pk', '_cls', '_text_score']:
msg = (
"The field '{0}' does not exist on the document '{1}'"
).format(var, self._class_name)
raise FieldDoesNotExist(msg)
if self.STRICT and not self._dynamic:
self._data = StrictDict.create(allowed_keys=self._fields_ordered)()
else:
self._data = SemiStrictDict.create(
allowed_keys=self._fields_ordered)()
_created = values.pop("_created", True)
self._data = {}
self._dynamic_fields = SON()
@@ -247,7 +229,7 @@ class BaseDocument(object):
u = self.__str__()
except (UnicodeEncodeError, UnicodeDecodeError):
u = '[Bad Unicode data]'
repr_type = str if u is None else type(u)
repr_type = type(u)
return repr_type('<%s: %s>' % (self.__class__.__name__, u))
def __str__(self):
@@ -259,12 +241,10 @@ class BaseDocument(object):
return txt_type('%s object' % self.__class__.__name__)
def __eq__(self, other):
if isinstance(other, self.__class__) and hasattr(other, 'id') and other.id is not None:
if isinstance(other, self.__class__) and hasattr(other, 'id'):
return self.id == other.id
if isinstance(other, DBRef):
return self._get_collection_name() == other.collection and self.id == other.id
if self.id is None:
return self is other
return False
def __ne__(self, other):
@@ -287,23 +267,10 @@ class BaseDocument(object):
"""
pass
def get_text_score(self):
"""
Get text score from text query
"""
if '_text_score' not in self._data:
raise InvalidDocumentError('This document is not originally built from a text query')
return self._data['_text_score']
def to_mongo(self, use_db_field=True, fields=None):
def to_mongo(self, use_db_field=True, fields=[]):
"""
Return as SON data ready for use with MongoDB.
"""
if not fields:
fields = []
data = SON()
data["_id"] = None
data['_cls'] = self._class_name
@@ -414,21 +381,20 @@ class BaseDocument(object):
"""Converts a document to JSON.
:param use_db_field: Set to True by default but enables the output of the json structure with the field names and not the mongodb store db_names in case of set to False
"""
use_db_field = kwargs.pop('use_db_field', True)
use_db_field = kwargs.pop('use_db_field') if kwargs.has_key(
'use_db_field') else True
return json_util.dumps(self.to_mongo(use_db_field), *args, **kwargs)
@classmethod
def from_json(cls, json_data, created=False):
def from_json(cls, json_data):
"""Converts json data to an unsaved document instance"""
return cls._from_son(json_util.loads(json_data), created=created)
return cls._from_son(json_util.loads(json_data))
def __expand_dynamic_values(self, name, value):
"""expand any dynamic values to their correct types / values"""
if not isinstance(value, (dict, list, tuple)):
return value
EmbeddedDocumentListField = _import_class('EmbeddedDocumentListField')
is_list = False
if not hasattr(value, 'items'):
is_list = True
@@ -452,10 +418,7 @@ class BaseDocument(object):
# Convert lists / values so we can watch for any changes on them
if (isinstance(value, (list, tuple)) and
not isinstance(value, BaseList)):
if issubclass(type(self), EmbeddedDocumentListField):
value = EmbeddedDocumentList(value, self, name)
else:
value = BaseList(value, self, name)
value = BaseList(value, self, name)
elif isinstance(value, dict) and not isinstance(value, BaseDict):
value = BaseDict(value, self, name)
@@ -654,11 +617,9 @@ class BaseDocument(object):
return cls._meta.get('collection', None)
@classmethod
def _from_son(cls, son, _auto_dereference=True, only_fields=None, created=False):
def _from_son(cls, son, _auto_dereference=True, only_fields=[]):
"""Create an instance of a Document (subclass) from a PyMongo SON.
"""
if not only_fields:
only_fields = []
# get the class name from the document, falling back to the given
# class if unavailable
@@ -706,7 +667,7 @@ class BaseDocument(object):
if cls.STRICT:
data = dict((k, v)
for k, v in data.iteritems() if k in cls._fields)
obj = cls(__auto_convert=False, _created=created, __only_fields=only_fields, **data)
obj = cls(__auto_convert=False, _created=False, __only_fields=only_fields, **data)
obj._changed_fields = changed_fields
if not _auto_dereference:
obj._fields = fields
@@ -729,7 +690,7 @@ class BaseDocument(object):
spec_fields = [v['fields']
for k, v in enumerate(index_specs)]
# Merge unique_indexes with existing specs
# Merge unqiue_indexes with existing specs
for k, v in enumerate(indices):
if v['fields'] in spec_fields:
index_specs[spec_fields.index(v['fields'])].update(v)
@@ -823,9 +784,10 @@ class BaseDocument(object):
"""
unique_indexes = []
for field_name, field in cls._fields.items():
sparse = field.sparse
sparse = False
# Generate a list of indexes needed by uniqueness constraints
if field.unique:
field.required = True
unique_fields = [field.db_field]
# Add any unique_with fields to the back of the index spec
@@ -853,9 +815,6 @@ class BaseDocument(object):
index = {'fields': fields, 'unique': True, 'sparse': sparse}
unique_indexes.append(index)
if field.__class__.__name__ == "ListField":
field = field.field
# Grab any embedded document field unique indexes
if (field.__class__.__name__ == "EmbeddedDocumentField" and
field.document_type != cls):
@@ -925,19 +884,6 @@ class BaseDocument(object):
elif cls._dynamic:
DynamicField = _import_class('DynamicField')
field = DynamicField(db_field=field_name)
elif cls._meta.get("allow_inheritance", False) or cls._meta.get("abstract", False):
# 744: in case the field is defined in a subclass
field = None
for subcls in cls.__subclasses__():
try:
field = subcls._lookup_field([field_name])[0]
except LookUpError:
continue
if field is not None:
break
else:
raise LookUpError('Cannot resolve field "%s"' % field_name)
else:
raise LookUpError('Cannot resolve field "%s"'
% field_name)

View File

@@ -9,9 +9,7 @@ from mongoengine.common import _import_class
from mongoengine.errors import ValidationError
from mongoengine.base.common import ALLOW_INHERITANCE
from mongoengine.base.datastructures import (
BaseDict, BaseList, EmbeddedDocumentList
)
from mongoengine.base.datastructures import BaseDict, BaseList
__all__ = ("BaseField", "ComplexBaseField",
"ObjectIdField", "GeoJsonBaseField")
@@ -39,7 +37,7 @@ class BaseField(object):
def __init__(self, db_field=None, name=None, required=False, default=None,
unique=False, unique_with=None, primary_key=False,
validation=None, choices=None, verbose_name=None,
help_text=None, null=False, sparse=False):
help_text=None):
"""
:param db_field: The database field to store this field in
(defaults to the name of the field)
@@ -62,10 +60,6 @@ class BaseField(object):
model forms from the document model.
:param help_text: (optional) The help text for this field and is often
used when generating model forms from the document model.
:param null: (optional) Is the field value can be null. If no and there is a default value
then the default value is set
:param sparse: (optional) `sparse=True` combined with `unique=True` and `required=False`
means that uniqueness won't be enforced for `None` values
"""
self.db_field = (db_field or name) if not primary_key else '_id'
@@ -81,8 +75,6 @@ class BaseField(object):
self.choices = choices
self.verbose_name = verbose_name
self.help_text = help_text
self.null = null
self.sparse = sparse
# Adjust the appropriate creation counter, and save our local copy.
if self.db_field == '_id':
@@ -108,13 +100,10 @@ class BaseField(object):
# If setting to None and theres a default
# Then set the value to the default value
if value is None:
if self.null:
value = None
elif self.default is not None:
value = self.default
if callable(value):
value = value()
if value is None and self.default is not None:
value = self.default
if callable(value):
value = value()
if instance._initialised:
try:
@@ -160,23 +149,21 @@ class BaseField(object):
def _validate(self, value, **kwargs):
Document = _import_class('Document')
EmbeddedDocument = _import_class('EmbeddedDocument')
# Check the Choices Constraint
# check choices
if self.choices:
choice_list = self.choices
is_cls = isinstance(value, (Document, EmbeddedDocument))
value_to_check = value.__class__ if is_cls else value
err_msg = 'an instance' if is_cls else 'one'
if isinstance(self.choices[0], (list, tuple)):
choice_list = [k for k, v in self.choices]
# Choices which are other types of Documents
if isinstance(value, (Document, EmbeddedDocument)):
if not any(isinstance(value, c) for c in choice_list):
self.error(
'Value must be instance of %s' % unicode(choice_list)
)
# Choices which are types other than Documents
elif value not in choice_list:
self.error('Value must be one of %s' % unicode(choice_list))
option_keys = [k for k, v in self.choices]
if value_to_check not in option_keys:
msg = ('Value must be %s of %s' %
(err_msg, unicode(option_keys)))
self.error(msg)
elif value_to_check not in self.choices:
msg = ('Value must be %s of %s' %
(err_msg, unicode(self.choices)))
self.error(msg)
# check validation argument
if self.validation is not None:
@@ -212,7 +199,6 @@ class ComplexBaseField(BaseField):
ReferenceField = _import_class('ReferenceField')
GenericReferenceField = _import_class('GenericReferenceField')
EmbeddedDocumentListField = _import_class('EmbeddedDocumentListField')
dereference = (self._auto_dereference and
(self.field is None or isinstance(self.field,
(GenericReferenceField, ReferenceField))))
@@ -229,12 +215,9 @@ class ComplexBaseField(BaseField):
value = super(ComplexBaseField, self).__get__(instance, owner)
# Convert lists / values so we can watch for any changes on them
if isinstance(value, (list, tuple)):
if (issubclass(type(self), EmbeddedDocumentListField) and
not isinstance(value, EmbeddedDocumentList)):
value = EmbeddedDocumentList(value, instance, self.name)
elif not isinstance(value, BaseList):
value = BaseList(value, instance, self.name)
if (isinstance(value, (list, tuple)) and
not isinstance(value, BaseList)):
value = BaseList(value, instance, self.name)
instance._data[self.name] = value
elif isinstance(value, dict) and not isinstance(value, BaseDict):
value = BaseDict(value, instance, self.name)
@@ -436,7 +419,6 @@ class ObjectIdField(BaseField):
class GeoJsonBaseField(BaseField):
"""A geo json field storing a geojson style object.
.. versionadded:: 0.8
"""
@@ -445,8 +427,8 @@ class GeoJsonBaseField(BaseField):
def __init__(self, auto_index=True, *args, **kwargs):
"""
:param bool auto_index: Automatically create a "2dsphere" index.\
Defaults to `True`.
:param auto_index: Automatically create a "2dsphere" index. Defaults
to `True`.
"""
self._name = "%sField" % self._type
if not auto_index:

View File

@@ -46,9 +46,8 @@ class DocumentMetaclass(type):
elif hasattr(base, '_meta'):
meta.merge(base._meta)
attrs['_meta'] = meta
attrs['_meta']['abstract'] = False # 789: EmbeddedDocument shouldn't inherit abstract
if attrs['_meta'].get('allow_inheritance', ALLOW_INHERITANCE):
if '_meta' in attrs and attrs['_meta'].get('allow_inheritance', ALLOW_INHERITANCE):
StringField = _import_class('StringField')
attrs['_cls'] = StringField()

View File

@@ -1,5 +1,4 @@
_class_registry_cache = {}
_field_list_cache = []
def _import_class(cls_name):
@@ -21,16 +20,13 @@ def _import_class(cls_name):
doc_classes = ('Document', 'DynamicEmbeddedDocument', 'EmbeddedDocument',
'MapReduceDocument')
# Field Classes
if not _field_list_cache:
from mongoengine.fields import __all__ as fields
_field_list_cache.extend(fields)
from mongoengine.base.fields import __all__ as fields
_field_list_cache.extend(fields)
field_classes = _field_list_cache
field_classes = ('DictField', 'DynamicField', 'EmbeddedDocumentField',
'FileField', 'GenericReferenceField',
'GenericEmbeddedDocumentField', 'GeoPointField',
'PointField', 'LineStringField', 'ListField',
'PolygonField', 'ReferenceField', 'StringField',
'CachedReferenceField',
'ComplexBaseField', 'GeoJsonBaseField')
queryset_classes = ('OperationError',)
deref_classes = ('DeReference',)

View File

@@ -1,6 +1,10 @@
import pymongo
from pymongo import MongoClient, MongoReplicaSetClient, uri_parser
try:
import motor
except ImportError:
motor = None
__all__ = ['ConnectionError', 'connect', 'register_connection',
'DEFAULT_CONNECTION_NAME']
@@ -21,6 +25,7 @@ _dbs = {}
def register_connection(alias, name=None, host=None, port=None,
read_preference=False,
username=None, password=None, authentication_source=None,
async=False,
**kwargs):
"""Add a connection.
@@ -35,7 +40,6 @@ def register_connection(alias, name=None, host=None, port=None,
:param password: password to authenticate with
:param authentication_source: database to authenticate against
:param kwargs: allow ad-hoc parameters to be passed into the pymongo driver
"""
global _connection_settings
@@ -46,7 +50,8 @@ def register_connection(alias, name=None, host=None, port=None,
'read_preference': read_preference,
'username': username,
'password': password,
'authentication_source': authentication_source
'authentication_source': authentication_source,
'async': async
}
# Handle uri style connections
@@ -98,8 +103,17 @@ def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
conn_settings.pop('username', None)
conn_settings.pop('password', None)
conn_settings.pop('authentication_source', None)
async = conn_settings.pop('async')
if async:
if not motor:
raise ImproperlyConfigured("Motor library was not found")
connection_class = motor.MotorClient
else:
connection_class = MongoClient
connection_class = MongoClient
if 'replicaSet' in conn_settings:
conn_settings['hosts_or_uri'] = conn_settings.pop('host', None)
# Discard port since it can't be used on MongoReplicaSetClient
@@ -107,12 +121,17 @@ def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
# Discard replicaSet if not base string
if not isinstance(conn_settings['replicaSet'], basestring):
conn_settings.pop('replicaSet', None)
connection_class = MongoReplicaSetClient
if async:
connection_class = motor.MotorReplicaSetClient
else:
connection_class = MongoReplicaSetClient
try:
connection = None
# check for shared connections
connection_settings_iterator = ((db_alias, settings.copy()) for db_alias, settings in _connection_settings.iteritems())
connection_settings_iterator = (
(db_alias, settings.copy()) for db_alias, settings in _connection_settings.iteritems())
for db_alias, connection_settings in connection_settings_iterator:
connection_settings.pop('name', None)
connection_settings.pop('username', None)
@@ -121,9 +140,11 @@ def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
connection = _connections[db_alias]
break
_connections[alias] = connection if connection else connection_class(**conn_settings)
_connections[alias] = connection if connection else connection_class(
**conn_settings)
except Exception, e:
raise ConnectionError("Cannot connect to database %s :\n%s" % (alias, e))
raise ConnectionError(
"Cannot connect to database %s :\n%s" % (alias, e))
return _connections[alias]

View File

@@ -1,9 +1,6 @@
from bson import DBRef, SON
from base import (
BaseDict, BaseList, EmbeddedDocumentList,
TopLevelDocumentMetaclass, get_document
)
from base import (BaseDict, BaseList, TopLevelDocumentMetaclass, get_document)
from fields import (ReferenceField, ListField, DictField, MapField)
from connection import get_db
from queryset import QuerySet
@@ -192,9 +189,6 @@ class DeReference(object):
if not hasattr(items, 'items'):
is_list = True
list_type = BaseList
if isinstance(items, EmbeddedDocumentList):
list_type = EmbeddedDocumentList
as_tuple = isinstance(items, tuple)
iterator = enumerate(items)
data = []
@@ -231,7 +225,7 @@ class DeReference(object):
if instance and name:
if is_list:
return tuple(data) if as_tuple else list_type(data, instance, name)
return tuple(data) if as_tuple else BaseList(data, instance, name)
return BaseDict(data, instance, name)
depth += 1
return data

View File

@@ -3,11 +3,7 @@ from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import UserManager
from django.core.exceptions import ImproperlyConfigured
from django.db import models
try:
from django.utils.module_loading import import_module
except ImportError:
"""Handle older versions of Django"""
from django.utils.importlib import import_module
from django.utils.importlib import import_module
from django.utils.translation import ugettext_lazy as _

View File

@@ -23,7 +23,7 @@ class MongoTestCase(TestCase):
def dropCollections(self):
for collection in self.db.collection_names():
if collection.startswith('system.'):
if collection == 'system.indexes':
continue
self.db.drop_collection(collection)

View File

@@ -9,17 +9,10 @@ from bson import ObjectId
from bson.dbref import DBRef
from mongoengine import signals
from mongoengine.common import _import_class
from mongoengine.base import (
DocumentMetaclass,
TopLevelDocumentMetaclass,
BaseDocument,
BaseDict,
BaseList,
EmbeddedDocumentList,
ALLOW_INHERITANCE,
get_document
)
from mongoengine.errors import ValidationError, InvalidQueryError, InvalidDocumentError
from mongoengine.base import (DocumentMetaclass, TopLevelDocumentMetaclass,
BaseDocument, BaseDict, BaseList,
ALLOW_INHERITANCE, get_document)
from mongoengine.errors import ValidationError
from mongoengine.queryset import (OperationError, NotUniqueError,
QuerySet, transform)
from mongoengine.connection import get_db, DEFAULT_CONNECTION_NAME
@@ -83,12 +76,6 @@ class EmbeddedDocument(BaseDocument):
def __ne__(self, other):
return not self.__eq__(other)
def save(self, *args, **kwargs):
self._instance.save(*args, **kwargs)
def reload(self, *args, **kwargs):
self._instance.reload(*args, **kwargs)
class Document(BaseDocument):
@@ -126,7 +113,7 @@ class Document(BaseDocument):
a **+** or **-** sign.
Automatic index creation can be disabled by specifying
:attr:`auto_create_index` in the :attr:`meta` dictionary. If this is set to
attr:`auto_create_index` in the :attr:`meta` dictionary. If this is set to
False then indexes will not be created by MongoEngine. This is useful in
production systems where index creation is performed as part of a
deployment system.
@@ -156,6 +143,13 @@ class Document(BaseDocument):
return property(fget, fset)
pk = pk()
@property
def text_score(self):
"""
Used for text searchs
"""
return self._data.get('text_score')
@classmethod
def _get_db(cls):
"""Some Model using other db_alias"""
@@ -198,44 +192,6 @@ class Document(BaseDocument):
cls.ensure_indexes()
return cls._collection
def modify(self, query={}, **update):
"""Perform an atomic update of the document in the database and reload
the document object using updated version.
Returns True if the document has been updated or False if the document
in the database doesn't match the query.
.. note:: All unsaved changes that has been made to the document are
rejected if the method returns True.
:param query: the update will be performed only if the document in the
database matches the query
:param update: Django-style update keyword arguments
"""
if self.pk is None:
raise InvalidDocumentError("The document does not have a primary key.")
id_field = self._meta["id_field"]
query = query.copy() if isinstance(query, dict) else query.to_query(self)
if id_field not in query:
query[id_field] = self.pk
elif query[id_field] != self.pk:
raise InvalidQueryError("Invalid document modify query: it must modify only this document.")
updated = self._qs(**query).modify(new=True, **update)
if updated is None:
return False
for field in self._fields_ordered:
setattr(self, field, self._reload(field, updated[field]))
self._changed_fields = updated._changed_fields
self._created = False
return True
def save(self, force_insert=False, validate=True, clean=True,
write_concern=None, cascade=None, cascade_kwargs=None,
_refs=None, save_condition=None, **kwargs):
@@ -263,7 +219,6 @@ class Document(BaseDocument):
:param _refs: A list of processed references used in cascading saves
:param save_condition: only perform save if matching record in db
satisfies condition(s) (e.g., version number)
.. versionchanged:: 0.5
In existing documents it only saves changed fields using
set / unset. Saves are cascaded and any
@@ -298,8 +253,6 @@ class Document(BaseDocument):
try:
collection = self._get_collection()
if self._meta.get('auto_create_index', True):
self.ensure_indexes()
if created:
if force_insert:
object_id = collection.insert(doc, **write_concern)
@@ -471,11 +424,10 @@ class Document(BaseDocument):
user.switch_db('archive-db')
user.save()
:param str db_alias: The database alias to use for saving the document
If you need to read from another database see
:class:`~mongoengine.context_managers.switch_db`
.. seealso::
Use :class:`~mongoengine.context_managers.switch_collection`
if you need to read from another collection
:param db_alias: The database alias to use for saving the document
"""
with switch_db(self.__class__, db_alias) as cls:
collection = cls._get_collection()
@@ -498,12 +450,11 @@ class Document(BaseDocument):
user.switch_collection('old-users')
user.save()
:param str collection_name: The database alias to use for saving the
document
If you need to read from another database see
:class:`~mongoengine.context_managers.switch_db`
.. seealso::
Use :class:`~mongoengine.context_managers.switch_db`
if you need to read from another database
:param collection_name: The database alias to use for saving the
document
"""
with switch_collection(self.__class__, collection_name) as cls:
collection = cls._get_collection()
@@ -554,13 +505,7 @@ class Document(BaseDocument):
for field in self._fields_ordered:
if not fields or field in fields:
try:
setattr(self, field, self._reload(field, obj[field]))
except KeyError:
# If field is removed from the database while the object
# is in memory, a reload would cause a KeyError
# i.e. obj.update(unset__field=1) followed by obj.reload()
delattr(self, field)
setattr(self, field, self._reload(field, obj[field]))
self._changed_fields = obj._changed_fields
self._created = False
@@ -573,9 +518,6 @@ class Document(BaseDocument):
if isinstance(value, BaseDict):
value = [(k, self._reload(k, v)) for k, v in value.items()]
value = BaseDict(value, self, key)
elif isinstance(value, EmbeddedDocumentList):
value = [self._reload(key, v) for v in value]
value = EmbeddedDocumentList(value, self, key)
elif isinstance(value, BaseList):
value = [self._reload(key, v) for v in value]
value = BaseList(value, self, key)
@@ -691,7 +633,7 @@ class Document(BaseDocument):
if cls._meta.get('abstract'):
return []
# get all the base classes, subclasses and siblings
# get all the base classes, subclasses and sieblings
classes = []
def get_classes(cls):

View File

@@ -5,8 +5,7 @@ from mongoengine.python_support import txt_type
__all__ = ('NotRegistered', 'InvalidDocumentError', 'LookUpError',
'DoesNotExist', 'MultipleObjectsReturned', 'InvalidQueryError',
'OperationError', 'NotUniqueError', 'FieldDoesNotExist',
'ValidationError')
'OperationError', 'NotUniqueError', 'ValidationError')
class NotRegistered(Exception):
@@ -41,10 +40,6 @@ class NotUniqueError(OperationError):
pass
class FieldDoesNotExist(Exception):
pass
class ValidationError(AssertionError):
"""Validation exception.

View File

@@ -39,13 +39,13 @@ __all__ = [
'FloatField', 'DecimalField', 'BooleanField', 'DateTimeField',
'ComplexDateTimeField', 'EmbeddedDocumentField', 'ObjectIdField',
'GenericEmbeddedDocumentField', 'DynamicField', 'ListField',
'SortedListField', 'EmbeddedDocumentListField', 'DictField',
'MapField', 'ReferenceField', 'CachedReferenceField',
'GenericReferenceField', 'BinaryField', 'GridFSError', 'GridFSProxy',
'FileField', 'ImageGridFsProxy', 'ImproperlyConfigured', 'ImageField',
'GeoPointField', 'PointField', 'LineStringField', 'PolygonField',
'SequenceField', 'UUIDField', 'MultiPointField', 'MultiLineStringField',
'MultiPolygonField', 'GeoJsonBaseField']
'SortedListField', 'DictField', 'MapField', 'ReferenceField',
'CachedReferenceField', 'GenericReferenceField', 'BinaryField',
'GridFSError', 'GridFSProxy', 'FileField', 'ImageGridFsProxy',
'ImproperlyConfigured', 'ImageField', 'GeoPointField', 'PointField',
'LineStringField', 'PolygonField', 'SequenceField', 'UUIDField',
'MultiPointField', 'MultiLineStringField', 'MultiPolygonField',
'GeoJsonBaseField']
RECURSIVE_REFERENCE_CONSTANT = 'self'
@@ -291,7 +291,7 @@ class DecimalField(BaseField):
:param max_value: Validation rule for the maximum acceptable value.
:param force_string: Store as a string.
:param precision: Number of decimal places to store.
:param rounding: The rounding rule from the python decimal library:
:param rounding: The rounding rule from the python decimal libary:
- decimal.ROUND_CEILING (towards Infinity)
- decimal.ROUND_DOWN (towards zero)
@@ -308,7 +308,7 @@ class DecimalField(BaseField):
self.min_value = min_value
self.max_value = max_value
self.force_string = force_string
self.precision = precision
self.precision = decimal.Decimal(".%s" % ("0" * precision))
self.rounding = rounding
super(DecimalField, self).__init__(**kwargs)
@@ -322,7 +322,7 @@ class DecimalField(BaseField):
value = decimal.Decimal("%s" % value)
except decimal.InvalidOperation:
return value
return value.quantize(decimal.Decimal(".%s" % ("0" * self.precision)), rounding=self.rounding)
return value.quantize(self.precision, rounding=self.rounding)
def to_mongo(self, value, use_db_field=True):
if value is None:
@@ -375,11 +375,11 @@ class DateTimeField(BaseField):
Uses the python-dateutil library if available alternatively use time.strptime
to parse the dates. Note: python-dateutil's parser is fully featured and when
installed you can utilise it to convert varying types of date formats into valid
installed you can utilise it to convert varing types of date formats into valid
python datetime objects.
Note: Microseconds are rounded to the nearest millisecond.
Pre UTC microsecond support is effectively broken.
Pre UTC microsecond support is effecively broken.
Use :class:`~mongoengine.fields.ComplexDateTimeField` if you
need accurate microsecond support.
"""
@@ -510,7 +510,7 @@ class ComplexDateTimeField(StringField):
def __get__(self, instance, owner):
data = super(ComplexDateTimeField, self).__get__(instance, owner)
if data is None:
return None if self.null else datetime.datetime.now()
return datetime.datetime.now()
if isinstance(data, datetime.datetime):
return data
return self._convert_from_string(data)
@@ -638,7 +638,7 @@ class DynamicField(BaseField):
Used by :class:`~mongoengine.DynamicDocument` to handle dynamic data"""
def to_mongo(self, value):
"""Convert a Python type to a MongoDB compatible type.
"""Convert a Python type to a MongoDBcompatible type.
"""
if isinstance(value, basestring):
@@ -728,32 +728,6 @@ class ListField(ComplexBaseField):
return super(ListField, self).prepare_query_value(op, value)
class EmbeddedDocumentListField(ListField):
"""A :class:`~mongoengine.ListField` designed specially to hold a list of
embedded documents to provide additional query helpers.
.. note::
The only valid list values are subclasses of
:class:`~mongoengine.EmbeddedDocument`.
.. versionadded:: 0.9
"""
def __init__(self, document_type, *args, **kwargs):
"""
:param document_type: The type of
:class:`~mongoengine.EmbeddedDocument` the list will hold.
:param args: Arguments passed directly into the parent
:class:`~mongoengine.ListField`.
:param kwargs: Keyword arguments passed directly into the parent
:class:`~mongoengine.ListField`.
"""
super(EmbeddedDocumentListField, self).__init__(
field=EmbeddedDocumentField(document_type), **kwargs
)
class SortedListField(ListField):
"""A ListField that sorts the contents of its list before writing to
@@ -885,7 +859,7 @@ class ReferenceField(BaseField):
Use the `reverse_delete_rule` to handle what should happen if the document
the field is referencing is deleted. EmbeddedDocuments, DictFields and
MapFields does not support reverse_delete_rule and an `InvalidDocumentError`
MapFields do not support reverse_delete_rules and an `InvalidDocumentError`
will be raised if trying to set on one of these Document / Field types.
The options are:
@@ -909,7 +883,7 @@ class ReferenceField(BaseField):
Bar.register_delete_rule(Foo, 'bar', NULLIFY)
.. note ::
`reverse_delete_rule` does not trigger pre / post delete signals to be
`reverse_delete_rules` do not trigger pre / post delete signals to be
triggered.
.. versionchanged:: 0.5 added `reverse_delete_rule`
@@ -1357,7 +1331,6 @@ class GridFSProxy(object):
def new_file(self, **kwargs):
self.newfile = self.fs.new_file(**kwargs)
self.grid_id = self.newfile._id
self._mark_as_changed()
def put(self, file_obj, **kwargs):
if self.grid_id:
@@ -1676,7 +1649,7 @@ class ImageField(FileField):
class SequenceField(BaseField):
"""Provides a sequential counter see:
"""Provides a sequental counter see:
http://www.mongodb.org/display/DOCS/Object+IDs#ObjectIDs-SequenceNumbers
.. note::
@@ -1777,7 +1750,7 @@ class SequenceField(BaseField):
def prepare_query_value(self, op, value):
"""
This method is overridden in order to convert the query value into to required
This method is overriden in order to convert the query value into to required
type. We need to do this in order to be able to successfully compare query
values passed as string, the base implementation returns the value as is.
"""
@@ -1887,7 +1860,6 @@ class PointField(GeoJsonBaseField):
to set the value.
Requires mongodb >= 2.4
.. versionadded:: 0.8
"""
_type = "Point"
@@ -1907,7 +1879,6 @@ class LineStringField(GeoJsonBaseField):
You can either pass a dict with the full information or a list of points.
Requires mongodb >= 2.4
.. versionadded:: 0.8
"""
_type = "LineString"
@@ -1930,7 +1901,6 @@ class PolygonField(GeoJsonBaseField):
holes.
Requires mongodb >= 2.4
.. versionadded:: 0.8
"""
_type = "Polygon"
@@ -1951,7 +1921,6 @@ class MultiPointField(GeoJsonBaseField):
to set the value.
Requires mongodb >= 2.6
.. versionadded:: 0.9
"""
_type = "MultiPoint"
@@ -1972,7 +1941,6 @@ class MultiLineStringField(GeoJsonBaseField):
You can either pass a dict with the full information or a list of points.
Requires mongodb >= 2.6
.. versionadded:: 0.9
"""
_type = "MultiLineString"
@@ -1986,7 +1954,7 @@ class MultiPolygonField(GeoJsonBaseField):
.. code-block:: js
{ "type" : "MultiPolygon" ,
{ "type" : "Polygon" ,
"coordinates" : [[
[[x1, y1], [x1, y1] ... [xn, yn]],
[[x1, y1], [x1, y1] ... [xn, yn]]
@@ -2000,7 +1968,6 @@ class MultiPolygonField(GeoJsonBaseField):
of Polygons.
Requires mongodb >= 2.6
.. versionadded:: 0.9
"""
_type = "MultiPolygon"

View File

@@ -66,6 +66,7 @@ class BaseQuerySet(object):
self._as_pymongo = False
self._as_pymongo_coerce = False
self._search_text = None
self._include_text_scores = False
# If inheritance is allowed, only return instances and instances of
# subclasses of the class being used
@@ -81,7 +82,6 @@ class BaseQuerySet(object):
self._skip = None
self._hint = -1 # Using -1 as None is a valid value for hint
self.only_fields = []
self._max_time_ms = None
def __call__(self, q_obj=None, class_check=True, slave_okay=False,
read_preference=None, **query):
@@ -158,8 +158,7 @@ class BaseQuerySet(object):
if queryset._as_pymongo:
return queryset._get_as_pymongo(queryset._cursor[key])
return queryset._document._from_son(queryset._cursor[key],
_auto_dereference=self._auto_dereference, only_fields=self.only_fields)
_auto_dereference=self._auto_dereference, only_fields=self.only_fields)
raise AttributeError
def __iter__(self):
@@ -192,7 +191,7 @@ class BaseQuerySet(object):
"""
return self.__call__(*q_objs, **query)
def search_text(self, text, language=None):
def search_text(self, text, language=None, include_text_scores=False):
"""
Start a text search, using text indexes.
Require: MongoDB server version 2.6+.
@@ -201,11 +200,14 @@ class BaseQuerySet(object):
for the search and the rules for the stemmer and tokenizer.
If not specified, the search uses the default language of the index.
For supported languages, see `Text Search Languages <http://docs.mongodb.org/manual/reference/text-search-languages/#text-search-languages>`.
:param include_text_scores: If True, automaticaly add a text_score attribute to Document.
"""
queryset = self.clone()
if queryset._search_text:
raise OperationError(
"It is not possible to use search_text two times.")
"Is not possible to use search_text two times.")
query_kwargs = SON({'$search': text})
if language:
@@ -215,6 +217,7 @@ class BaseQuerySet(object):
queryset._mongo_query = None
queryset._cursor_obj = None
queryset._search_text = text
queryset._include_text_scores = include_text_scores
return queryset
@@ -268,7 +271,7 @@ class BaseQuerySet(object):
.. note:: This requires two separate operations and therefore a
race condition exists. Because there are no transactions in
mongoDB other approaches should be investigated, to ensure you
don't accidentally duplicate data when using this method. This is
don't accidently duplicate data when using this method. This is
now scheduled to be removed before 1.0
:param write_concern: optional extra keyword arguments used if we
@@ -380,7 +383,7 @@ class BaseQuerySet(object):
self._document, documents=results, loaded=True)
return return_one and results[0] or results
def count(self, with_limit_and_skip=False):
def count(self, with_limit_and_skip=True):
"""Count the selected elements in the query.
:param with_limit_and_skip (optional): take any :meth:`limit` or
@@ -402,7 +405,6 @@ class BaseQuerySet(object):
will force an fsync on the primary server.
:param _from_doc_delete: True when called from document delete therefore
signals will have been triggered so don't loop.
:returns number of deleted documents
"""
queryset = self.clone()
@@ -432,8 +434,6 @@ class BaseQuerySet(object):
# references
for rule_entry in delete_rules:
document_cls, field_name = rule_entry
if document_cls._meta.get('abstract'):
continue
rule = doc._meta['delete_rules'][rule_entry]
if rule == DENY and document_cls.objects(
**{field_name + '__in': self}).count() > 0:
@@ -443,8 +443,6 @@ class BaseQuerySet(object):
for rule_entry in delete_rules:
document_cls, field_name = rule_entry
if document_cls._meta.get('abstract'):
continue
rule = doc._meta['delete_rules'][rule_entry]
if rule == CASCADE:
ref_q = document_cls.objects(**{field_name + '__in': self})
@@ -460,7 +458,7 @@ class BaseQuerySet(object):
write_concern=write_concern,
**{'pull_all__%s' % field_name: self})
result = queryset._collection.remove(queryset._query, **write_concern)
result = queryset._collection.remove(queryset._query, write_concern=write_concern)
return result["n"]
def update(self, upsert=False, multi=True, write_concern=None,
@@ -621,9 +619,7 @@ class BaseQuerySet(object):
doc_map[doc['_id']] = self._get_as_pymongo(doc)
else:
for doc in docs:
doc_map[doc['_id']] = self._document._from_son(doc,
only_fields=self.only_fields,
_auto_dereference=self._auto_dereference)
doc_map[doc['_id']] = self._document._from_son(doc, only_fields=self.only_fields)
return doc_map
@@ -676,7 +672,7 @@ class BaseQuerySet(object):
'_timeout', '_class_check', '_slave_okay', '_read_preference',
'_iter', '_scalar', '_as_pymongo', '_as_pymongo_coerce',
'_limit', '_skip', '_hint', '_auto_dereference',
'_search_text', 'only_fields', '_max_time_ms')
'_search_text', '_include_text_scores', 'only_fields')
for prop in copy_props:
val = getattr(self, prop)
@@ -762,29 +758,14 @@ class BaseQuerySet(object):
distinct = self._dereference(queryset._cursor.distinct(field), 1,
name=field, instance=self._document)
doc_field = self._document._fields.get(field.split('.', 1)[0])
instance = False
# We may need to cast to the correct type eg. ListField(EmbeddedDocumentField)
# We may need to cast to the correct type eg.
# ListField(EmbeddedDocumentField)
doc_field = getattr(
self._document._fields.get(field), "field", None)
instance = getattr(doc_field, "document_type", False)
EmbeddedDocumentField = _import_class('EmbeddedDocumentField')
ListField = _import_class('ListField')
GenericEmbeddedDocumentField = _import_class('GenericEmbeddedDocumentField')
if isinstance(doc_field, ListField):
doc_field = getattr(doc_field, "field", doc_field)
if isinstance(doc_field, (EmbeddedDocumentField, GenericEmbeddedDocumentField)):
instance = getattr(doc_field, "document_type", False)
# handle distinct on subdocuments
if '.' in field:
for field_part in field.split('.')[1:]:
# if looping on embedded document, get the document type instance
if instance and isinstance(doc_field, (EmbeddedDocumentField, GenericEmbeddedDocumentField)):
doc_field = instance
# now get the subdocument
doc_field = getattr(doc_field, field_part, doc_field)
# We may need to cast to the correct type eg. ListField(EmbeddedDocumentField)
if isinstance(doc_field, ListField):
doc_field = getattr(doc_field, "field", doc_field)
if isinstance(doc_field, (EmbeddedDocumentField, GenericEmbeddedDocumentField)):
instance = getattr(doc_field, "document_type", False)
GenericEmbeddedDocumentField = _import_class(
'GenericEmbeddedDocumentField')
if instance and isinstance(doc_field, (EmbeddedDocumentField,
GenericEmbeddedDocumentField)):
distinct = [instance(**doc) for doc in distinct]
@@ -988,13 +969,6 @@ class BaseQuerySet(object):
queryset._as_pymongo_coerce = coerce_types
return queryset
def max_time_ms(self, ms):
"""Wait `ms` milliseconds before killing the query on the server
:param ms: the number of milliseconds before killing the query on the server
"""
return self._chainable_method("max_time_ms", ms)
# JSON Helpers
def to_json(self, *args, **kwargs):
@@ -1008,8 +982,8 @@ class BaseQuerySet(object):
def aggregate(self, *pipeline, **kwargs):
"""
Perform a aggregate function based in your queryset params
:param pipeline: list of aggregation commands,\
Perform a aggreggate function based in your queryset params
:param pipeline: list of agreggation commands,
see: http://docs.mongodb.org/manual/core/aggregation-pipeline/
.. versionadded:: 0.9
@@ -1357,7 +1331,6 @@ class BaseQuerySet(object):
return self._get_as_pymongo(raw_doc)
doc = self._document._from_son(raw_doc,
_auto_dereference=self._auto_dereference, only_fields=self.only_fields)
if self._scalar:
return self._get_scalar(doc)
@@ -1366,7 +1339,6 @@ class BaseQuerySet(object):
def rewind(self):
"""Rewind the cursor to its unevaluated state.
.. versionadded:: 0.3
"""
self._iter = False
@@ -1394,11 +1366,11 @@ class BaseQuerySet(object):
if self._loaded_fields:
cursor_args['fields'] = self._loaded_fields.as_dict()
if self._search_text:
if self._include_text_scores:
if 'fields' not in cursor_args:
cursor_args['fields'] = {}
cursor_args['fields']['_text_score'] = {'$meta': "textScore"}
cursor_args['fields']['text_score'] = {'$meta': "textScore"}
return cursor_args
@@ -1613,7 +1585,9 @@ class BaseQuerySet(object):
continue
if key == '$text_score':
key_list.append(('_text_score', {'$meta': "textScore"}))
# automatically set to include text scores
self._include_text_scores = True
key_list.append(('text_score', {'$meta': "textScore"}))
continue
direction = pymongo.ASCENDING
@@ -1726,13 +1700,6 @@ class BaseQuerySet(object):
code)
return code
def _chainable_method(self, method_name, val):
queryset = self.clone()
method = getattr(queryset._cursor, method_name)
method(val)
setattr(queryset, "_" + method_name, val)
return queryset
# Deprecated
def ensure_index(self, **kwargs):
"""Deprecated use :func:`Document.ensure_index`"""

View File

@@ -94,7 +94,7 @@ class QuerySet(BaseQuerySet):
except StopIteration:
self._has_more = False
def count(self, with_limit_and_skip=False):
def count(self, with_limit_and_skip=True):
"""Count the selected elements in the query.
:param with_limit_and_skip (optional): take any :meth:`limit` or

View File

@@ -11,7 +11,7 @@ __all__ = ('query', 'update')
COMPARISON_OPERATORS = ('ne', 'gt', 'gte', 'lt', 'lte', 'in', 'nin', 'mod',
'all', 'size', 'exists', 'not', 'elemMatch', 'type')
'all', 'size', 'exists', 'not', 'elemMatch')
GEO_OPERATORS = ('within_distance', 'within_spherical_distance',
'within_box', 'within_polygon', 'near', 'near_sphere',
'max_distance', 'geo_within', 'geo_within_box',
@@ -26,7 +26,7 @@ MATCH_OPERATORS = (COMPARISON_OPERATORS + GEO_OPERATORS +
UPDATE_OPERATORS = ('set', 'unset', 'inc', 'dec', 'pop', 'push',
'push_all', 'pull', 'pull_all', 'add_to_set',
'set_on_insert', 'min', 'max')
'set_on_insert')
def query(_doc_cls=None, _field_operation=False, **query):
@@ -160,7 +160,7 @@ def query(_doc_cls=None, _field_operation=False, **query):
if isinstance(v, list):
value = [{k: val} for val in v]
if '$and' in mongo_query.keys():
mongo_query['$and'].extend(value)
mongo_query['$and'].append(value)
else:
mongo_query['$and'] = value

View File

@@ -29,7 +29,7 @@ class DuplicateQueryConditionsError(InvalidQueryError):
class SimplificationVisitor(QNodeVisitor):
"""Simplifies query trees by combining unnecessary 'and' connection nodes
"""Simplifies query trees by combinging unnecessary 'and' connection nodes
into a single Q-object.
"""

View File

@@ -1,2 +1 @@
pymongo>=2.7.1
nose

View File

@@ -38,7 +38,7 @@ CLASSIFIERS = [
'Operating System :: OS Independent',
'Programming Language :: Python',
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.6.6",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.2",

0
tests/async/__init__.py Normal file
View File

View File

@@ -0,0 +1,36 @@
from mongoengine import *
import motor
import mongoengine.connection
from mongoengine.connection import get_db, get_connection, ConnectionError
try:
import unittest2 as unittest
except ImportError:
import unittest
class ConnectionTest(unittest.TestCase):
def setUp(self):
mongoengine.connection._connection_settings = {}
mongoengine.connection._connections = {}
mongoengine.connection._dbs = {}
def test_register_connection(self):
"""
Ensure that the connect() method works properly.
"""
register_connection('asyncdb', 'mongoengineasynctest', async=True)
self.assertEqual(
mongoengine.connection._connection_settings['asyncdb']['name'],
'mongoengineasynctest')
self.assertTrue(
mongoengine.connection._connection_settings['asyncdb']['async'])
conn = get_connection('asyncdb')
self.assertTrue(isinstance(conn, motor.MotorClient))
db = get_db('asyncdb')
self.assertTrue(isinstance(db, motor.MotorDatabase))
self.assertEqual(db.name, 'mongoengineasynctest')

View File

@@ -81,13 +81,6 @@ class DynamicTest(unittest.TestCase):
obj = collection.find_one()
self.assertEqual(sorted(obj.keys()), ['_cls', '_id', 'name'])
def test_reload_after_unsetting(self):
p = self.Person()
p.misc = 22
p.save()
p.update(unset__misc=1)
p.reload()
def test_dynamic_document_queries(self):
"""Ensure we can query dynamic fields"""
p = self.Person()

View File

@@ -18,7 +18,7 @@ __all__ = ("IndexesTest", )
class IndexesTest(unittest.TestCase):
def setUp(self):
self.connection = connect(db='mongoenginetest')
connect(db='mongoenginetest')
self.db = get_db()
class Person(Document):
@@ -495,12 +495,9 @@ class IndexesTest(unittest.TestCase):
self.assertEqual(BlogPost.objects.hint([('ZZ', 1)]).count(), 10)
if pymongo.version >= '2.8':
self.assertEqual(BlogPost.objects.hint('tags').count(), 10)
else:
def invalid_index():
BlogPost.objects.hint('tags')
self.assertRaises(TypeError, invalid_index)
def invalid_index():
BlogPost.objects.hint('tags')
self.assertRaises(TypeError, invalid_index)
def invalid_index_2():
return BlogPost.objects.hint(('tags', 1))
@@ -580,38 +577,6 @@ class IndexesTest(unittest.TestCase):
BlogPost.drop_collection()
def test_unique_embedded_document_in_list(self):
"""
Ensure that the uniqueness constraints are applied to fields in
embedded documents, even when the embedded documents in in a
list field.
"""
class SubDocument(EmbeddedDocument):
year = IntField(db_field='yr')
slug = StringField(unique=True)
class BlogPost(Document):
title = StringField()
subs = ListField(EmbeddedDocumentField(SubDocument))
BlogPost.drop_collection()
post1 = BlogPost(
title='test1', subs=[
SubDocument(year=2009, slug='conflict'),
SubDocument(year=2009, slug='conflict')
]
)
post1.save()
post2 = BlogPost(
title='test2', subs=[SubDocument(year=2014, slug='conflict')]
)
self.assertRaises(NotUniqueError, post2.save)
BlogPost.drop_collection()
def test_unique_with_embedded_document_and_embedded_unique(self):
"""Ensure that uniqueness constraints are applied to fields on
embedded documents. And work with unique_with as well.
@@ -798,33 +763,6 @@ class IndexesTest(unittest.TestCase):
key = indexes["title_text"]["key"]
self.assertTrue(('_fts', 'text') in key)
def test_indexes_after_database_drop(self):
"""
Test to ensure that indexes are re-created on a collection even
after the database has been dropped.
Issue #812
"""
class BlogPost(Document):
title = StringField()
slug = StringField(unique=True)
BlogPost.drop_collection()
# Create Post #1
post1 = BlogPost(title='test1', slug='test')
post1.save()
# Drop the Database
self.connection.drop_database(BlogPost._get_db().name)
# Re-create Post #1
post1 = BlogPost(title='test1', slug='test')
post1.save()
# Create Post #2
post2 = BlogPost(title='test2', slug='test')
self.assertRaises(NotUniqueError, post2.save)
if __name__ == '__main__':
unittest.main()

View File

@@ -397,16 +397,6 @@ class InheritanceTest(unittest.TestCase):
meta = {'abstract': True}
self.assertRaises(ValueError, create_bad_abstract)
def test_abstract_embedded_documents(self):
# 789: EmbeddedDocument shouldn't inherit abstract
class A(EmbeddedDocument):
meta = {"abstract": True}
class B(A):
pass
self.assertFalse(B._meta["abstract"])
def test_inherited_collections(self):
"""Ensure that subclassed documents don't override parents'
collections

View File

@@ -9,7 +9,7 @@ import unittest
import uuid
from datetime import datetime
from bson import DBRef, ObjectId
from bson import DBRef
from tests.fixtures import (PickleEmbedded, PickleTest, PickleSignalsTest,
PickleDyanmicEmbedded, PickleDynamicTest)
@@ -34,21 +34,15 @@ class InstanceTest(unittest.TestCase):
connect(db='mongoenginetest')
self.db = get_db()
class Job(EmbeddedDocument):
name = StringField()
years = IntField()
class Person(Document):
name = StringField()
age = IntField()
job = EmbeddedDocumentField(Job)
non_field = True
meta = {"allow_inheritance": True}
self.Person = Person
self.Job = Job
def tearDown(self):
for collection in self.db.collection_names():
@@ -56,11 +50,6 @@ class InstanceTest(unittest.TestCase):
continue
self.db.drop_collection(collection)
def assertDbEqual(self, docs):
self.assertEqual(
list(self.Person._get_collection().find().sort("id")),
sorted(docs, key=lambda doc: doc["_id"]))
def test_capped_collection(self):
"""Ensure that capped collections work properly.
"""
@@ -114,19 +103,6 @@ class InstanceTest(unittest.TestCase):
self.assertEqual('<Article: привет мир>', repr(doc))
def test_repr_none(self):
"""Ensure None values handled correctly
"""
class Article(Document):
title = StringField()
def __str__(self):
return None
doc = Article(title=u'привет мир')
self.assertEqual('<Article: None>', repr(doc))
def test_queryset_resurrects_dropped_collection(self):
self.Person.drop_collection()
@@ -146,18 +122,10 @@ class InstanceTest(unittest.TestCase):
"""
class Animal(Document):
meta = {'allow_inheritance': True}
class Fish(Animal):
pass
class Mammal(Animal):
pass
class Dog(Mammal):
pass
class Human(Mammal):
pass
class Fish(Animal): pass
class Mammal(Animal): pass
class Dog(Mammal): pass
class Human(Mammal): pass
class Zoo(Document):
animals = ListField(ReferenceField(Animal))
@@ -469,7 +437,7 @@ class InstanceTest(unittest.TestCase):
f.reload()
except Foo.DoesNotExist:
pass
except Exception:
except Exception as ex:
self.assertFalse("Threw wrong exception")
f.save()
@@ -478,13 +446,13 @@ class InstanceTest(unittest.TestCase):
f.reload()
except Foo.DoesNotExist:
pass
except Exception:
except Exception as ex:
self.assertFalse("Threw wrong exception")
def test_dictionary_access(self):
"""Ensure that dictionary-style field access works properly.
"""
person = self.Person(name='Test User', age=30, job=self.Job())
person = self.Person(name='Test User', age=30)
self.assertEqual(person['name'], 'Test User')
self.assertRaises(KeyError, person.__getitem__, 'salary')
@@ -494,7 +462,7 @@ class InstanceTest(unittest.TestCase):
self.assertEqual(person['name'], 'Another User')
# Length = length(assigned fields + id)
self.assertEqual(len(person), 5)
self.assertEqual(len(person), 4)
self.assertTrue('age' in person)
person.age = None
@@ -513,9 +481,8 @@ class InstanceTest(unittest.TestCase):
self.assertEqual(Person(name="Bob", age=35).to_mongo().keys(),
['_cls', 'name', 'age'])
self.assertEqual(
Employee(name="Bob", age=35, salary=0).to_mongo().keys(),
['_cls', 'name', 'age', 'salary'])
self.assertEqual(Employee(name="Bob", age=35, salary=0).to_mongo().keys(),
['_cls', 'name', 'age', 'salary'])
def test_embedded_document_to_mongo_id(self):
class SubDoc(EmbeddedDocument):
@@ -650,64 +617,6 @@ class InstanceTest(unittest.TestCase):
t = TestDocument(doc=TestEmbeddedDocument(x=15, y=35, z=5))
t.save(clean=False)
def test_modify_empty(self):
doc = self.Person(name="bob", age=10).save()
self.assertRaises(
InvalidDocumentError, lambda: self.Person().modify(set__age=10))
self.assertDbEqual([dict(doc.to_mongo())])
def test_modify_invalid_query(self):
doc1 = self.Person(name="bob", age=10).save()
doc2 = self.Person(name="jim", age=20).save()
docs = [dict(doc1.to_mongo()), dict(doc2.to_mongo())]
self.assertRaises(
InvalidQueryError,
lambda: doc1.modify(dict(id=doc2.id), set__value=20))
self.assertDbEqual(docs)
def test_modify_match_another_document(self):
doc1 = self.Person(name="bob", age=10).save()
doc2 = self.Person(name="jim", age=20).save()
docs = [dict(doc1.to_mongo()), dict(doc2.to_mongo())]
assert not doc1.modify(dict(name=doc2.name), set__age=100)
self.assertDbEqual(docs)
def test_modify_not_exists(self):
doc1 = self.Person(name="bob", age=10).save()
doc2 = self.Person(id=ObjectId(), name="jim", age=20)
docs = [dict(doc1.to_mongo())]
assert not doc2.modify(dict(name=doc2.name), set__age=100)
self.assertDbEqual(docs)
def test_modify_update(self):
other_doc = self.Person(name="bob", age=10).save()
doc = self.Person(
name="jim", age=20, job=self.Job(name="10gen", years=3)).save()
doc_copy = doc._from_son(doc.to_mongo())
# these changes must go away
doc.name = "liza"
doc.job.name = "Google"
doc.job.years = 3
assert doc.modify(
set__age=21, set__job__name="MongoDB", unset__job__years=True)
doc_copy.age = 21
doc_copy.job.name = "MongoDB"
del doc_copy.job.years
assert doc.to_json() == doc_copy.to_json()
assert doc._get_changed_fields() == []
self.assertDbEqual([dict(other_doc.to_mongo()), dict(doc.to_mongo())])
def test_save(self):
"""Ensure that a document may be saved in the database.
"""
@@ -946,7 +855,7 @@ class InstanceTest(unittest.TestCase):
w1 = Widget(toggle=False, save_id=UUID(1))
# ignore save_condition on new record creation
w1.save(save_condition={'save_id': UUID(42)})
w1.save(save_condition={'save_id':UUID(42)})
w1.reload()
self.assertFalse(w1.toggle)
self.assertEqual(w1.save_id, UUID(1))
@@ -956,7 +865,7 @@ class InstanceTest(unittest.TestCase):
flip(w1)
self.assertTrue(w1.toggle)
self.assertEqual(w1.count, 1)
w1.save(save_condition={'save_id': UUID(42)})
w1.save(save_condition={'save_id':UUID(42)})
w1.reload()
self.assertFalse(w1.toggle)
self.assertEqual(w1.count, 0)
@@ -965,7 +874,7 @@ class InstanceTest(unittest.TestCase):
flip(w1)
self.assertTrue(w1.toggle)
self.assertEqual(w1.count, 1)
w1.save(save_condition={'save_id': UUID(1)})
w1.save(save_condition={'save_id':UUID(1)})
w1.reload()
self.assertTrue(w1.toggle)
self.assertEqual(w1.count, 1)
@@ -978,25 +887,25 @@ class InstanceTest(unittest.TestCase):
flip(w1)
w1.save_id = UUID(2)
w1.save(save_condition={'save_id': old_id})
w1.save(save_condition={'save_id':old_id})
w1.reload()
self.assertFalse(w1.toggle)
self.assertEqual(w1.count, 2)
flip(w2)
flip(w2)
w2.save(save_condition={'save_id': old_id})
w2.save(save_condition={'save_id':old_id})
w2.reload()
self.assertFalse(w2.toggle)
self.assertEqual(w2.count, 2)
# save_condition uses mongoengine-style operator syntax
flip(w1)
w1.save(save_condition={'count__lt': w1.count})
w1.save(save_condition={'count__lt':w1.count})
w1.reload()
self.assertTrue(w1.toggle)
self.assertEqual(w1.count, 3)
flip(w1)
w1.save(save_condition={'count__gte': w1.count})
w1.save(save_condition={'count__gte':w1.count})
w1.reload()
self.assertTrue(w1.toggle)
self.assertEqual(w1.count, 3)
@@ -1442,8 +1351,7 @@ class InstanceTest(unittest.TestCase):
self.assertEqual(str(person_obj['_id']), '497ce96f395f2f052a494fd4')
def test_save_custom_pk(self):
"""
Ensure that a document may be saved with a custom _id using pk alias.
"""Ensure that a document may be saved with a custom _id using pk alias.
"""
# Create person object and save it to the database
person = self.Person(name='Test User', age=30,
@@ -1529,15 +1437,9 @@ class InstanceTest(unittest.TestCase):
p4 = Page(comments=[Comment(user=u2, comment="Heavy Metal song")])
p4.save()
self.assertEqual(
[p1, p2],
list(Page.objects.filter(comments__user=u1)))
self.assertEqual(
[p1, p2, p4],
list(Page.objects.filter(comments__user=u2)))
self.assertEqual(
[p1, p3],
list(Page.objects.filter(comments__user=u3)))
self.assertEqual([p1, p2], list(Page.objects.filter(comments__user=u1)))
self.assertEqual([p1, p2, p4], list(Page.objects.filter(comments__user=u2)))
self.assertEqual([p1, p3], list(Page.objects.filter(comments__user=u3)))
def test_save_embedded_document(self):
"""Ensure that a document with an embedded document field may be
@@ -1612,8 +1514,7 @@ class InstanceTest(unittest.TestCase):
self.assertEqual(promoted_employee.age, 50)
# Ensure that the 'details' embedded object saved correctly
self.assertEqual(
promoted_employee.details.position, 'Senior Developer')
self.assertEqual(promoted_employee.details.position, 'Senior Developer')
# Test removal
promoted_employee.details = None
@@ -1749,8 +1650,7 @@ class InstanceTest(unittest.TestCase):
post.save()
reviewer.delete()
# No effect on the BlogPost
self.assertEqual(BlogPost.objects.count(), 1)
self.assertEqual(BlogPost.objects.count(), 1) # No effect on the BlogPost
self.assertEqual(BlogPost.objects.get().reviewer, None)
# Delete the Person, which should lead to deletion of the BlogPost, too
@@ -1799,10 +1699,8 @@ class InstanceTest(unittest.TestCase):
class BlogPost(Document):
content = StringField()
authors = ListField(ReferenceField(
self.Person, reverse_delete_rule=CASCADE))
reviewers = ListField(ReferenceField(
self.Person, reverse_delete_rule=NULLIFY))
authors = ListField(ReferenceField(self.Person, reverse_delete_rule=CASCADE))
reviewers = ListField(ReferenceField(self.Person, reverse_delete_rule=NULLIFY))
self.Person.drop_collection()
@@ -1897,17 +1795,13 @@ class InstanceTest(unittest.TestCase):
self.assertEqual(Bar.objects.count(), 1) # No effect on the BlogPost
self.assertEqual(Bar.objects.get().foo, None)
def test_invalid_reverse_delete_rule_raise_errors(self):
def test_invalid_reverse_delete_rules_raise_errors(self):
def throw_invalid_document_error():
class Blog(Document):
content = StringField()
authors = MapField(ReferenceField(
self.Person, reverse_delete_rule=CASCADE))
reviewers = DictField(
field=ReferenceField(
self.Person,
reverse_delete_rule=NULLIFY))
authors = MapField(ReferenceField(self.Person, reverse_delete_rule=CASCADE))
reviewers = DictField(field=ReferenceField(self.Person, reverse_delete_rule=NULLIFY))
self.assertRaises(InvalidDocumentError, throw_invalid_document_error)
@@ -1916,8 +1810,7 @@ class InstanceTest(unittest.TestCase):
father = ReferenceField('Person', reverse_delete_rule=DENY)
mother = ReferenceField('Person', reverse_delete_rule=DENY)
self.assertRaises(
InvalidDocumentError, throw_invalid_document_error_embedded)
self.assertRaises(InvalidDocumentError, throw_invalid_document_error_embedded)
def test_reverse_delete_rule_cascade_recurs(self):
"""Ensure that a chain of documents is also deleted upon cascaded
@@ -1939,16 +1832,16 @@ class InstanceTest(unittest.TestCase):
author = self.Person(name='Test User')
author.save()
post = BlogPost(content='Watched some TV')
post = BlogPost(content = 'Watched some TV')
post.author = author
post.save()
comment = Comment(text='Kudos.')
comment = Comment(text = 'Kudos.')
comment.post = post
comment.save()
# Delete the Person, which should lead to deletion of the BlogPost,
# and, recursively to the Comment, too
# Delete the Person, which should lead to deletion of the BlogPost, and,
# recursively to the Comment, too
author.delete()
self.assertEqual(Comment.objects.count(), 0)
@@ -1971,7 +1864,7 @@ class InstanceTest(unittest.TestCase):
author = self.Person(name='Test User')
author.save()
post = BlogPost(content='Watched some TV')
post = BlogPost(content = 'Watched some TV')
post.author = author
post.save()
@@ -2087,8 +1980,7 @@ class InstanceTest(unittest.TestCase):
def test_dynamic_document_pickle(self):
pickle_doc = PickleDynamicTest(
name="test", number=1, string="One", lists=['1', '2'])
pickle_doc = PickleDynamicTest(name="test", number=1, string="One", lists=['1', '2'])
pickle_doc.embedded = PickleDyanmicEmbedded(foo="Bar")
pickled_doc = pickle.dumps(pickle_doc) # make sure pickling works even before the doc is saved
@@ -2110,8 +2002,7 @@ class InstanceTest(unittest.TestCase):
pickle_doc.embedded._dynamic_fields.keys())
def test_picklable_on_signals(self):
pickle_doc = PickleSignalsTest(
number=1, string="One", lists=['1', '2'])
pickle_doc = PickleSignalsTest(number=1, string="One", lists=['1', '2'])
pickle_doc.embedded = PickleEmbedded()
pickle_doc.save()
pickle_doc.delete()
@@ -2266,15 +2157,9 @@ class InstanceTest(unittest.TestCase):
self.assertEqual(AuthorBooks._get_db(), get_db("testdb-3"))
# Collections
self.assertEqual(
User._get_collection(),
get_db("testdb-1")[User._get_collection_name()])
self.assertEqual(
Book._get_collection(),
get_db("testdb-2")[Book._get_collection_name()])
self.assertEqual(
AuthorBooks._get_collection(),
get_db("testdb-3")[AuthorBooks._get_collection_name()])
self.assertEqual(User._get_collection(), get_db("testdb-1")[User._get_collection_name()])
self.assertEqual(Book._get_collection(), get_db("testdb-2")[Book._get_collection_name()])
self.assertEqual(AuthorBooks._get_collection(), get_db("testdb-3")[AuthorBooks._get_collection_name()])
def test_db_alias_overrides(self):
"""db_alias can be overriden
@@ -2443,6 +2328,30 @@ class InstanceTest(unittest.TestCase):
group = Group.objects.first()
self.assertEqual("hello - default", group.name)
def test_no_overwritting_no_data_loss(self):
class User(Document):
username = StringField(primary_key=True)
name = StringField()
@property
def foo(self):
return True
User.drop_collection()
user = User(username="Ross", foo="bar")
self.assertTrue(user.foo)
User._get_collection().save({"_id": "Ross", "foo": "Bar",
"data": [1, 2, 3]})
user = User.objects.first()
self.assertEqual("Ross", user.username)
self.assertEqual(True, user.foo)
self.assertEqual("Bar", user._data["foo"])
self.assertEqual([1, 2, 3], user._data["data"])
def test_spaces_in_keys(self):
class Embedded(DynamicEmbeddedDocument):
@@ -2518,10 +2427,6 @@ class InstanceTest(unittest.TestCase):
doc_name = StringField()
doc = EmbeddedDocumentField(Embedded)
def __eq__(self, other):
return (self.doc_name == other.doc_name and
self.doc == other.doc)
classic_doc = Doc(doc_name="my doc", doc=Embedded(name="embedded doc"))
dict_doc = Doc(**{"doc_name": "my doc",
"doc": {"name": "embedded doc"}})
@@ -2538,10 +2443,6 @@ class InstanceTest(unittest.TestCase):
doc_name = StringField()
docs = ListField(EmbeddedDocumentField(Embedded))
def __eq__(self, other):
return (self.doc_name == other.doc_name and
self.docs == other.docs)
classic_doc = Doc(doc_name="my doc", docs=[
Embedded(name="embedded doc1"),
Embedded(name="embedded doc2")])
@@ -2636,9 +2537,7 @@ class InstanceTest(unittest.TestCase):
system.save()
system = NodesSystem.objects.first()
self.assertEqual(
"UNDEFINED",
system.nodes["node"].parameters["param"].macros["test"].value)
self.assertEqual("UNDEFINED", system.nodes["node"].parameters["param"].macros["test"].value)
def test_embedded_document_equality(self):
@@ -2744,60 +2643,5 @@ class InstanceTest(unittest.TestCase):
self.assertEquals(p4.height, 189)
self.assertEquals(Person.objects(height=189).count(), 1)
def test_from_son(self):
# 771
class MyPerson(self.Person):
meta = dict(shard_key=["id"])
p = MyPerson.from_json('{"name": "name", "age": 27}', created=True)
self.assertEquals(p.id, None)
p.id = "12345" # in case it is not working: "OperationError: Shard Keys are immutable..." will be raised here
p = MyPerson._from_son({"name": "name", "age": 27}, created=True)
self.assertEquals(p.id, None)
p.id = "12345" # in case it is not working: "OperationError: Shard Keys are immutable..." will be raised here
def test_null_field(self):
# 734
class User(Document):
name = StringField()
height = IntField(default=184, null=True)
str_fld = StringField(null=True)
int_fld = IntField(null=True)
flt_fld = FloatField(null=True)
dt_fld = DateTimeField(null=True)
cdt_fld = ComplexDateTimeField(null=True)
User.objects.delete()
u = User(name='user')
u.save()
u_from_db = User.objects.get(name='user')
u_from_db.height = None
u_from_db.save()
self.assertEquals(u_from_db.height, None)
# 864
self.assertEqual(u_from_db.str_fld, None)
self.assertEqual(u_from_db.int_fld, None)
self.assertEqual(u_from_db.flt_fld, None)
self.assertEqual(u_from_db.dt_fld, None)
self.assertEqual(u_from_db.cdt_fld, None)
# 735
User.objects.delete()
u = User(name='user')
u.save()
User.objects(name='user').update_one(set__height=None, upsert=True)
u_from_db = User.objects.get(name='user')
self.assertEquals(u_from_db.height, None)
def test_not_saved_eq(self):
"""Ensure we can compare documents not saved.
"""
class Person(Document):
pass
p = Person()
p1 = Person()
self.assertNotEqual(p, p1)
self.assertEqual(p, p)
if __name__ == '__main__':
unittest.main()

View File

@@ -51,10 +51,6 @@ class TestJson(unittest.TestCase):
string = StringField()
embedded_field = EmbeddedDocumentField(Embedded)
def __eq__(self, other):
return (self.string == other.string and
self.embedded_field == other.embedded_field)
doc = Doc(string="Hi", embedded_field=Embedded(string="Hi"))
doc_json = doc.to_json(sort_keys=True, separators=(',', ':'))
@@ -103,10 +99,6 @@ class TestJson(unittest.TestCase):
generic_embedded_document_field = GenericEmbeddedDocumentField(
default=lambda: EmbeddedDoc())
def __eq__(self, other):
import json
return json.loads(self.to_json()) == json.loads(other.to_json())
doc = Doc()
self.assertEqual(doc, Doc.from_json(doc.to_json()))

View File

@@ -18,11 +18,11 @@ from bson import Binary, DBRef, ObjectId
from mongoengine import *
from mongoengine.connection import get_db
from mongoengine.base import _document_registry
from mongoengine.base.datastructures import BaseDict, EmbeddedDocumentList
from mongoengine.base.datastructures import BaseDict
from mongoengine.errors import NotRegistered
from mongoengine.python_support import PY3, b, bin_type
__all__ = ("FieldTest", "EmbeddedDocumentListFieldTestCase")
__all__ = ("FieldTest", )
class FieldTest(unittest.TestCase):
@@ -1176,11 +1176,6 @@ class FieldTest(unittest.TestCase):
post.reload()
self.assertEqual('updated', post.info['title'])
post.info.setdefault('authors', [])
post.save()
post.reload()
self.assertEqual([], post.info['authors'])
BlogPost.drop_collection()
def test_dictfield_strict(self):
@@ -1819,7 +1814,7 @@ class FieldTest(unittest.TestCase):
Animal.drop_collection()
Ocorrence.drop_collection()
a = Animal(name="Leopard", tag="heavy",
a = Animal(nam="Leopard", tag="heavy",
owner=Owner(tp='u', name="Wilson Júnior")
)
a.save()
@@ -1869,7 +1864,7 @@ class FieldTest(unittest.TestCase):
Animal.drop_collection()
Ocorrence.drop_collection()
a = Animal(name="Leopard", tag="heavy",
a = Animal(nam="Leopard", tag="heavy",
owner=Owner(tags=['cool', 'funny'],
name="Wilson Júnior")
)
@@ -1979,14 +1974,14 @@ class FieldTest(unittest.TestCase):
def test_recursive_embedding(self):
"""Ensure that EmbeddedDocumentFields can contain their own documents.
"""
class TreeNode(EmbeddedDocument):
name = StringField()
children = ListField(EmbeddedDocumentField('self'))
class Tree(Document):
name = StringField()
children = ListField(EmbeddedDocumentField('TreeNode'))
class TreeNode(EmbeddedDocument):
name = StringField()
children = ListField(EmbeddedDocumentField('self'))
Tree.drop_collection()
tree = Tree(name="Tree")
@@ -2451,79 +2446,6 @@ class FieldTest(unittest.TestCase):
Shirt.drop_collection()
def test_choices_validation_documents(self):
"""
Ensure fields with document choices validate given a valid choice.
"""
class UserComments(EmbeddedDocument):
author = StringField()
message = StringField()
class BlogPost(Document):
comments = ListField(
GenericEmbeddedDocumentField(choices=(UserComments,))
)
# Ensure Validation Passes
BlogPost(comments=[
UserComments(author='user2', message='message2'),
]).save()
def test_choices_validation_documents_invalid(self):
"""
Ensure fields with document choices validate given an invalid choice.
This should throw a ValidationError exception.
"""
class UserComments(EmbeddedDocument):
author = StringField()
message = StringField()
class ModeratorComments(EmbeddedDocument):
author = StringField()
message = StringField()
class BlogPost(Document):
comments = ListField(
GenericEmbeddedDocumentField(choices=(UserComments,))
)
# Single Entry Failure
post = BlogPost(comments=[
ModeratorComments(author='mod1', message='message1'),
])
self.assertRaises(ValidationError, post.save)
# Mixed Entry Failure
post = BlogPost(comments=[
ModeratorComments(author='mod1', message='message1'),
UserComments(author='user2', message='message2'),
])
self.assertRaises(ValidationError, post.save)
def test_choices_validation_documents_inheritance(self):
"""
Ensure fields with document choices validate given subclass of choice.
"""
class Comments(EmbeddedDocument):
meta = {
'abstract': True
}
author = StringField()
message = StringField()
class UserComments(Comments):
pass
class BlogPost(Document):
comments = ListField(
GenericEmbeddedDocumentField(choices=(Comments,))
)
# Save Valid EmbeddedDocument Type
BlogPost(comments=[
UserComments(author='user2', message='message2'),
]).save()
def test_choices_get_field_display(self):
"""Test dynamic helper for returning the display value of a choices
field.
@@ -2795,11 +2717,9 @@ class FieldTest(unittest.TestCase):
class Animal(Document):
id = SequenceField(primary_key=True)
name = StringField()
class Person(Document):
id = SequenceField(primary_key=True)
name = StringField()
self.db['mongoengine.counters'].drop()
Animal.drop_collection()
@@ -3137,495 +3057,6 @@ class FieldTest(unittest.TestCase):
self.assertEquals(Animal.objects(_cls__in=["Animal.Mammal.Dog", "Animal.Fish"]).count(), 2)
self.assertEquals(Animal.objects(_cls__in=["Animal.Fish.Guppy"]).count(), 0)
def test_sparse_field(self):
class Doc(Document):
name = StringField(required=False, unique=True, sparse=True)
try:
Doc().save()
Doc().save()
except Exception:
self.fail()
def test_undefined_field_exception(self):
"""Tests if a `FieldDoesNotExist` exception is raised when trying to
set a value to a field that's not defined.
"""
class Doc(Document):
foo = StringField(db_field='f')
def test():
Doc(bar='test')
self.assertRaises(FieldDoesNotExist, test)
class EmbeddedDocumentListFieldTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.db = connect(db='EmbeddedDocumentListFieldTestCase')
class Comments(EmbeddedDocument):
author = StringField()
message = StringField()
class BlogPost(Document):
comments = EmbeddedDocumentListField(Comments)
cls.Comments = Comments
cls.BlogPost = BlogPost
def setUp(self):
"""
Create two BlogPost entries in the database, each with
several EmbeddedDocuments.
"""
self.post1 = self.BlogPost(comments=[
self.Comments(author='user1', message='message1'),
self.Comments(author='user2', message='message1')
]).save()
self.post2 = self.BlogPost(comments=[
self.Comments(author='user2', message='message2'),
self.Comments(author='user2', message='message3'),
self.Comments(author='user3', message='message1')
]).save()
def tearDown(self):
self.BlogPost.drop_collection()
@classmethod
def tearDownClass(cls):
cls.db.drop_database('EmbeddedDocumentListFieldTestCase')
def test_no_keyword_filter(self):
"""
Tests the filter method of a List of Embedded Documents
with a no keyword.
"""
filtered = self.post1.comments.filter()
# Ensure nothing was changed
# < 2.6 Incompatible >
# self.assertListEqual(filtered, self.post1.comments)
self.assertEqual(filtered, self.post1.comments)
def test_single_keyword_filter(self):
"""
Tests the filter method of a List of Embedded Documents
with a single keyword.
"""
filtered = self.post1.comments.filter(author='user1')
# Ensure only 1 entry was returned.
self.assertEqual(len(filtered), 1)
# Ensure the entry returned is the correct entry.
self.assertEqual(filtered[0].author, 'user1')
def test_multi_keyword_filter(self):
"""
Tests the filter method of a List of Embedded Documents
with multiple keywords.
"""
filtered = self.post2.comments.filter(
author='user2', message='message2'
)
# Ensure only 1 entry was returned.
self.assertEqual(len(filtered), 1)
# Ensure the entry returned is the correct entry.
self.assertEqual(filtered[0].author, 'user2')
self.assertEqual(filtered[0].message, 'message2')
def test_chained_filter(self):
"""
Tests chained filter methods of a List of Embedded Documents
"""
filtered = self.post2.comments.filter(author='user2').filter(
message='message2'
)
# Ensure only 1 entry was returned.
self.assertEqual(len(filtered), 1)
# Ensure the entry returned is the correct entry.
self.assertEqual(filtered[0].author, 'user2')
self.assertEqual(filtered[0].message, 'message2')
def test_unknown_keyword_filter(self):
"""
Tests the filter method of a List of Embedded Documents
when the keyword is not a known keyword.
"""
# < 2.6 Incompatible >
# with self.assertRaises(AttributeError):
# self.post2.comments.filter(year=2)
self.assertRaises(AttributeError, self.post2.comments.filter, year=2)
def test_no_keyword_exclude(self):
"""
Tests the exclude method of a List of Embedded Documents
with a no keyword.
"""
filtered = self.post1.comments.exclude()
# Ensure everything was removed
# < 2.6 Incompatible >
# self.assertListEqual(filtered, [])
self.assertEqual(filtered, [])
def test_single_keyword_exclude(self):
"""
Tests the exclude method of a List of Embedded Documents
with a single keyword.
"""
excluded = self.post1.comments.exclude(author='user1')
# Ensure only 1 entry was returned.
self.assertEqual(len(excluded), 1)
# Ensure the entry returned is the correct entry.
self.assertEqual(excluded[0].author, 'user2')
def test_multi_keyword_exclude(self):
"""
Tests the exclude method of a List of Embedded Documents
with multiple keywords.
"""
excluded = self.post2.comments.exclude(
author='user3', message='message1'
)
# Ensure only 2 entries were returned.
self.assertEqual(len(excluded), 2)
# Ensure the entries returned are the correct entries.
self.assertEqual(excluded[0].author, 'user2')
self.assertEqual(excluded[1].author, 'user2')
def test_non_matching_exclude(self):
"""
Tests the exclude method of a List of Embedded Documents
when the keyword does not match any entries.
"""
excluded = self.post2.comments.exclude(author='user4')
# Ensure the 3 entries still exist.
self.assertEqual(len(excluded), 3)
def test_unknown_keyword_exclude(self):
"""
Tests the exclude method of a List of Embedded Documents
when the keyword is not a known keyword.
"""
# < 2.6 Incompatible >
# with self.assertRaises(AttributeError):
# self.post2.comments.exclude(year=2)
self.assertRaises(AttributeError, self.post2.comments.exclude, year=2)
def test_chained_filter_exclude(self):
"""
Tests the exclude method after a filter method of a List of
Embedded Documents.
"""
excluded = self.post2.comments.filter(author='user2').exclude(
message='message2'
)
# Ensure only 1 entry was returned.
self.assertEqual(len(excluded), 1)
# Ensure the entry returned is the correct entry.
self.assertEqual(excluded[0].author, 'user2')
self.assertEqual(excluded[0].message, 'message3')
def test_count(self):
"""
Tests the count method of a List of Embedded Documents.
"""
self.assertEqual(self.post1.comments.count(), 2)
self.assertEqual(self.post1.comments.count(), len(self.post1.comments))
def test_filtered_count(self):
"""
Tests the filter + count method of a List of Embedded Documents.
"""
count = self.post1.comments.filter(author='user1').count()
self.assertEqual(count, 1)
def test_single_keyword_get(self):
"""
Tests the get method of a List of Embedded Documents using a
single keyword.
"""
comment = self.post1.comments.get(author='user1')
# < 2.6 Incompatible >
# self.assertIsInstance(comment, self.Comments)
self.assertTrue(isinstance(comment, self.Comments))
self.assertEqual(comment.author, 'user1')
def test_multi_keyword_get(self):
"""
Tests the get method of a List of Embedded Documents using
multiple keywords.
"""
comment = self.post2.comments.get(author='user2', message='message2')
# < 2.6 Incompatible >
# self.assertIsInstance(comment, self.Comments)
self.assertTrue(isinstance(comment, self.Comments))
self.assertEqual(comment.author, 'user2')
self.assertEqual(comment.message, 'message2')
def test_no_keyword_multiple_return_get(self):
"""
Tests the get method of a List of Embedded Documents without
a keyword to return multiple documents.
"""
# < 2.6 Incompatible >
# with self.assertRaises(MultipleObjectsReturned):
# self.post1.comments.get()
self.assertRaises(MultipleObjectsReturned, self.post1.comments.get)
def test_keyword_multiple_return_get(self):
"""
Tests the get method of a List of Embedded Documents with a keyword
to return multiple documents.
"""
# < 2.6 Incompatible >
# with self.assertRaises(MultipleObjectsReturned):
# self.post2.comments.get(author='user2')
self.assertRaises(
MultipleObjectsReturned, self.post2.comments.get, author='user2'
)
def test_unknown_keyword_get(self):
"""
Tests the get method of a List of Embedded Documents with an
unknown keyword.
"""
# < 2.6 Incompatible >
# with self.assertRaises(AttributeError):
# self.post2.comments.get(year=2020)
self.assertRaises(AttributeError, self.post2.comments.get, year=2020)
def test_no_result_get(self):
"""
Tests the get method of a List of Embedded Documents where get
returns no results.
"""
# < 2.6 Incompatible >
# with self.assertRaises(DoesNotExist):
# self.post1.comments.get(author='user3')
self.assertRaises(
DoesNotExist, self.post1.comments.get, author='user3'
)
def test_first(self):
"""
Tests the first method of a List of Embedded Documents to
ensure it returns the first comment.
"""
comment = self.post1.comments.first()
# Ensure a Comment object was returned.
# < 2.6 Incompatible >
# self.assertIsInstance(comment, self.Comments)
self.assertTrue(isinstance(comment, self.Comments))
self.assertEqual(comment, self.post1.comments[0])
def test_create(self):
"""
Test the create method of a List of Embedded Documents.
"""
comment = self.post1.comments.create(
author='user4', message='message1'
)
self.post1.save()
# Ensure the returned value is the comment object.
# < 2.6 Incompatible >
# self.assertIsInstance(comment, self.Comments)
self.assertTrue(isinstance(comment, self.Comments))
self.assertEqual(comment.author, 'user4')
self.assertEqual(comment.message, 'message1')
# Ensure the new comment was actually saved to the database.
# < 2.6 Incompatible >
# self.assertIn(
# comment,
# self.BlogPost.objects(comments__author='user4')[0].comments
# )
self.assertTrue(
comment in self.BlogPost.objects(
comments__author='user4'
)[0].comments
)
def test_filtered_create(self):
"""
Test the create method of a List of Embedded Documents chained
to a call to the filter method. Filtering should have no effect
on creation.
"""
comment = self.post1.comments.filter(author='user1').create(
author='user4', message='message1'
)
self.post1.save()
# Ensure the returned value is the comment object.
# < 2.6 Incompatible >
# self.assertIsInstance(comment, self.Comments)
self.assertTrue(isinstance(comment, self.Comments))
self.assertEqual(comment.author, 'user4')
self.assertEqual(comment.message, 'message1')
# Ensure the new comment was actually saved to the database.
# < 2.6 Incompatible >
# self.assertIn(
# comment,
# self.BlogPost.objects(comments__author='user4')[0].comments
# )
self.assertTrue(
comment in self.BlogPost.objects(
comments__author='user4'
)[0].comments
)
def test_no_keyword_update(self):
"""
Tests the update method of a List of Embedded Documents with
no keywords.
"""
original = list(self.post1.comments)
number = self.post1.comments.update()
self.post1.save()
# Ensure that nothing was altered.
# < 2.6 Incompatible >
# self.assertIn(
# original[0],
# self.BlogPost.objects(id=self.post1.id)[0].comments
# )
self.assertTrue(
original[0] in self.BlogPost.objects(id=self.post1.id)[0].comments
)
# < 2.6 Incompatible >
# self.assertIn(
# original[1],
# self.BlogPost.objects(id=self.post1.id)[0].comments
# )
self.assertTrue(
original[1] in self.BlogPost.objects(id=self.post1.id)[0].comments
)
# Ensure the method returned 0 as the number of entries
# modified
self.assertEqual(number, 0)
def test_single_keyword_update(self):
"""
Tests the update method of a List of Embedded Documents with
a single keyword.
"""
number = self.post1.comments.update(author='user4')
self.post1.save()
comments = self.BlogPost.objects(id=self.post1.id)[0].comments
# Ensure that the database was updated properly.
self.assertEqual(comments[0].author, 'user4')
self.assertEqual(comments[1].author, 'user4')
# Ensure the method returned 2 as the number of entries
# modified
self.assertEqual(number, 2)
def test_save(self):
"""
Tests the save method of a List of Embedded Documents.
"""
comments = self.post1.comments
new_comment = self.Comments(author='user4')
comments.append(new_comment)
comments.save()
# Ensure that the new comment has been added to the database.
# < 2.6 Incompatible >
# self.assertIn(
# new_comment,
# self.BlogPost.objects(id=self.post1.id)[0].comments
# )
self.assertTrue(
new_comment in self.BlogPost.objects(id=self.post1.id)[0].comments
)
def test_delete(self):
"""
Tests the delete method of a List of Embedded Documents.
"""
number = self.post1.comments.delete()
self.post1.save()
# Ensure that all the comments under post1 were deleted in the
# database.
# < 2.6 Incompatible >
# self.assertListEqual(
# self.BlogPost.objects(id=self.post1.id)[0].comments, []
# )
self.assertEqual(
self.BlogPost.objects(id=self.post1.id)[0].comments, []
)
# Ensure that post1 comments were deleted from the list.
# < 2.6 Incompatible >
# self.assertListEqual(self.post1.comments, [])
self.assertEqual(self.post1.comments, [])
# Ensure that comments still returned a EmbeddedDocumentList object.
# < 2.6 Incompatible >
# self.assertIsInstance(self.post1.comments, EmbeddedDocumentList)
self.assertTrue(isinstance(self.post1.comments, EmbeddedDocumentList))
# Ensure that the delete method returned 2 as the number of entries
# deleted from the database
self.assertEqual(number, 2)
def test_filtered_delete(self):
"""
Tests the delete method of a List of Embedded Documents
after the filter method has been called.
"""
comment = self.post1.comments[1]
number = self.post1.comments.filter(author='user2').delete()
self.post1.save()
# Ensure that only the user2 comment was deleted.
# < 2.6 Incompatible >
# self.assertNotIn(
# comment, self.BlogPost.objects(id=self.post1.id)[0].comments
# )
self.assertTrue(
comment not in self.BlogPost.objects(id=self.post1.id)[0].comments
)
self.assertEqual(
len(self.BlogPost.objects(id=self.post1.id)[0].comments), 1
)
# Ensure that the user2 comment no longer exists in the list.
# < 2.6 Incompatible >
# self.assertNotIn(comment, self.post1.comments)
self.assertTrue(comment not in self.post1.comments)
self.assertEqual(len(self.post1.comments), 1)
# Ensure that the delete method returned 1 as the number of entries
# deleted from the database
self.assertEqual(number, 1)
if __name__ == '__main__':
unittest.main()

View File

@@ -114,42 +114,6 @@ class FileTest(unittest.TestCase):
# Ensure deleted file returns None
self.assertTrue(result.the_file.read() == None)
def test_file_fields_stream_after_none(self):
"""Ensure that a file field can be written to after it has been saved as
None
"""
class StreamFile(Document):
the_file = FileField()
StreamFile.drop_collection()
text = b('Hello, World!')
more_text = b('Foo Bar')
content_type = 'text/plain'
streamfile = StreamFile()
streamfile.save()
streamfile.the_file.new_file()
streamfile.the_file.write(text)
streamfile.the_file.write(more_text)
streamfile.the_file.close()
streamfile.save()
result = StreamFile.objects.first()
self.assertTrue(streamfile == result)
self.assertEqual(result.the_file.read(), text + more_text)
#self.assertEqual(result.the_file.content_type, content_type)
result.the_file.seek(0)
self.assertEqual(result.the_file.tell(), 0)
self.assertEqual(result.the_file.read(len(text)), text)
self.assertEqual(result.the_file.tell(), len(text))
self.assertEqual(result.the_file.read(len(more_text)), more_text)
self.assertEqual(result.the_file.tell(), len(text + more_text))
result.the_file.delete()
# Ensure deleted file returns None
self.assertTrue(result.the_file.read() == None)
def test_file_fields_set(self):
class SetFile(Document):

View File

@@ -510,29 +510,18 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(post.comments[0].by, 'joe')
self.assertEqual(post.comments[0].votes.score, 4)
def test_update_min_max(self):
class Scores(Document):
high_score = IntField()
low_score = IntField()
scores = Scores(high_score=800, low_score=200)
scores.save()
Scores.objects(id=scores.id).update(min__low_score=150)
self.assertEqual(Scores.objects(id=scores.id).get().low_score, 150)
Scores.objects(id=scores.id).update(min__low_score=250)
self.assertEqual(Scores.objects(id=scores.id).get().low_score, 150)
def test_updates_can_have_match_operators(self):
class Comment(EmbeddedDocument):
content = StringField()
name = StringField(max_length=120)
vote = IntField()
class Post(Document):
title = StringField(required=True)
tags = ListField(StringField())
comments = ListField(EmbeddedDocumentField("Comment"))
class Comment(EmbeddedDocument):
content = StringField()
name = StringField(max_length=120)
vote = IntField()
Post.drop_collection()
comm1 = Comment(content="very funny indeed", name="John S", vote=1)
@@ -925,7 +914,7 @@ class QuerySetTest(unittest.TestCase):
docs = docs[1:4]
self.assertEqual('[<Doc: 1>, <Doc: 2>, <Doc: 3>]', "%s" % docs)
self.assertEqual(docs.count(with_limit_and_skip=True), 3)
self.assertEqual(docs.count(), 3)
for doc in docs:
self.assertEqual('.. queryset mid-iteration ..', repr(docs))
@@ -1313,31 +1302,6 @@ class QuerySetTest(unittest.TestCase):
self.Person.objects(name='Test User').delete()
self.assertEqual(1, BlogPost.objects.count())
def test_reverse_delete_rule_cascade_on_abstract_document(self):
"""Ensure cascading deletion of referring documents from the database
does not fail on abstract document.
"""
class AbstractBlogPost(Document):
meta = {'abstract': True}
author = ReferenceField(self.Person, reverse_delete_rule=CASCADE)
class BlogPost(AbstractBlogPost):
content = StringField()
BlogPost.drop_collection()
me = self.Person(name='Test User')
me.save()
someoneelse = self.Person(name='Some-one Else')
someoneelse.save()
BlogPost(content='Watching TV', author=me).save()
BlogPost(content='Chilling out', author=me).save()
BlogPost(content='Pro Testing', author=someoneelse).save()
self.assertEqual(3, BlogPost.objects.count())
self.Person.objects(name='Test User').delete()
self.assertEqual(1, BlogPost.objects.count())
def test_reverse_delete_rule_cascade_self_referencing(self):
"""Ensure self-referencing CASCADE deletes do not result in infinite
loop
@@ -1397,31 +1361,6 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(1, BlogPost.objects.count())
self.assertEqual(None, BlogPost.objects.first().category)
def test_reverse_delete_rule_nullify_on_abstract_document(self):
"""Ensure nullification of references to deleted documents when
reference is on an abstract document.
"""
class AbstractBlogPost(Document):
meta = {'abstract': True}
author = ReferenceField(self.Person, reverse_delete_rule=NULLIFY)
class BlogPost(AbstractBlogPost):
content = StringField()
BlogPost.drop_collection()
me = self.Person(name='Test User')
me.save()
someoneelse = self.Person(name='Some-one Else')
someoneelse.save()
BlogPost(content='Watching TV', author=me).save()
self.assertEqual(1, BlogPost.objects.count())
self.assertEqual(me, BlogPost.objects.first().author)
self.Person.objects(name='Test User').delete()
self.assertEqual(1, BlogPost.objects.count())
self.assertEqual(None, BlogPost.objects.first().author)
def test_reverse_delete_rule_deny(self):
"""Ensure deletion gets denied on documents that still have references
to them.
@@ -1441,26 +1380,6 @@ class QuerySetTest(unittest.TestCase):
self.assertRaises(OperationError, self.Person.objects.delete)
def test_reverse_delete_rule_deny_on_abstract_document(self):
"""Ensure deletion gets denied on documents that still have references
to them, when reference is on an abstract document.
"""
class AbstractBlogPost(Document):
meta = {'abstract': True}
author = ReferenceField(self.Person, reverse_delete_rule=DENY)
class BlogPost(AbstractBlogPost):
content = StringField()
BlogPost.drop_collection()
me = self.Person(name='Test User')
me.save()
BlogPost(content='Watching TV', author=me).save()
self.assertEqual(1, BlogPost.objects.count())
self.assertRaises(OperationError, self.Person.objects.delete)
def test_reverse_delete_rule_pull(self):
"""Ensure pulling of references to deleted documents.
"""
@@ -1491,40 +1410,6 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(post.authors, [me])
self.assertEqual(another.authors, [])
def test_reverse_delete_rule_pull_on_abstract_documents(self):
"""Ensure pulling of references to deleted documents when reference
is defined on an abstract document..
"""
class AbstractBlogPost(Document):
meta = {'abstract': True}
authors = ListField(ReferenceField(self.Person,
reverse_delete_rule=PULL))
class BlogPost(AbstractBlogPost):
content = StringField()
BlogPost.drop_collection()
self.Person.drop_collection()
me = self.Person(name='Test User')
me.save()
someoneelse = self.Person(name='Some-one Else')
someoneelse.save()
post = BlogPost(content='Watching TV', authors=[me, someoneelse])
post.save()
another = BlogPost(content='Chilling Out', authors=[someoneelse])
another.save()
someoneelse.delete()
post.reload()
another.reload()
self.assertEqual(post.authors, [me])
self.assertEqual(another.authors, [])
def test_delete_with_limits(self):
class Log(Document):
@@ -1607,7 +1492,6 @@ class QuerySetTest(unittest.TestCase):
"""Ensure that atomic updates work properly.
"""
class BlogPost(Document):
name = StringField()
title = StringField()
hits = IntField()
tags = ListField(StringField())
@@ -2899,23 +2783,25 @@ class QuerySetTest(unittest.TestCase):
self.assertTrue('dilma' in new.content)
self.assertTrue('planejamento' in new.title)
query = News.objects.search_text("candidata")
self.assertEqual(query._search_text, "candidata")
query = News.objects.search_text(
"candidata", include_text_scores=True)
self.assertTrue(query._include_text_scores)
new = query.first()
self.assertTrue(isinstance(new.get_text_score(), float))
self.assertTrue(isinstance(new.text_score, float))
# count
query = News.objects.search_text('brasil').order_by('$text_score')
self.assertEqual(query._search_text, "brasil")
self.assertTrue(query._include_text_scores)
self.assertEqual(query.count(), 3)
self.assertEqual(query._query, {'$text': {'$search': 'brasil'}})
cursor_args = query._cursor_args
self.assertEqual(
cursor_args['fields'], {'_text_score': {'$meta': 'textScore'}})
cursor_args['fields'], {'text_score': {'$meta': 'textScore'}})
text_scores = [i.get_text_score() for i in query]
text_scores = [i.text_score for i in query]
self.assertEqual(len(text_scores), 3)
self.assertTrue(text_scores[0] > text_scores[1])
@@ -2925,7 +2811,7 @@ class QuerySetTest(unittest.TestCase):
# get item
item = News.objects.search_text(
'brasil').order_by('$text_score').first()
self.assertEqual(item.get_text_score(), max_text_score)
self.assertEqual(item.text_score, max_text_score)
@skip_older_mongodb
def test_distinct_handles_references_to_alias(self):
@@ -2992,55 +2878,13 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(authors, [mark_twain, john_tolkien])
def test_distinct_ListField_EmbeddedDocumentField_EmbeddedDocumentField(self):
class Continent(EmbeddedDocument):
continent_name = StringField()
class Country(EmbeddedDocument):
country_name = StringField()
continent = EmbeddedDocumentField(Continent)
class Author(EmbeddedDocument):
name = StringField()
country = EmbeddedDocumentField(Country)
class Book(Document):
title = StringField()
authors = ListField(EmbeddedDocumentField(Author))
Book.drop_collection()
europe = Continent(continent_name='europe')
asia = Continent(continent_name='asia')
scotland = Country(country_name="Scotland", continent=europe)
tibet = Country(country_name="Tibet", continent=asia)
mark_twain = Author(name="Mark Twain", country=scotland)
john_tolkien = Author(name="John Ronald Reuel Tolkien", country=tibet)
book = Book(title="Tom Sawyer", authors=[mark_twain]).save()
book = Book(
title="The Lord of the Rings", authors=[john_tolkien]).save()
book = Book(
title="The Stories", authors=[mark_twain, john_tolkien]).save()
country_list = Book.objects.distinct("authors.country")
self.assertEqual(country_list, [scotland, tibet])
continent_list = Book.objects.distinct("authors.country.continent")
self.assertEqual(continent_list, [europe, asia])
def test_distinct_ListField_ReferenceField(self):
class Foo(Document):
bar_lst = ListField(ReferenceField('Bar'))
class Bar(Document):
text = StringField()
class Foo(Document):
bar = ReferenceField('Bar')
bar_lst = ListField(ReferenceField('Bar'))
Bar.drop_collection()
Foo.drop_collection()
@@ -3400,7 +3244,7 @@ class QuerySetTest(unittest.TestCase):
for i in xrange(10):
Post(title="Post %s" % i).save()
self.assertEqual(5, Post.objects.limit(5).skip(5).count(with_limit_and_skip=True))
self.assertEqual(5, Post.objects.limit(5).skip(5).count())
self.assertEqual(
10, Post.objects.limit(5).skip(5).count(with_limit_and_skip=False))
@@ -4169,7 +4013,7 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(100, people._len) # Caused by list calling len
self.assertEqual(q, 1)
people.count(with_limit_and_skip=True) # count is cached
people.count() # count is cached
self.assertEqual(q, 1)
def test_no_cached_queryset(self):
@@ -4265,7 +4109,7 @@ class QuerySetTest(unittest.TestCase):
with query_counter() as q:
self.assertEqual(q, 0)
self.assertEqual(users.count(with_limit_and_skip=True), 7)
self.assertEqual(users.count(), 7)
for i, outer_user in enumerate(users):
self.assertEqual(outer_user.name, names[i])
@@ -4273,7 +4117,7 @@ class QuerySetTest(unittest.TestCase):
inner_count = 0
# Calling len might disrupt the inner loop if there are bugs
self.assertEqual(users.count(with_limit_and_skip=True), 7)
self.assertEqual(users.count(), 7)
for j, inner_user in enumerate(users):
self.assertEqual(inner_user.name, names[j])
@@ -4557,27 +4401,6 @@ class QuerySetTest(unittest.TestCase):
self.Person.objects().delete()
self.assertEqual(self.Person.objects().skip(1).delete(), 0) # test Document delete without existing documents
def test_max_time_ms(self):
# 778: max_time_ms can get only int or None as input
self.assertRaises(TypeError, self.Person.objects(name="name").max_time_ms, "not a number")
def test_subclass_field_query(self):
class Animal(Document):
is_mamal = BooleanField()
meta = dict(allow_inheritance=True)
class Cat(Animal):
whiskers_length = FloatField()
class ScottishCat(Cat):
folded_ears = BooleanField()
Animal(is_mamal=False).save()
Cat(is_mamal=True, whiskers_length=5.1).save()
ScottishCat(is_mamal=True, folded_ears=True).save()
self.assertEquals(Animal.objects(folded_ears=True).count(), 1)
self.assertEquals(Animal.objects(whiskers_length=5.1).count(), 1)
if __name__ == '__main__':
unittest.main()

View File

@@ -197,17 +197,5 @@ class TransformTest(unittest.TestCase):
update = transform.update(Location, set__poly={"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]})
self.assertEqual(update, {'$set': {'poly': {"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]}}})
def test_type(self):
class Doc(Document):
df = DynamicField()
Doc(df=True).save()
Doc(df=7).save()
Doc(df="df").save()
self.assertEqual(Doc.objects(df__type=1).count(), 0) # double
self.assertEqual(Doc.objects(df__type=8).count(), 1) # bool
self.assertEqual(Doc.objects(df__type=2).count(), 1) # str
self.assertEqual(Doc.objects(df__type=16).count(), 1) # int
if __name__ == '__main__':
unittest.main()

View File

@@ -318,10 +318,6 @@ class FieldTest(unittest.TestCase):
def test_circular_reference(self):
"""Ensure you can handle circular references
"""
class Relation(EmbeddedDocument):
name = StringField()
person = ReferenceField('Person')
class Person(Document):
name = StringField()
relations = ListField(EmbeddedDocumentField('Relation'))
@@ -329,6 +325,10 @@ class FieldTest(unittest.TestCase):
def __repr__(self):
return "<Person: %s>" % self.name
class Relation(EmbeddedDocument):
name = StringField()
person = ReferenceField('Person')
Person.drop_collection()
mother = Person(name="Mother")
daughter = Person(name="Daughter")
@@ -947,8 +947,6 @@ class FieldTest(unittest.TestCase):
class Asset(Document):
name = StringField(max_length=250, required=True)
path = StringField()
title = StringField()
parent = GenericReferenceField(default=None)
parents = ListField(GenericReferenceField())
children = ListField(GenericReferenceField())
@@ -1222,15 +1220,14 @@ class FieldTest(unittest.TestCase):
self.assertEqual(page.tags[0], page.posts[0].tags[0])
def test_select_related_follows_embedded_referencefields(self):
class Song(Document):
title = StringField()
class Playlist(Document):
items = ListField(EmbeddedDocumentField("PlaylistItem"))
class PlaylistItem(EmbeddedDocument):
song = ReferenceField("Song")
class Playlist(Document):
items = ListField(EmbeddedDocumentField("PlaylistItem"))
class Song(Document):
title = StringField()
Playlist.drop_collection()
Song.drop_collection()

View File

@@ -19,12 +19,9 @@ settings.configure(
AUTHENTICATION_BACKENDS = ('mongoengine.django.auth.MongoEngineBackend',)
)
try:
# For Django >= 1.7
if hasattr(django, 'setup'):
django.setup()
except RuntimeError:
pass
# For Django >= 1.7
if hasattr(django, 'setup'):
django.setup()
try:
from django.contrib.auth import authenticate, get_user_model
@@ -37,6 +34,7 @@ try:
DJ15 = True
except Exception:
DJ15 = False
from django.contrib.sessions.tests import SessionTestsMixin
from mongoengine.django.sessions import SessionStore, MongoSession
from mongoengine.django.tests import MongoTestCase
from datetime import tzinfo, timedelta
@@ -172,13 +170,8 @@ class QuerySetTest(unittest.TestCase):
"""Ensure that a queryset and filters work as expected
"""
class LimitCountQuerySet(QuerySet):
def count(self, with_limit_and_skip=True):
return super(LimitCountQuerySet, self).count(with_limit_and_skip)
class Note(Document):
meta = dict(queryset_class=LimitCountQuerySet)
name = StringField()
text = StringField()
Note.drop_collection()
@@ -228,13 +221,13 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(t.render(c), "10")
class _BaseMongoDBSessionTest(unittest.TestCase):
class MongoDBSessionTest(SessionTestsMixin, unittest.TestCase):
backend = SessionStore
def setUp(self):
connect(db='mongoenginetest')
MongoSession.drop_collection()
super(_BaseMongoDBSessionTest, self).setUp()
super(MongoDBSessionTest, self).setUp()
def assertIn(self, first, second, msg=None):
self.assertTrue(first in second, msg)
@@ -261,21 +254,6 @@ class _BaseMongoDBSessionTest(unittest.TestCase):
self.assertTrue('test_expire' in session, 'Session has expired before it is expected')
try:
# SessionTestsMixin isn't available for import on django > 1.8a1
from django.contrib.sessions.tests import SessionTestsMixin
class _MongoDBSessionTest(SessionTestsMixin):
pass
class MongoDBSessionTest(_BaseMongoDBSessionTest):
pass
except ImportError:
class MongoDBSessionTest(_BaseMongoDBSessionTest):
pass
class MongoAuthTest(unittest.TestCase):
user_data = {
'username': 'user',