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

10
AUTHORS
View File

@@ -208,13 +208,5 @@ that much better:
* Norberto Leite (https://github.com/nleite) * Norberto Leite (https://github.com/nleite)
* Bob Cribbs (https://github.com/bocribbz) * Bob Cribbs (https://github.com/bocribbz)
* Jay Shirley (https://github.com/jshirley) * Jay Shirley (https://github.com/jshirley)
* David Bordeynik (https://github.com/DavidBord) * DavidBord (https://github.com/DavidBord)
* Axel Haustant (https://github.com/noirbizarre) * 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.GenericEmbeddedDocumentField
.. autoclass:: mongoengine.fields.DynamicField .. autoclass:: mongoengine.fields.DynamicField
.. autoclass:: mongoengine.fields.ListField .. autoclass:: mongoengine.fields.ListField
.. autoclass:: mongoengine.fields.EmbeddedDocumentListField
.. autoclass:: mongoengine.fields.SortedListField .. autoclass:: mongoengine.fields.SortedListField
.. autoclass:: mongoengine.fields.DictField .. autoclass:: mongoengine.fields.DictField
.. autoclass:: mongoengine.fields.MapField .. autoclass:: mongoengine.fields.MapField
@@ -104,21 +103,6 @@ Fields
.. autoclass:: mongoengine.fields.ImageGridFsProxy .. autoclass:: mongoengine.fields.ImageGridFsProxy
.. autoclass:: mongoengine.fields.ImproperlyConfigured .. 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 Misc
==== ====

View File

@@ -5,27 +5,6 @@ Changelog
Changes in 0.9.X - DEV 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 - Allow specifying the '_cls' as a field for indexes #397
- Stop ensure_indexes running on a secondaries unless connection is through mongos #746 - Stop ensure_indexes running on a secondaries unless connection is through mongos #746
- Not overriding default values when loading a subset of fields #399 - 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 Django 1.4.x, pymongo 2.5.x, pymongo 2.6.x.
- Removing support for Python < 2.6.6 - Removing support for Python < 2.6.6
- Fixed $maxDistance location for geoJSON $near queries with MongoDB 2.6+ #664 - 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 - Added support for the using() method on a queryset #676
- PYPY support #673 - PYPY support #673
- Connection pooling #674 - Connection pooling #674
@@ -75,13 +54,10 @@ Changes in 0.9.X - DEV
- Allow atomic update for the entire `DictField` #742 - Allow atomic update for the entire `DictField` #742
- Added MultiPointField, MultiLineField, MultiPolygonField - Added MultiPointField, MultiLineField, MultiPolygonField
- Fix multiple connections aliases being rewritten #748 - 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 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) - Stop ensure_indexes running on a secondaries (#555)
- Fix circular import issue with django auth (#531) (#545) - 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) - Fix multi level nested fields getting marked as changed (#523)
- Django 1.6 login fix (#522) (#527) - Django 1.6 login fix (#522) (#527)
- Django 1.6 session fix (#509) - 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 EmbeddedDocument with ReferenceField equality issue (#502)
- Fixed GenericReferenceField serialization order (#499) - Fixed GenericReferenceField serialization order (#499)
- Fixed count and none bug (#498) - Fixed count and none bug (#498)
@@ -184,7 +160,7 @@ Changes in 0.8.0
- Added `get_next_value` preview for SequenceFields (#319) - Added `get_next_value` preview for SequenceFields (#319)
- Added no_sub_classes context manager and queryset helper (#312) - Added no_sub_classes context manager and queryset helper (#312)
- Querysets now utilises a local cache - 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) - Fixed querying string versions of ObjectIds issue with ReferenceField (#307)
- Added $setOnInsert support for upserts (#308) - Added $setOnInsert support for upserts (#308)
- Upserts now possible with just query parameters (#309) - 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) - Uses getlasterror to test created on updated saves (#163)
- Fixed inheritance and unique index creation (#140) - Fixed inheritance and unique index creation (#140)
- Fixed reverse delete rule with inheritance (#197) - 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 context manager (#106)
- Added switch_db method to document instances (#106) - Added switch_db method to document instances (#106)
- Added no_dereference context manager (#82) (#61) - 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) - Update index spec generation so its not destructive (#113)
Changes in 0.7.1 Changes in 0.7.1
================ =================
- Fixed index spec inheritance (#111) - Fixed index spec inheritance (#111)
Changes in 0.7.0 Changes in 0.7.0
================ =================
- Updated queryset.delete so you can use with skip / limit (#107) - Updated queryset.delete so you can use with skip / limit (#107)
- Updated index creation allows kwargs to be passed through refs (#104) - Updated index creation allows kwargs to be passed through refs (#104)
- Fixed Q object merge edge case (#109) - Fixed Q object merge edge case (#109)
@@ -402,7 +378,7 @@ Changes in 0.6.12
- Fixes error with _delta handling DBRefs - Fixes error with _delta handling DBRefs
Changes in 0.6.11 Changes in 0.6.11
================= ==================
- Fixed inconsistency handling None values field attrs - Fixed inconsistency handling None values field attrs
- Fixed map_field embedded db_field issue - Fixed map_field embedded db_field issue
- Fixed .save() _delta issue with DbRefs - Fixed .save() _delta issue with DbRefs
@@ -482,7 +458,7 @@ Changes in 0.6.1
- Fix for replicaSet connections - Fix for replicaSet connections
Changes in 0.6 Changes in 0.6
============== ================
- Added FutureWarning to inherited classes not declaring 'allow_inheritance' as the default will change in 0.7 - 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 - Added support for covered indexes when inheritance is off
@@ -570,8 +546,8 @@ Changes in v0.5
- Updated default collection naming convention - Updated default collection naming convention
- Added Document Mixin support - Added Document Mixin support
- Fixed queryet __repr__ mid iteration - Fixed queryet __repr__ mid iteration
- Added hint() support, so can tell Mongo the proper index to use for the query - Added hint() support, so cantell Mongo the proper index to use for the query
- Fixed issue with inconsistent setting of _cls breaking inherited referencing - Fixed issue with inconsitent setting of _cls breaking inherited referencing
- Added help_text and verbose_name to fields to help with some form libs - Added help_text and verbose_name to fields to help with some form libs
- Updated item_frequencies to handle embedded document lookups - Updated item_frequencies to handle embedded document lookups
- Added delta tracking now only sets / unsets explicitly changed fields - 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') 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 the :attr:`host` to
:func:`~mongoengine.connect`:: :func:`~mongoengine.connect`::
connect('project1', host='mongodb://localhost/database_name') connect('project1', host='mongodb://localhost/database_name')
.. note:: Database, username and password from URI string overrides Note that database name from uri has priority over name
corresponding parameters in :func:`~mongoengine.connect`: :: 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.
ReplicaSets ReplicaSets
=========== ===========
MongoEngine supports MongoEngine supports :class:`~pymongo.mongo_replica_set_client.MongoReplicaSetClient`.
:class:`~pymongo.mongo_replica_set_client.MongoReplicaSetClient`. To use them, To use them, please use a URI style connection and provide the `replicaSet` name in the
please use an URI style connection and provide the ``replicaSet`` name connection kwargs.
in the connection kwargs.
Read preferences are supported through the connection or via individual Read preferences are supported through the connection or via individual
queries by passing the read_preference :: 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"} meta = {"db_alias": "users-books-db"}
Context Managers Switch Database Context Manager
================ ===============================
Sometimes you may want to switch the database or collection to query against
for a class. 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 for example, archiving older data into a separate database for performance
reasons or writing functions that dynamically choose collections to write reasons.
document to.
Switch Database
---------------
The :class:`~mongoengine.context_managers.switch_db` context manager allows The :class:`~mongoengine.context_managers.switch_db` context manager allows
you to change the database alias for a given class allowing quick and easy 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): class User(Document):
name = StringField() name = StringField()
meta = {"db_alias": "user-db"} meta = {"db_alias": "user-db"}
with switch_db(User, 'archive-user-db') as User: with switch_db(User, 'archive-user-db') as User:
User(name="Ross").save() # Saves the 'archive-user-db' 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 There is also a switch collection context manager as well. The
----------------- :class:`~mongoengine.context_managers.switch_collection` context manager allows
The :class:`~mongoengine.context_managers.switch_collection` context manager you to change the collection for a given class allowing quick and easy
allows you to change the collection for a given class allowing quick and easy access to the same Group document across collection::
access the same Group document across collection::
from mongoengine.context_managers import switch_collection from mongoengine.context_managers import switch_db
class Group(Document): class Group(Document):
name = StringField() name = StringField()
@@ -128,9 +115,3 @@ access the same Group document across collection::
with switch_collection(Group, 'group2000') as Group: with switch_collection(Group, 'group2000') as Group:
Group(name="hello Group 2000 collection!").save() # Saves in group2000 collection 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 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 working with relational databases, rows are stored in **tables**, which have a
strict **schema** that the rows follow. MongoDB stores documents in 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. is enforced at a database level.
Defining a document's schema Defining a document's schema
@@ -171,15 +171,15 @@ arguments can be set on all fields:
size = StringField(max_length=3, choices=SIZE) size = StringField(max_length=3, choices=SIZE)
:attr:`help_text` (Default: None) :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) :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 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 :class:`~mongoengine.Document`, use the :class:`~mongoengine.fields.ListField` field
type. :class:`~mongoengine.fields.ListField` takes another field object as its first type. :class:`~mongoengine.fields.ListField` takes another field object as its first
argument, which specifies which type elements may be stored within the list:: 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 Any object's fields still referring to the object being deleted are removed
(using MongoDB's "unset" operation), effectively nullifying the relationship. (using MongoDB's "unset" operation), effectively nullifying the relationship.
:const:`mongoengine.CASCADE` :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. are deleted first.
:const:`mongoengine.PULL` :const:`mongoengine.PULL`
Removes the reference to the object (using MongoDB's "pull" operation) 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` Document classes that inherit **directly** from :class:`~mongoengine.Document`
will have their own **collection** in the database. The name of the collection 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 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 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 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): class Page(Document):
title = StringField() title = StringField()
rating = StringField() rating = StringField()
created = DateTimeField()
meta = { meta = {
'indexes': [ 'indexes': ['title', ('title', '-rating')]
'title',
('title', '-rating'),
{
'fields': ['created'],
'expireAfterSeconds': 3600
}
]
} }
If a dictionary is passed then the following options are available: 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, 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 This ensures that the shard key is sent with the query when calling the
:meth:`~mongoengine.document.Document.save` or :meth:`~mongoengine.document.Document.save` or
:meth:`~mongoengine.document.Document.update` method on an existing :meth:`~mongoengine.document.Document.update` method on an existing
:class:`~mongoengine.Document` instance:: :class:`-mongoengine.Document` instance::
class LogEntry(Document): class LogEntry(Document):
machine = StringField() 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 As this is new class is not a direct subclass of
:class:`~mongoengine.Document`, it will not be stored in its own collection; it :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 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 set :attr:`allow_inheritance` to True in the :attr:`meta` data for a
document.:: document.::
@@ -712,12 +704,12 @@ document.::
class DatedPage(Page): class DatedPage(Page):
date = DateTimeField() 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. to False, meaning you must set it to True to use inheritance.
Working with existing data 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 easily get working with existing data. Just define the document to match
the expected schema in your database :: 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 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 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 This won't turn on :ref:`document-inheritance` but will allow you to keep your
code DRY:: code DRY::

View File

@@ -2,7 +2,7 @@
Documents instances Documents instances
=================== ===================
To create a new document object, create an instance of the relevant document 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:: You may provide values for any of the fields on the document::
>>> page = Page(title="Test Page") >>> 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. Changes to documents are tracked and on the whole perform ``set`` operations.
* ``list_field.push(0)`` --- *sets* the resulting list * ``list_field.push(0)`` - *sets* the resulting list
* ``del(list_field)`` --- *unsets* whole list * ``del(list_field)`` - *unsets* whole list
With lists its preferable to use ``Doc.update(push__list_field=0)`` as 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:: .. seealso::
:ref:`guide-atomic-updates` :ref:`guide-atomic-updates`
@@ -74,7 +74,7 @@ Cascading Saves
If your document contains :class:`~mongoengine.fields.ReferenceField` or If your document contains :class:`~mongoengine.fields.ReferenceField` or
:class:`~mongoengine.fields.GenericReferenceField` objects, then by default the :class:`~mongoengine.fields.GenericReferenceField` objects, then by default the
:meth:`~mongoengine.Document.save` method will not save any changes to :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 save is a separate query, then passing :attr:`cascade` as True
to the save method will cascade any saves. 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' >>> bob.id == bob.email == 'bob@example.com'
True True
You can also access the document's "primary key" using the :attr:`pk` field, You can also access the document's "primary key" using the :attr:`pk` field; in
it's an alias to :attr:`id`:: is an alias to :attr:`id`::
>>> page = Page(title="Another Test Page") >>> page = Page(title="Another Test Page")
>>> page.save() >>> page.save()
>>> page.id == page.pk >>> page.id == page.pk
True
.. note:: .. 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 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 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. (version **0.8.3+**) to return a non-caching queryset.
Filtering queries Filtering queries
@@ -42,7 +42,7 @@ syntax::
Query operators 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:: operator name to a key with a double-underscore::
# Only find users whose age is 18 or less # Only find users whose age is 18 or less
@@ -84,20 +84,19 @@ expressions:
Geo queries Geo queries
----------- -----------
There are a few special operators for performing geographical queries. There are a few special operators for performing geographical queries. The following
The following were added in MongoEngine 0.8 for were added in 0.8 for: :class:`~mongoengine.fields.PointField`,
:class:`~mongoengine.fields.PointField`,
:class:`~mongoengine.fields.LineStringField` and :class:`~mongoengine.fields.LineStringField` and
:class:`~mongoengine.fields.PolygonField`: :class:`~mongoengine.fields.PolygonField`:
* ``geo_within`` -- check if a geometry is within a polygon. For ease of use * ``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:: 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=[[[40, 5], [40, 6], [41, 6], [40, 5]]])
loc.objects(point__geo_within={"type": "Polygon", loc.objects(point__geo_within={"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]}) "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=[(-125.0, 35.0), (-100.0, 40.0)])
loc.objects(point__geo_within_box=[<bottom left coordinates>, <upper right coordinates>]) 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", loc.objects(poly__geo_intersects={"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]]}) "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=[40, 5])
loc.objects(point__near={"type": "Point", "coordinates": [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) loc.objects(point__near=[40, 5], point__max_distance=1000)
The older 2D indexes are still supported with the The older 2D indexes are still supported with the
:class:`~mongoengine.fields.GeoPointField`: :class:`~mongoengine.fields.GeoPointField`:
* ``within_distance`` -- provide a list containing a point and a maximum * ``within_distance`` -- provide a list containing a point and a maximum
distance (e.g. [(41.342, -87.653), 5]) 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]) (e.g. [(41.342, -87.653), 5/earth_radius])
* ``near`` -- order the documents by how close they are to a given point * ``near`` -- order the documents by how close they are to a given point
* ``near_sphere`` -- Same as above but using the spherical geo model * ``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) Post.objects(comments__by="joe").update(inc__comments__S__votes=1)
.. note:: .. note:: Due to Mongo currently the $ operator only applies to the first matched item in the query.
Due to :program:`Mongo`, currently the $ operator only applies to the
first matched item in the query.
Raw queries 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__`` be integrated directly into the query. This is done using the ``__raw__``
keyword argument:: keyword argument::
@@ -214,12 +213,12 @@ keyword argument::
Limiting and skipping results Limiting and skipping results
============================= =============================
Just as with traditional ORMs, you may limit the number of results returned or Just as with traditional ORMs, you may limit the number of results returned, or
skip a number or results in you query. skip a number or results in you query.
:meth:`~mongoengine.queryset.QuerySet.limit` and :meth:`~mongoengine.queryset.QuerySet.limit` and
:meth:`~mongoengine.queryset.QuerySet.skip` and methods are available on :meth:`~mongoengine.queryset.QuerySet.skip` and methods are available on
:class:`~mongoengine.queryset.QuerySet` objects, but the `array-slicing` syntax :class:`~mongoengine.queryset.QuerySet` objects, but the prefered syntax for
is preferred for achieving this:: achieving this is using array-slicing syntax::
# Only the first 5 people # Only the first 5 people
users = User.objects[:5] 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 no document matches the query, and
:class:`~mongoengine.queryset.MultipleObjectsReturned` :class:`~mongoengine.queryset.MultipleObjectsReturned`
if more than one document matched the query. These exceptions are merged into 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, 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 document with the query arguments if no documents match the query. An
additional keyword argument, :attr:`defaults` may be provided, which will be 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 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 >>> a.name == b.name and a.age == b.age
True True
.. warning::
:meth:`~mongoengine.queryset.QuerySet.get_or_create` method is deprecated
since :mod:`mongoengine` 0.8.
Default Document queries 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 document returns a :class:`~mongoengine.queryset.QuerySet` that doesn't filter
the collection -- it returns all objects. This may be changed by defining a 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 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 documents, extending the :class:`~mongoengine.queryset.QuerySet` class may be
the way to go. To use a custom :class:`~mongoengine.queryset.QuerySet` class on 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 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): 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_one`,
:meth:`~mongoengine.queryset.QuerySet.update` and :meth:`~mongoengine.queryset.QuerySet.update` and
:meth:`~mongoengine.queryset.QuerySet.modify` methods on a :meth:`~mongoengine.queryset.QuerySet.modify` methods on a
:class:`~mongoengine.queryset.QuerySet` or :meth:`~mongoengine.queryset.QuerySet`. There are several different "modifiers"
:meth:`~mongoengine.Document.modify` and that you may use with these methods:
: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:
* ``set`` -- set a particular value * ``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 * ``inc`` -- increment a value by a given amount
* ``dec`` -- decrement a value by a given amount * ``dec`` -- decrement a value by a given amount
* ``push`` -- append a value to a list * ``push`` -- append a value to a list
@@ -663,4 +655,3 @@ following example shows how the substitutions are made::
return comments; return comments;
} }
""") """)

View File

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

View File

@@ -46,6 +46,4 @@ Next, start a text search using :attr:`QuerySet.search_text` method::
Ordering by text score Ordering by text score
====================== ======================
::
objects = News.objects.search('mongo').order_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. MongoEngine.
:doc:`guide/index` :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. from querying for data to firing signals and *everything* between.
:doc:`apireference` :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) first_name = StringField(max_length=50)
last_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 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 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 changes easy to manage. Also, the User documents will be stored in a

View File

@@ -5,7 +5,7 @@ Upgrading
0.8.7 0.8.7
***** *****
Calling reload on deleted / nonexistent documents now raises a DoesNotExist Calling reload on deleted / nonexistant documents now raises a DoesNotExist
exception. exception.
@@ -263,7 +263,7 @@ update your code like so: ::
[m for m in mammals] # This will return all carnivores [m for m in mammals] # This will return all carnivores
Len iterates the queryset Len iterates the queryset
------------------------- --------------------------
If you ever did `len(queryset)` it previously did a `count()` under the covers, 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 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__ + __all__ = (list(document.__all__) + fields.__all__ + connection.__all__ +
list(queryset.__all__) + signals.__all__ + list(errors.__all__)) list(queryset.__all__) + signals.__all__ + list(errors.__all__))
VERSION = (0, 9, 0) VERSION = (0, 8, 7)
def get_version(): def get_version():

View File

@@ -2,9 +2,8 @@ import weakref
import functools import functools
import itertools import itertools
from mongoengine.common import _import_class from mongoengine.common import _import_class
from mongoengine.errors import DoesNotExist, MultipleObjectsReturned
__all__ = ("BaseDict", "BaseList", "EmbeddedDocumentList") __all__ = ("BaseDict", "BaseList")
class BaseDict(dict): class BaseDict(dict):
@@ -76,10 +75,6 @@ class BaseDict(dict):
self._mark_as_changed() self._mark_as_changed()
return super(BaseDict, self).popitem(*args, **kwargs) 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): def update(self, *args, **kwargs):
self._mark_as_changed() self._mark_as_changed()
return super(BaseDict, self).update(*args, **kwargs) return super(BaseDict, self).update(*args, **kwargs)
@@ -107,7 +102,7 @@ class BaseList(list):
if isinstance(instance, (Document, EmbeddedDocument)): if isinstance(instance, (Document, EmbeddedDocument)):
self._instance = weakref.proxy(instance) self._instance = weakref.proxy(instance)
self._name = name self._name = name
super(BaseList, self).__init__(list_items) return super(BaseList, self).__init__(list_items)
def __getitem__(self, key, *args, **kwargs): def __getitem__(self, key, *args, **kwargs):
value = super(BaseList, self).__getitem__(key) value = super(BaseList, self).__getitem__(key)
@@ -192,167 +187,6 @@ class BaseList(list):
self._instance._mark_as_changed(self._name) 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): class StrictDict(object):
__slots__ = () __slots__ = ()
_special_fields = set(['get', 'pop', 'iteritems', 'items', 'keys', 'create']) _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 import signals
from mongoengine.common import _import_class from mongoengine.common import _import_class
from mongoengine.errors import (ValidationError, InvalidDocumentError, from mongoengine.errors import (ValidationError, InvalidDocumentError,
LookUpError, FieldDoesNotExist) LookUpError)
from mongoengine.python_support import PY3, txt_type from mongoengine.python_support import PY3, txt_type
from mongoengine.base.common import get_document, ALLOW_INHERITANCE from mongoengine.base.common import get_document, ALLOW_INHERITANCE
from mongoengine.base.datastructures import ( from mongoengine.base.datastructures import BaseDict, BaseList, StrictDict, SemiStrictDict
BaseDict,
BaseList,
EmbeddedDocumentList,
StrictDict,
SemiStrictDict
)
from mongoengine.base.fields import ComplexBaseField from mongoengine.base.fields import ComplexBaseField
__all__ = ('BaseDocument', 'NON_FIELD_ERRORS') __all__ = ('BaseDocument', 'NON_FIELD_ERRORS')
@@ -60,32 +54,20 @@ class BaseDocument(object):
raise TypeError( raise TypeError(
"Multiple values for keyword argument '" + name + "'") "Multiple values for keyword argument '" + name + "'")
values[name] = value values[name] = value
__auto_convert = values.pop("__auto_convert", True) __auto_convert = values.pop("__auto_convert", True)
# 399: set default values only to fields loaded from DB # 399: set default values only to fields loaded from DB
__only_fields = set(values.pop("__only_fields", values)) __only_fields = set(values.pop("__only_fields", values))
_created = values.pop("_created", True)
signals.pre_init.send(self.__class__, document=self, values=values) 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: if self.STRICT and not self._dynamic:
self._data = StrictDict.create(allowed_keys=self._fields_ordered)() self._data = StrictDict.create(allowed_keys=self._fields_ordered)()
else: else:
self._data = SemiStrictDict.create( self._data = SemiStrictDict.create(
allowed_keys=self._fields_ordered)() allowed_keys=self._fields_ordered)()
_created = values.pop("_created", True)
self._data = {} self._data = {}
self._dynamic_fields = SON() self._dynamic_fields = SON()
@@ -247,7 +229,7 @@ class BaseDocument(object):
u = self.__str__() u = self.__str__()
except (UnicodeEncodeError, UnicodeDecodeError): except (UnicodeEncodeError, UnicodeDecodeError):
u = '[Bad Unicode data]' 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)) return repr_type('<%s: %s>' % (self.__class__.__name__, u))
def __str__(self): def __str__(self):
@@ -259,12 +241,10 @@ class BaseDocument(object):
return txt_type('%s object' % self.__class__.__name__) return txt_type('%s object' % self.__class__.__name__)
def __eq__(self, other): 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 return self.id == other.id
if isinstance(other, DBRef): if isinstance(other, DBRef):
return self._get_collection_name() == other.collection and self.id == other.id return self._get_collection_name() == other.collection and self.id == other.id
if self.id is None:
return self is other
return False return False
def __ne__(self, other): def __ne__(self, other):
@@ -287,23 +267,10 @@ class BaseDocument(object):
""" """
pass pass
def get_text_score(self): def to_mongo(self, use_db_field=True, fields=[]):
"""
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):
""" """
Return as SON data ready for use with MongoDB. Return as SON data ready for use with MongoDB.
""" """
if not fields:
fields = []
data = SON() data = SON()
data["_id"] = None data["_id"] = None
data['_cls'] = self._class_name data['_cls'] = self._class_name
@@ -414,21 +381,20 @@ class BaseDocument(object):
"""Converts a document to JSON. """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 :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) return json_util.dumps(self.to_mongo(use_db_field), *args, **kwargs)
@classmethod @classmethod
def from_json(cls, json_data, created=False): def from_json(cls, json_data):
"""Converts json data to an unsaved document instance""" """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): def __expand_dynamic_values(self, name, value):
"""expand any dynamic values to their correct types / values""" """expand any dynamic values to their correct types / values"""
if not isinstance(value, (dict, list, tuple)): if not isinstance(value, (dict, list, tuple)):
return value return value
EmbeddedDocumentListField = _import_class('EmbeddedDocumentListField')
is_list = False is_list = False
if not hasattr(value, 'items'): if not hasattr(value, 'items'):
is_list = True is_list = True
@@ -452,10 +418,7 @@ class BaseDocument(object):
# Convert lists / values so we can watch for any changes on them # Convert lists / values so we can watch for any changes on them
if (isinstance(value, (list, tuple)) and if (isinstance(value, (list, tuple)) and
not isinstance(value, BaseList)): not isinstance(value, BaseList)):
if issubclass(type(self), EmbeddedDocumentListField): value = BaseList(value, self, name)
value = EmbeddedDocumentList(value, self, name)
else:
value = BaseList(value, self, name)
elif isinstance(value, dict) and not isinstance(value, BaseDict): elif isinstance(value, dict) and not isinstance(value, BaseDict):
value = BaseDict(value, self, name) value = BaseDict(value, self, name)
@@ -654,11 +617,9 @@ class BaseDocument(object):
return cls._meta.get('collection', None) return cls._meta.get('collection', None)
@classmethod @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. """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 # get the class name from the document, falling back to the given
# class if unavailable # class if unavailable
@@ -706,7 +667,7 @@ class BaseDocument(object):
if cls.STRICT: if cls.STRICT:
data = dict((k, v) data = dict((k, v)
for k, v in data.iteritems() if k in cls._fields) 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 obj._changed_fields = changed_fields
if not _auto_dereference: if not _auto_dereference:
obj._fields = fields obj._fields = fields
@@ -729,7 +690,7 @@ class BaseDocument(object):
spec_fields = [v['fields'] spec_fields = [v['fields']
for k, v in enumerate(index_specs)] 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): for k, v in enumerate(indices):
if v['fields'] in spec_fields: if v['fields'] in spec_fields:
index_specs[spec_fields.index(v['fields'])].update(v) index_specs[spec_fields.index(v['fields'])].update(v)
@@ -823,9 +784,10 @@ class BaseDocument(object):
""" """
unique_indexes = [] unique_indexes = []
for field_name, field in cls._fields.items(): for field_name, field in cls._fields.items():
sparse = field.sparse sparse = False
# Generate a list of indexes needed by uniqueness constraints # Generate a list of indexes needed by uniqueness constraints
if field.unique: if field.unique:
field.required = True
unique_fields = [field.db_field] unique_fields = [field.db_field]
# Add any unique_with fields to the back of the index spec # 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} index = {'fields': fields, 'unique': True, 'sparse': sparse}
unique_indexes.append(index) unique_indexes.append(index)
if field.__class__.__name__ == "ListField":
field = field.field
# Grab any embedded document field unique indexes # Grab any embedded document field unique indexes
if (field.__class__.__name__ == "EmbeddedDocumentField" and if (field.__class__.__name__ == "EmbeddedDocumentField" and
field.document_type != cls): field.document_type != cls):
@@ -925,19 +884,6 @@ class BaseDocument(object):
elif cls._dynamic: elif cls._dynamic:
DynamicField = _import_class('DynamicField') DynamicField = _import_class('DynamicField')
field = DynamicField(db_field=field_name) 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: else:
raise LookUpError('Cannot resolve field "%s"' raise LookUpError('Cannot resolve field "%s"'
% field_name) % field_name)

View File

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

View File

@@ -46,9 +46,8 @@ class DocumentMetaclass(type):
elif hasattr(base, '_meta'): elif hasattr(base, '_meta'):
meta.merge(base._meta) meta.merge(base._meta)
attrs['_meta'] = 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') StringField = _import_class('StringField')
attrs['_cls'] = StringField() attrs['_cls'] = StringField()

View File

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

View File

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

View File

@@ -1,9 +1,6 @@
from bson import DBRef, SON from bson import DBRef, SON
from base import ( from base import (BaseDict, BaseList, TopLevelDocumentMetaclass, get_document)
BaseDict, BaseList, EmbeddedDocumentList,
TopLevelDocumentMetaclass, get_document
)
from fields import (ReferenceField, ListField, DictField, MapField) from fields import (ReferenceField, ListField, DictField, MapField)
from connection import get_db from connection import get_db
from queryset import QuerySet from queryset import QuerySet
@@ -192,9 +189,6 @@ class DeReference(object):
if not hasattr(items, 'items'): if not hasattr(items, 'items'):
is_list = True is_list = True
list_type = BaseList
if isinstance(items, EmbeddedDocumentList):
list_type = EmbeddedDocumentList
as_tuple = isinstance(items, tuple) as_tuple = isinstance(items, tuple)
iterator = enumerate(items) iterator = enumerate(items)
data = [] data = []
@@ -231,7 +225,7 @@ class DeReference(object):
if instance and name: if instance and name:
if is_list: 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) return BaseDict(data, instance, name)
depth += 1 depth += 1
return data 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.contrib.auth.models import UserManager
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.db import models from django.db import models
try: from django.utils.importlib import import_module
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.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _

View File

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

View File

@@ -9,17 +9,10 @@ from bson import ObjectId
from bson.dbref import DBRef from bson.dbref import DBRef
from mongoengine import signals from mongoengine import signals
from mongoengine.common import _import_class from mongoengine.common import _import_class
from mongoengine.base import ( from mongoengine.base import (DocumentMetaclass, TopLevelDocumentMetaclass,
DocumentMetaclass, BaseDocument, BaseDict, BaseList,
TopLevelDocumentMetaclass, ALLOW_INHERITANCE, get_document)
BaseDocument, from mongoengine.errors import ValidationError
BaseDict,
BaseList,
EmbeddedDocumentList,
ALLOW_INHERITANCE,
get_document
)
from mongoengine.errors import ValidationError, InvalidQueryError, InvalidDocumentError
from mongoengine.queryset import (OperationError, NotUniqueError, from mongoengine.queryset import (OperationError, NotUniqueError,
QuerySet, transform) QuerySet, transform)
from mongoengine.connection import get_db, DEFAULT_CONNECTION_NAME from mongoengine.connection import get_db, DEFAULT_CONNECTION_NAME
@@ -83,12 +76,6 @@ class EmbeddedDocument(BaseDocument):
def __ne__(self, other): def __ne__(self, other):
return not self.__eq__(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): class Document(BaseDocument):
@@ -126,7 +113,7 @@ class Document(BaseDocument):
a **+** or **-** sign. a **+** or **-** sign.
Automatic index creation can be disabled by specifying 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 False then indexes will not be created by MongoEngine. This is useful in
production systems where index creation is performed as part of a production systems where index creation is performed as part of a
deployment system. deployment system.
@@ -156,6 +143,13 @@ class Document(BaseDocument):
return property(fget, fset) return property(fget, fset)
pk = pk() pk = pk()
@property
def text_score(self):
"""
Used for text searchs
"""
return self._data.get('text_score')
@classmethod @classmethod
def _get_db(cls): def _get_db(cls):
"""Some Model using other db_alias""" """Some Model using other db_alias"""
@@ -198,44 +192,6 @@ class Document(BaseDocument):
cls.ensure_indexes() cls.ensure_indexes()
return cls._collection 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, def save(self, force_insert=False, validate=True, clean=True,
write_concern=None, cascade=None, cascade_kwargs=None, write_concern=None, cascade=None, cascade_kwargs=None,
_refs=None, save_condition=None, **kwargs): _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 _refs: A list of processed references used in cascading saves
:param save_condition: only perform save if matching record in db :param save_condition: only perform save if matching record in db
satisfies condition(s) (e.g., version number) satisfies condition(s) (e.g., version number)
.. versionchanged:: 0.5 .. versionchanged:: 0.5
In existing documents it only saves changed fields using In existing documents it only saves changed fields using
set / unset. Saves are cascaded and any set / unset. Saves are cascaded and any
@@ -298,8 +253,6 @@ class Document(BaseDocument):
try: try:
collection = self._get_collection() collection = self._get_collection()
if self._meta.get('auto_create_index', True):
self.ensure_indexes()
if created: if created:
if force_insert: if force_insert:
object_id = collection.insert(doc, **write_concern) object_id = collection.insert(doc, **write_concern)
@@ -471,11 +424,10 @@ class Document(BaseDocument):
user.switch_db('archive-db') user.switch_db('archive-db')
user.save() 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:: :param db_alias: The database alias to use for saving the document
Use :class:`~mongoengine.context_managers.switch_collection`
if you need to read from another collection
""" """
with switch_db(self.__class__, db_alias) as cls: with switch_db(self.__class__, db_alias) as cls:
collection = cls._get_collection() collection = cls._get_collection()
@@ -498,12 +450,11 @@ class Document(BaseDocument):
user.switch_collection('old-users') user.switch_collection('old-users')
user.save() user.save()
:param str collection_name: The database alias to use for saving the If you need to read from another database see
document :class:`~mongoengine.context_managers.switch_db`
.. seealso:: :param collection_name: The database alias to use for saving the
Use :class:`~mongoengine.context_managers.switch_db` document
if you need to read from another database
""" """
with switch_collection(self.__class__, collection_name) as cls: with switch_collection(self.__class__, collection_name) as cls:
collection = cls._get_collection() collection = cls._get_collection()
@@ -554,13 +505,7 @@ class Document(BaseDocument):
for field in self._fields_ordered: for field in self._fields_ordered:
if not fields or field in fields: if not fields or field in fields:
try: setattr(self, field, self._reload(field, obj[field]))
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)
self._changed_fields = obj._changed_fields self._changed_fields = obj._changed_fields
self._created = False self._created = False
@@ -573,9 +518,6 @@ class Document(BaseDocument):
if isinstance(value, BaseDict): if isinstance(value, BaseDict):
value = [(k, self._reload(k, v)) for k, v in value.items()] value = [(k, self._reload(k, v)) for k, v in value.items()]
value = BaseDict(value, self, key) 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): elif isinstance(value, BaseList):
value = [self._reload(key, v) for v in value] value = [self._reload(key, v) for v in value]
value = BaseList(value, self, key) value = BaseList(value, self, key)
@@ -691,7 +633,7 @@ class Document(BaseDocument):
if cls._meta.get('abstract'): if cls._meta.get('abstract'):
return [] return []
# get all the base classes, subclasses and siblings # get all the base classes, subclasses and sieblings
classes = [] classes = []
def get_classes(cls): def get_classes(cls):

View File

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

View File

@@ -39,13 +39,13 @@ __all__ = [
'FloatField', 'DecimalField', 'BooleanField', 'DateTimeField', 'FloatField', 'DecimalField', 'BooleanField', 'DateTimeField',
'ComplexDateTimeField', 'EmbeddedDocumentField', 'ObjectIdField', 'ComplexDateTimeField', 'EmbeddedDocumentField', 'ObjectIdField',
'GenericEmbeddedDocumentField', 'DynamicField', 'ListField', 'GenericEmbeddedDocumentField', 'DynamicField', 'ListField',
'SortedListField', 'EmbeddedDocumentListField', 'DictField', 'SortedListField', 'DictField', 'MapField', 'ReferenceField',
'MapField', 'ReferenceField', 'CachedReferenceField', 'CachedReferenceField', 'GenericReferenceField', 'BinaryField',
'GenericReferenceField', 'BinaryField', 'GridFSError', 'GridFSProxy', 'GridFSError', 'GridFSProxy', 'FileField', 'ImageGridFsProxy',
'FileField', 'ImageGridFsProxy', 'ImproperlyConfigured', 'ImageField', 'ImproperlyConfigured', 'ImageField', 'GeoPointField', 'PointField',
'GeoPointField', 'PointField', 'LineStringField', 'PolygonField', 'LineStringField', 'PolygonField', 'SequenceField', 'UUIDField',
'SequenceField', 'UUIDField', 'MultiPointField', 'MultiLineStringField', 'MultiPointField', 'MultiLineStringField', 'MultiPolygonField',
'MultiPolygonField', 'GeoJsonBaseField'] 'GeoJsonBaseField']
RECURSIVE_REFERENCE_CONSTANT = 'self' RECURSIVE_REFERENCE_CONSTANT = 'self'
@@ -291,7 +291,7 @@ class DecimalField(BaseField):
:param max_value: Validation rule for the maximum acceptable value. :param max_value: Validation rule for the maximum acceptable value.
:param force_string: Store as a string. :param force_string: Store as a string.
:param precision: Number of decimal places to store. :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_CEILING (towards Infinity)
- decimal.ROUND_DOWN (towards zero) - decimal.ROUND_DOWN (towards zero)
@@ -308,7 +308,7 @@ class DecimalField(BaseField):
self.min_value = min_value self.min_value = min_value
self.max_value = max_value self.max_value = max_value
self.force_string = force_string self.force_string = force_string
self.precision = precision self.precision = decimal.Decimal(".%s" % ("0" * precision))
self.rounding = rounding self.rounding = rounding
super(DecimalField, self).__init__(**kwargs) super(DecimalField, self).__init__(**kwargs)
@@ -322,7 +322,7 @@ class DecimalField(BaseField):
value = decimal.Decimal("%s" % value) value = decimal.Decimal("%s" % value)
except decimal.InvalidOperation: except decimal.InvalidOperation:
return value 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): def to_mongo(self, value, use_db_field=True):
if value is None: if value is None:
@@ -375,11 +375,11 @@ class DateTimeField(BaseField):
Uses the python-dateutil library if available alternatively use time.strptime 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 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. python datetime objects.
Note: Microseconds are rounded to the nearest millisecond. 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 Use :class:`~mongoengine.fields.ComplexDateTimeField` if you
need accurate microsecond support. need accurate microsecond support.
""" """
@@ -510,7 +510,7 @@ class ComplexDateTimeField(StringField):
def __get__(self, instance, owner): def __get__(self, instance, owner):
data = super(ComplexDateTimeField, self).__get__(instance, owner) data = super(ComplexDateTimeField, self).__get__(instance, owner)
if data is None: if data is None:
return None if self.null else datetime.datetime.now() return datetime.datetime.now()
if isinstance(data, datetime.datetime): if isinstance(data, datetime.datetime):
return data return data
return self._convert_from_string(data) return self._convert_from_string(data)
@@ -638,7 +638,7 @@ class DynamicField(BaseField):
Used by :class:`~mongoengine.DynamicDocument` to handle dynamic data""" Used by :class:`~mongoengine.DynamicDocument` to handle dynamic data"""
def to_mongo(self, value): 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): if isinstance(value, basestring):
@@ -728,32 +728,6 @@ class ListField(ComplexBaseField):
return super(ListField, self).prepare_query_value(op, value) 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): class SortedListField(ListField):
"""A ListField that sorts the contents of its list before writing to """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 Use the `reverse_delete_rule` to handle what should happen if the document
the field is referencing is deleted. EmbeddedDocuments, DictFields and 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. will be raised if trying to set on one of these Document / Field types.
The options are: The options are:
@@ -909,7 +883,7 @@ class ReferenceField(BaseField):
Bar.register_delete_rule(Foo, 'bar', NULLIFY) Bar.register_delete_rule(Foo, 'bar', NULLIFY)
.. note :: .. 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. triggered.
.. versionchanged:: 0.5 added `reverse_delete_rule` .. versionchanged:: 0.5 added `reverse_delete_rule`
@@ -1357,7 +1331,6 @@ class GridFSProxy(object):
def new_file(self, **kwargs): def new_file(self, **kwargs):
self.newfile = self.fs.new_file(**kwargs) self.newfile = self.fs.new_file(**kwargs)
self.grid_id = self.newfile._id self.grid_id = self.newfile._id
self._mark_as_changed()
def put(self, file_obj, **kwargs): def put(self, file_obj, **kwargs):
if self.grid_id: if self.grid_id:
@@ -1676,7 +1649,7 @@ class ImageField(FileField):
class SequenceField(BaseField): class SequenceField(BaseField):
"""Provides a sequential counter see: """Provides a sequental counter see:
http://www.mongodb.org/display/DOCS/Object+IDs#ObjectIDs-SequenceNumbers http://www.mongodb.org/display/DOCS/Object+IDs#ObjectIDs-SequenceNumbers
.. note:: .. note::
@@ -1777,7 +1750,7 @@ class SequenceField(BaseField):
def prepare_query_value(self, op, value): 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 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. values passed as string, the base implementation returns the value as is.
""" """
@@ -1887,7 +1860,6 @@ class PointField(GeoJsonBaseField):
to set the value. to set the value.
Requires mongodb >= 2.4 Requires mongodb >= 2.4
.. versionadded:: 0.8 .. versionadded:: 0.8
""" """
_type = "Point" _type = "Point"
@@ -1907,7 +1879,6 @@ class LineStringField(GeoJsonBaseField):
You can either pass a dict with the full information or a list of points. You can either pass a dict with the full information or a list of points.
Requires mongodb >= 2.4 Requires mongodb >= 2.4
.. versionadded:: 0.8 .. versionadded:: 0.8
""" """
_type = "LineString" _type = "LineString"
@@ -1930,7 +1901,6 @@ class PolygonField(GeoJsonBaseField):
holes. holes.
Requires mongodb >= 2.4 Requires mongodb >= 2.4
.. versionadded:: 0.8 .. versionadded:: 0.8
""" """
_type = "Polygon" _type = "Polygon"
@@ -1951,7 +1921,6 @@ class MultiPointField(GeoJsonBaseField):
to set the value. to set the value.
Requires mongodb >= 2.6 Requires mongodb >= 2.6
.. versionadded:: 0.9 .. versionadded:: 0.9
""" """
_type = "MultiPoint" _type = "MultiPoint"
@@ -1972,7 +1941,6 @@ class MultiLineStringField(GeoJsonBaseField):
You can either pass a dict with the full information or a list of points. You can either pass a dict with the full information or a list of points.
Requires mongodb >= 2.6 Requires mongodb >= 2.6
.. versionadded:: 0.9 .. versionadded:: 0.9
""" """
_type = "MultiLineString" _type = "MultiLineString"
@@ -1986,7 +1954,7 @@ class MultiPolygonField(GeoJsonBaseField):
.. code-block:: js .. code-block:: js
{ "type" : "MultiPolygon" , { "type" : "Polygon" ,
"coordinates" : [[ "coordinates" : [[
[[x1, y1], [x1, y1] ... [xn, yn]], [[x1, y1], [x1, y1] ... [xn, yn]],
[[x1, y1], [x1, y1] ... [xn, yn]] [[x1, y1], [x1, y1] ... [xn, yn]]
@@ -2000,7 +1968,6 @@ class MultiPolygonField(GeoJsonBaseField):
of Polygons. of Polygons.
Requires mongodb >= 2.6 Requires mongodb >= 2.6
.. versionadded:: 0.9 .. versionadded:: 0.9
""" """
_type = "MultiPolygon" _type = "MultiPolygon"

View File

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

View File

@@ -94,7 +94,7 @@ class QuerySet(BaseQuerySet):
except StopIteration: except StopIteration:
self._has_more = False 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. """Count the selected elements in the query.
:param with_limit_and_skip (optional): take any :meth:`limit` or :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', 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', GEO_OPERATORS = ('within_distance', 'within_spherical_distance',
'within_box', 'within_polygon', 'near', 'near_sphere', 'within_box', 'within_polygon', 'near', 'near_sphere',
'max_distance', 'geo_within', 'geo_within_box', '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', UPDATE_OPERATORS = ('set', 'unset', 'inc', 'dec', 'pop', 'push',
'push_all', 'pull', 'pull_all', 'add_to_set', '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): 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): if isinstance(v, list):
value = [{k: val} for val in v] value = [{k: val} for val in v]
if '$and' in mongo_query.keys(): if '$and' in mongo_query.keys():
mongo_query['$and'].extend(value) mongo_query['$and'].append(value)
else: else:
mongo_query['$and'] = value mongo_query['$and'] = value

View File

@@ -29,7 +29,7 @@ class DuplicateQueryConditionsError(InvalidQueryError):
class SimplificationVisitor(QNodeVisitor): 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. into a single Q-object.
""" """

View File

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

View File

@@ -38,7 +38,7 @@ CLASSIFIERS = [
'Operating System :: OS Independent', 'Operating System :: OS Independent',
'Programming Language :: Python', 'Programming Language :: Python',
"Programming Language :: Python :: 2", "Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.6.6",
"Programming Language :: Python :: 2.7", "Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.2", "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() obj = collection.find_one()
self.assertEqual(sorted(obj.keys()), ['_cls', '_id', 'name']) 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): def test_dynamic_document_queries(self):
"""Ensure we can query dynamic fields""" """Ensure we can query dynamic fields"""
p = self.Person() p = self.Person()

View File

@@ -18,7 +18,7 @@ __all__ = ("IndexesTest", )
class IndexesTest(unittest.TestCase): class IndexesTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.connection = connect(db='mongoenginetest') connect(db='mongoenginetest')
self.db = get_db() self.db = get_db()
class Person(Document): class Person(Document):
@@ -495,12 +495,9 @@ class IndexesTest(unittest.TestCase):
self.assertEqual(BlogPost.objects.hint([('ZZ', 1)]).count(), 10) self.assertEqual(BlogPost.objects.hint([('ZZ', 1)]).count(), 10)
if pymongo.version >= '2.8': def invalid_index():
self.assertEqual(BlogPost.objects.hint('tags').count(), 10) BlogPost.objects.hint('tags')
else: self.assertRaises(TypeError, invalid_index)
def invalid_index():
BlogPost.objects.hint('tags')
self.assertRaises(TypeError, invalid_index)
def invalid_index_2(): def invalid_index_2():
return BlogPost.objects.hint(('tags', 1)) return BlogPost.objects.hint(('tags', 1))
@@ -580,38 +577,6 @@ class IndexesTest(unittest.TestCase):
BlogPost.drop_collection() 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): def test_unique_with_embedded_document_and_embedded_unique(self):
"""Ensure that uniqueness constraints are applied to fields on """Ensure that uniqueness constraints are applied to fields on
embedded documents. And work with unique_with as well. embedded documents. And work with unique_with as well.
@@ -798,33 +763,6 @@ class IndexesTest(unittest.TestCase):
key = indexes["title_text"]["key"] key = indexes["title_text"]["key"]
self.assertTrue(('_fts', 'text') in 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__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@@ -397,16 +397,6 @@ class InheritanceTest(unittest.TestCase):
meta = {'abstract': True} meta = {'abstract': True}
self.assertRaises(ValueError, create_bad_abstract) 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): def test_inherited_collections(self):
"""Ensure that subclassed documents don't override parents' """Ensure that subclassed documents don't override parents'
collections collections

View File

@@ -9,7 +9,7 @@ import unittest
import uuid import uuid
from datetime import datetime from datetime import datetime
from bson import DBRef, ObjectId from bson import DBRef
from tests.fixtures import (PickleEmbedded, PickleTest, PickleSignalsTest, from tests.fixtures import (PickleEmbedded, PickleTest, PickleSignalsTest,
PickleDyanmicEmbedded, PickleDynamicTest) PickleDyanmicEmbedded, PickleDynamicTest)
@@ -34,21 +34,15 @@ class InstanceTest(unittest.TestCase):
connect(db='mongoenginetest') connect(db='mongoenginetest')
self.db = get_db() self.db = get_db()
class Job(EmbeddedDocument):
name = StringField()
years = IntField()
class Person(Document): class Person(Document):
name = StringField() name = StringField()
age = IntField() age = IntField()
job = EmbeddedDocumentField(Job)
non_field = True non_field = True
meta = {"allow_inheritance": True} meta = {"allow_inheritance": True}
self.Person = Person self.Person = Person
self.Job = Job
def tearDown(self): def tearDown(self):
for collection in self.db.collection_names(): for collection in self.db.collection_names():
@@ -56,11 +50,6 @@ class InstanceTest(unittest.TestCase):
continue continue
self.db.drop_collection(collection) 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): def test_capped_collection(self):
"""Ensure that capped collections work properly. """Ensure that capped collections work properly.
""" """
@@ -114,19 +103,6 @@ class InstanceTest(unittest.TestCase):
self.assertEqual('<Article: привет мир>', repr(doc)) 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): def test_queryset_resurrects_dropped_collection(self):
self.Person.drop_collection() self.Person.drop_collection()
@@ -146,18 +122,10 @@ class InstanceTest(unittest.TestCase):
""" """
class Animal(Document): class Animal(Document):
meta = {'allow_inheritance': True} meta = {'allow_inheritance': True}
class Fish(Animal): pass
class Fish(Animal): class Mammal(Animal): pass
pass class Dog(Mammal): pass
class Human(Mammal): pass
class Mammal(Animal):
pass
class Dog(Mammal):
pass
class Human(Mammal):
pass
class Zoo(Document): class Zoo(Document):
animals = ListField(ReferenceField(Animal)) animals = ListField(ReferenceField(Animal))
@@ -469,7 +437,7 @@ class InstanceTest(unittest.TestCase):
f.reload() f.reload()
except Foo.DoesNotExist: except Foo.DoesNotExist:
pass pass
except Exception: except Exception as ex:
self.assertFalse("Threw wrong exception") self.assertFalse("Threw wrong exception")
f.save() f.save()
@@ -478,13 +446,13 @@ class InstanceTest(unittest.TestCase):
f.reload() f.reload()
except Foo.DoesNotExist: except Foo.DoesNotExist:
pass pass
except Exception: except Exception as ex:
self.assertFalse("Threw wrong exception") self.assertFalse("Threw wrong exception")
def test_dictionary_access(self): def test_dictionary_access(self):
"""Ensure that dictionary-style field access works properly. """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.assertEqual(person['name'], 'Test User')
self.assertRaises(KeyError, person.__getitem__, 'salary') self.assertRaises(KeyError, person.__getitem__, 'salary')
@@ -494,7 +462,7 @@ class InstanceTest(unittest.TestCase):
self.assertEqual(person['name'], 'Another User') self.assertEqual(person['name'], 'Another User')
# Length = length(assigned fields + id) # Length = length(assigned fields + id)
self.assertEqual(len(person), 5) self.assertEqual(len(person), 4)
self.assertTrue('age' in person) self.assertTrue('age' in person)
person.age = None person.age = None
@@ -513,9 +481,8 @@ class InstanceTest(unittest.TestCase):
self.assertEqual(Person(name="Bob", age=35).to_mongo().keys(), self.assertEqual(Person(name="Bob", age=35).to_mongo().keys(),
['_cls', 'name', 'age']) ['_cls', 'name', 'age'])
self.assertEqual( self.assertEqual(Employee(name="Bob", age=35, salary=0).to_mongo().keys(),
Employee(name="Bob", age=35, salary=0).to_mongo().keys(), ['_cls', 'name', 'age', 'salary'])
['_cls', 'name', 'age', 'salary'])
def test_embedded_document_to_mongo_id(self): def test_embedded_document_to_mongo_id(self):
class SubDoc(EmbeddedDocument): class SubDoc(EmbeddedDocument):
@@ -650,64 +617,6 @@ class InstanceTest(unittest.TestCase):
t = TestDocument(doc=TestEmbeddedDocument(x=15, y=35, z=5)) t = TestDocument(doc=TestEmbeddedDocument(x=15, y=35, z=5))
t.save(clean=False) 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): def test_save(self):
"""Ensure that a document may be saved in the database. """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)) w1 = Widget(toggle=False, save_id=UUID(1))
# ignore save_condition on new record creation # 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() w1.reload()
self.assertFalse(w1.toggle) self.assertFalse(w1.toggle)
self.assertEqual(w1.save_id, UUID(1)) self.assertEqual(w1.save_id, UUID(1))
@@ -956,7 +865,7 @@ class InstanceTest(unittest.TestCase):
flip(w1) flip(w1)
self.assertTrue(w1.toggle) self.assertTrue(w1.toggle)
self.assertEqual(w1.count, 1) self.assertEqual(w1.count, 1)
w1.save(save_condition={'save_id': UUID(42)}) w1.save(save_condition={'save_id':UUID(42)})
w1.reload() w1.reload()
self.assertFalse(w1.toggle) self.assertFalse(w1.toggle)
self.assertEqual(w1.count, 0) self.assertEqual(w1.count, 0)
@@ -965,7 +874,7 @@ class InstanceTest(unittest.TestCase):
flip(w1) flip(w1)
self.assertTrue(w1.toggle) self.assertTrue(w1.toggle)
self.assertEqual(w1.count, 1) self.assertEqual(w1.count, 1)
w1.save(save_condition={'save_id': UUID(1)}) w1.save(save_condition={'save_id':UUID(1)})
w1.reload() w1.reload()
self.assertTrue(w1.toggle) self.assertTrue(w1.toggle)
self.assertEqual(w1.count, 1) self.assertEqual(w1.count, 1)
@@ -978,25 +887,25 @@ class InstanceTest(unittest.TestCase):
flip(w1) flip(w1)
w1.save_id = UUID(2) w1.save_id = UUID(2)
w1.save(save_condition={'save_id': old_id}) w1.save(save_condition={'save_id':old_id})
w1.reload() w1.reload()
self.assertFalse(w1.toggle) self.assertFalse(w1.toggle)
self.assertEqual(w1.count, 2) self.assertEqual(w1.count, 2)
flip(w2) flip(w2)
flip(w2) flip(w2)
w2.save(save_condition={'save_id': old_id}) w2.save(save_condition={'save_id':old_id})
w2.reload() w2.reload()
self.assertFalse(w2.toggle) self.assertFalse(w2.toggle)
self.assertEqual(w2.count, 2) self.assertEqual(w2.count, 2)
# save_condition uses mongoengine-style operator syntax # save_condition uses mongoengine-style operator syntax
flip(w1) flip(w1)
w1.save(save_condition={'count__lt': w1.count}) w1.save(save_condition={'count__lt':w1.count})
w1.reload() w1.reload()
self.assertTrue(w1.toggle) self.assertTrue(w1.toggle)
self.assertEqual(w1.count, 3) self.assertEqual(w1.count, 3)
flip(w1) flip(w1)
w1.save(save_condition={'count__gte': w1.count}) w1.save(save_condition={'count__gte':w1.count})
w1.reload() w1.reload()
self.assertTrue(w1.toggle) self.assertTrue(w1.toggle)
self.assertEqual(w1.count, 3) self.assertEqual(w1.count, 3)
@@ -1442,8 +1351,7 @@ class InstanceTest(unittest.TestCase):
self.assertEqual(str(person_obj['_id']), '497ce96f395f2f052a494fd4') self.assertEqual(str(person_obj['_id']), '497ce96f395f2f052a494fd4')
def test_save_custom_pk(self): 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 # Create person object and save it to the database
person = self.Person(name='Test User', age=30, 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 = Page(comments=[Comment(user=u2, comment="Heavy Metal song")])
p4.save() p4.save()
self.assertEqual( self.assertEqual([p1, p2], list(Page.objects.filter(comments__user=u1)))
[p1, p2], self.assertEqual([p1, p2, p4], list(Page.objects.filter(comments__user=u2)))
list(Page.objects.filter(comments__user=u1))) self.assertEqual([p1, p3], list(Page.objects.filter(comments__user=u3)))
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): def test_save_embedded_document(self):
"""Ensure that a document with an embedded document field may be """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) self.assertEqual(promoted_employee.age, 50)
# Ensure that the 'details' embedded object saved correctly # Ensure that the 'details' embedded object saved correctly
self.assertEqual( self.assertEqual(promoted_employee.details.position, 'Senior Developer')
promoted_employee.details.position, 'Senior Developer')
# Test removal # Test removal
promoted_employee.details = None promoted_employee.details = None
@@ -1749,8 +1650,7 @@ class InstanceTest(unittest.TestCase):
post.save() post.save()
reviewer.delete() reviewer.delete()
# No effect on the BlogPost self.assertEqual(BlogPost.objects.count(), 1) # No effect on the BlogPost
self.assertEqual(BlogPost.objects.count(), 1)
self.assertEqual(BlogPost.objects.get().reviewer, None) self.assertEqual(BlogPost.objects.get().reviewer, None)
# Delete the Person, which should lead to deletion of the BlogPost, too # Delete the Person, which should lead to deletion of the BlogPost, too
@@ -1799,10 +1699,8 @@ class InstanceTest(unittest.TestCase):
class BlogPost(Document): class BlogPost(Document):
content = StringField() content = StringField()
authors = ListField(ReferenceField( authors = ListField(ReferenceField(self.Person, reverse_delete_rule=CASCADE))
self.Person, reverse_delete_rule=CASCADE)) reviewers = ListField(ReferenceField(self.Person, reverse_delete_rule=NULLIFY))
reviewers = ListField(ReferenceField(
self.Person, reverse_delete_rule=NULLIFY))
self.Person.drop_collection() 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.count(), 1) # No effect on the BlogPost
self.assertEqual(Bar.objects.get().foo, None) 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(): def throw_invalid_document_error():
class Blog(Document): class Blog(Document):
content = StringField() content = StringField()
authors = MapField(ReferenceField( authors = MapField(ReferenceField(self.Person, reverse_delete_rule=CASCADE))
self.Person, reverse_delete_rule=CASCADE)) reviewers = DictField(field=ReferenceField(self.Person, reverse_delete_rule=NULLIFY))
reviewers = DictField(
field=ReferenceField(
self.Person,
reverse_delete_rule=NULLIFY))
self.assertRaises(InvalidDocumentError, throw_invalid_document_error) self.assertRaises(InvalidDocumentError, throw_invalid_document_error)
@@ -1916,8 +1810,7 @@ class InstanceTest(unittest.TestCase):
father = ReferenceField('Person', reverse_delete_rule=DENY) father = ReferenceField('Person', reverse_delete_rule=DENY)
mother = ReferenceField('Person', reverse_delete_rule=DENY) mother = ReferenceField('Person', reverse_delete_rule=DENY)
self.assertRaises( self.assertRaises(InvalidDocumentError, throw_invalid_document_error_embedded)
InvalidDocumentError, throw_invalid_document_error_embedded)
def test_reverse_delete_rule_cascade_recurs(self): def test_reverse_delete_rule_cascade_recurs(self):
"""Ensure that a chain of documents is also deleted upon cascaded """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 = self.Person(name='Test User')
author.save() author.save()
post = BlogPost(content='Watched some TV') post = BlogPost(content = 'Watched some TV')
post.author = author post.author = author
post.save() post.save()
comment = Comment(text='Kudos.') comment = Comment(text = 'Kudos.')
comment.post = post comment.post = post
comment.save() comment.save()
# Delete the Person, which should lead to deletion of the BlogPost, # Delete the Person, which should lead to deletion of the BlogPost, and,
# and, recursively to the Comment, too # recursively to the Comment, too
author.delete() author.delete()
self.assertEqual(Comment.objects.count(), 0) self.assertEqual(Comment.objects.count(), 0)
@@ -1971,7 +1864,7 @@ class InstanceTest(unittest.TestCase):
author = self.Person(name='Test User') author = self.Person(name='Test User')
author.save() author.save()
post = BlogPost(content='Watched some TV') post = BlogPost(content = 'Watched some TV')
post.author = author post.author = author
post.save() post.save()
@@ -2087,8 +1980,7 @@ class InstanceTest(unittest.TestCase):
def test_dynamic_document_pickle(self): def test_dynamic_document_pickle(self):
pickle_doc = PickleDynamicTest( pickle_doc = PickleDynamicTest(name="test", number=1, string="One", lists=['1', '2'])
name="test", number=1, string="One", lists=['1', '2'])
pickle_doc.embedded = PickleDyanmicEmbedded(foo="Bar") pickle_doc.embedded = PickleDyanmicEmbedded(foo="Bar")
pickled_doc = pickle.dumps(pickle_doc) # make sure pickling works even before the doc is saved 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()) pickle_doc.embedded._dynamic_fields.keys())
def test_picklable_on_signals(self): def test_picklable_on_signals(self):
pickle_doc = PickleSignalsTest( pickle_doc = PickleSignalsTest(number=1, string="One", lists=['1', '2'])
number=1, string="One", lists=['1', '2'])
pickle_doc.embedded = PickleEmbedded() pickle_doc.embedded = PickleEmbedded()
pickle_doc.save() pickle_doc.save()
pickle_doc.delete() pickle_doc.delete()
@@ -2266,15 +2157,9 @@ class InstanceTest(unittest.TestCase):
self.assertEqual(AuthorBooks._get_db(), get_db("testdb-3")) self.assertEqual(AuthorBooks._get_db(), get_db("testdb-3"))
# Collections # Collections
self.assertEqual( self.assertEqual(User._get_collection(), get_db("testdb-1")[User._get_collection_name()])
User._get_collection(), self.assertEqual(Book._get_collection(), get_db("testdb-2")[Book._get_collection_name()])
get_db("testdb-1")[User._get_collection_name()]) self.assertEqual(AuthorBooks._get_collection(), get_db("testdb-3")[AuthorBooks._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): def test_db_alias_overrides(self):
"""db_alias can be overriden """db_alias can be overriden
@@ -2443,6 +2328,30 @@ class InstanceTest(unittest.TestCase):
group = Group.objects.first() group = Group.objects.first()
self.assertEqual("hello - default", group.name) 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): def test_spaces_in_keys(self):
class Embedded(DynamicEmbeddedDocument): class Embedded(DynamicEmbeddedDocument):
@@ -2518,10 +2427,6 @@ class InstanceTest(unittest.TestCase):
doc_name = StringField() doc_name = StringField()
doc = EmbeddedDocumentField(Embedded) 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")) classic_doc = Doc(doc_name="my doc", doc=Embedded(name="embedded doc"))
dict_doc = Doc(**{"doc_name": "my doc", dict_doc = Doc(**{"doc_name": "my doc",
"doc": {"name": "embedded doc"}}) "doc": {"name": "embedded doc"}})
@@ -2538,10 +2443,6 @@ class InstanceTest(unittest.TestCase):
doc_name = StringField() doc_name = StringField()
docs = ListField(EmbeddedDocumentField(Embedded)) 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=[ classic_doc = Doc(doc_name="my doc", docs=[
Embedded(name="embedded doc1"), Embedded(name="embedded doc1"),
Embedded(name="embedded doc2")]) Embedded(name="embedded doc2")])
@@ -2636,9 +2537,7 @@ class InstanceTest(unittest.TestCase):
system.save() system.save()
system = NodesSystem.objects.first() system = NodesSystem.objects.first()
self.assertEqual( self.assertEqual("UNDEFINED", system.nodes["node"].parameters["param"].macros["test"].value)
"UNDEFINED",
system.nodes["node"].parameters["param"].macros["test"].value)
def test_embedded_document_equality(self): def test_embedded_document_equality(self):
@@ -2744,60 +2643,5 @@ class InstanceTest(unittest.TestCase):
self.assertEquals(p4.height, 189) self.assertEquals(p4.height, 189)
self.assertEquals(Person.objects(height=189).count(), 1) 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__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@@ -51,10 +51,6 @@ class TestJson(unittest.TestCase):
string = StringField() string = StringField()
embedded_field = EmbeddedDocumentField(Embedded) 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 = Doc(string="Hi", embedded_field=Embedded(string="Hi"))
doc_json = doc.to_json(sort_keys=True, separators=(',', ':')) doc_json = doc.to_json(sort_keys=True, separators=(',', ':'))
@@ -103,10 +99,6 @@ class TestJson(unittest.TestCase):
generic_embedded_document_field = GenericEmbeddedDocumentField( generic_embedded_document_field = GenericEmbeddedDocumentField(
default=lambda: EmbeddedDoc()) default=lambda: EmbeddedDoc())
def __eq__(self, other):
import json
return json.loads(self.to_json()) == json.loads(other.to_json())
doc = Doc() doc = Doc()
self.assertEqual(doc, Doc.from_json(doc.to_json())) 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 import *
from mongoengine.connection import get_db from mongoengine.connection import get_db
from mongoengine.base import _document_registry 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.errors import NotRegistered
from mongoengine.python_support import PY3, b, bin_type from mongoengine.python_support import PY3, b, bin_type
__all__ = ("FieldTest", "EmbeddedDocumentListFieldTestCase") __all__ = ("FieldTest", )
class FieldTest(unittest.TestCase): class FieldTest(unittest.TestCase):
@@ -1176,11 +1176,6 @@ class FieldTest(unittest.TestCase):
post.reload() post.reload()
self.assertEqual('updated', post.info['title']) self.assertEqual('updated', post.info['title'])
post.info.setdefault('authors', [])
post.save()
post.reload()
self.assertEqual([], post.info['authors'])
BlogPost.drop_collection() BlogPost.drop_collection()
def test_dictfield_strict(self): def test_dictfield_strict(self):
@@ -1819,7 +1814,7 @@ class FieldTest(unittest.TestCase):
Animal.drop_collection() Animal.drop_collection()
Ocorrence.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") owner=Owner(tp='u', name="Wilson Júnior")
) )
a.save() a.save()
@@ -1869,7 +1864,7 @@ class FieldTest(unittest.TestCase):
Animal.drop_collection() Animal.drop_collection()
Ocorrence.drop_collection() Ocorrence.drop_collection()
a = Animal(name="Leopard", tag="heavy", a = Animal(nam="Leopard", tag="heavy",
owner=Owner(tags=['cool', 'funny'], owner=Owner(tags=['cool', 'funny'],
name="Wilson Júnior") name="Wilson Júnior")
) )
@@ -1979,14 +1974,14 @@ class FieldTest(unittest.TestCase):
def test_recursive_embedding(self): def test_recursive_embedding(self):
"""Ensure that EmbeddedDocumentFields can contain their own documents. """Ensure that EmbeddedDocumentFields can contain their own documents.
""" """
class TreeNode(EmbeddedDocument):
name = StringField()
children = ListField(EmbeddedDocumentField('self'))
class Tree(Document): class Tree(Document):
name = StringField() name = StringField()
children = ListField(EmbeddedDocumentField('TreeNode')) children = ListField(EmbeddedDocumentField('TreeNode'))
class TreeNode(EmbeddedDocument):
name = StringField()
children = ListField(EmbeddedDocumentField('self'))
Tree.drop_collection() Tree.drop_collection()
tree = Tree(name="Tree") tree = Tree(name="Tree")
@@ -2451,79 +2446,6 @@ class FieldTest(unittest.TestCase):
Shirt.drop_collection() 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): def test_choices_get_field_display(self):
"""Test dynamic helper for returning the display value of a choices """Test dynamic helper for returning the display value of a choices
field. field.
@@ -2795,11 +2717,9 @@ class FieldTest(unittest.TestCase):
class Animal(Document): class Animal(Document):
id = SequenceField(primary_key=True) id = SequenceField(primary_key=True)
name = StringField()
class Person(Document): class Person(Document):
id = SequenceField(primary_key=True) id = SequenceField(primary_key=True)
name = StringField()
self.db['mongoengine.counters'].drop() self.db['mongoengine.counters'].drop()
Animal.drop_collection() 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.Mammal.Dog", "Animal.Fish"]).count(), 2)
self.assertEquals(Animal.objects(_cls__in=["Animal.Fish.Guppy"]).count(), 0) 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__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@@ -114,42 +114,6 @@ class FileTest(unittest.TestCase):
# Ensure deleted file returns None # Ensure deleted file returns None
self.assertTrue(result.the_file.read() == 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): def test_file_fields_set(self):
class SetFile(Document): 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].by, 'joe')
self.assertEqual(post.comments[0].votes.score, 4) 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): def test_updates_can_have_match_operators(self):
class Comment(EmbeddedDocument):
content = StringField()
name = StringField(max_length=120)
vote = IntField()
class Post(Document): class Post(Document):
title = StringField(required=True) title = StringField(required=True)
tags = ListField(StringField()) tags = ListField(StringField())
comments = ListField(EmbeddedDocumentField("Comment")) comments = ListField(EmbeddedDocumentField("Comment"))
class Comment(EmbeddedDocument):
content = StringField()
name = StringField(max_length=120)
vote = IntField()
Post.drop_collection() Post.drop_collection()
comm1 = Comment(content="very funny indeed", name="John S", vote=1) comm1 = Comment(content="very funny indeed", name="John S", vote=1)
@@ -925,7 +914,7 @@ class QuerySetTest(unittest.TestCase):
docs = docs[1:4] docs = docs[1:4]
self.assertEqual('[<Doc: 1>, <Doc: 2>, <Doc: 3>]', "%s" % docs) 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: for doc in docs:
self.assertEqual('.. queryset mid-iteration ..', repr(docs)) self.assertEqual('.. queryset mid-iteration ..', repr(docs))
@@ -1313,31 +1302,6 @@ class QuerySetTest(unittest.TestCase):
self.Person.objects(name='Test User').delete() self.Person.objects(name='Test User').delete()
self.assertEqual(1, BlogPost.objects.count()) 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): def test_reverse_delete_rule_cascade_self_referencing(self):
"""Ensure self-referencing CASCADE deletes do not result in infinite """Ensure self-referencing CASCADE deletes do not result in infinite
loop loop
@@ -1397,31 +1361,6 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(1, BlogPost.objects.count()) self.assertEqual(1, BlogPost.objects.count())
self.assertEqual(None, BlogPost.objects.first().category) 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): def test_reverse_delete_rule_deny(self):
"""Ensure deletion gets denied on documents that still have references """Ensure deletion gets denied on documents that still have references
to them. to them.
@@ -1441,26 +1380,6 @@ class QuerySetTest(unittest.TestCase):
self.assertRaises(OperationError, self.Person.objects.delete) 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): def test_reverse_delete_rule_pull(self):
"""Ensure pulling of references to deleted documents. """Ensure pulling of references to deleted documents.
""" """
@@ -1491,40 +1410,6 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(post.authors, [me]) self.assertEqual(post.authors, [me])
self.assertEqual(another.authors, []) 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): def test_delete_with_limits(self):
class Log(Document): class Log(Document):
@@ -1607,7 +1492,6 @@ class QuerySetTest(unittest.TestCase):
"""Ensure that atomic updates work properly. """Ensure that atomic updates work properly.
""" """
class BlogPost(Document): class BlogPost(Document):
name = StringField()
title = StringField() title = StringField()
hits = IntField() hits = IntField()
tags = ListField(StringField()) tags = ListField(StringField())
@@ -2899,23 +2783,25 @@ class QuerySetTest(unittest.TestCase):
self.assertTrue('dilma' in new.content) self.assertTrue('dilma' in new.content)
self.assertTrue('planejamento' in new.title) self.assertTrue('planejamento' in new.title)
query = News.objects.search_text("candidata") query = News.objects.search_text(
self.assertEqual(query._search_text, "candidata") "candidata", include_text_scores=True)
self.assertTrue(query._include_text_scores)
new = query.first() new = query.first()
self.assertTrue(isinstance(new.get_text_score(), float)) self.assertTrue(isinstance(new.text_score, float))
# count # count
query = News.objects.search_text('brasil').order_by('$text_score') 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.count(), 3)
self.assertEqual(query._query, {'$text': {'$search': 'brasil'}}) self.assertEqual(query._query, {'$text': {'$search': 'brasil'}})
cursor_args = query._cursor_args cursor_args = query._cursor_args
self.assertEqual( 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.assertEqual(len(text_scores), 3)
self.assertTrue(text_scores[0] > text_scores[1]) self.assertTrue(text_scores[0] > text_scores[1])
@@ -2925,7 +2811,7 @@ class QuerySetTest(unittest.TestCase):
# get item # get item
item = News.objects.search_text( item = News.objects.search_text(
'brasil').order_by('$text_score').first() '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 @skip_older_mongodb
def test_distinct_handles_references_to_alias(self): def test_distinct_handles_references_to_alias(self):
@@ -2992,55 +2878,13 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(authors, [mark_twain, john_tolkien]) 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): def test_distinct_ListField_ReferenceField(self):
class Foo(Document):
bar_lst = ListField(ReferenceField('Bar'))
class Bar(Document): class Bar(Document):
text = StringField() text = StringField()
class Foo(Document):
bar = ReferenceField('Bar')
bar_lst = ListField(ReferenceField('Bar'))
Bar.drop_collection() Bar.drop_collection()
Foo.drop_collection() Foo.drop_collection()
@@ -3400,7 +3244,7 @@ class QuerySetTest(unittest.TestCase):
for i in xrange(10): for i in xrange(10):
Post(title="Post %s" % i).save() 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( self.assertEqual(
10, Post.objects.limit(5).skip(5).count(with_limit_and_skip=False)) 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(100, people._len) # Caused by list calling len
self.assertEqual(q, 1) self.assertEqual(q, 1)
people.count(with_limit_and_skip=True) # count is cached people.count() # count is cached
self.assertEqual(q, 1) self.assertEqual(q, 1)
def test_no_cached_queryset(self): def test_no_cached_queryset(self):
@@ -4265,7 +4109,7 @@ class QuerySetTest(unittest.TestCase):
with query_counter() as q: with query_counter() as q:
self.assertEqual(q, 0) 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): for i, outer_user in enumerate(users):
self.assertEqual(outer_user.name, names[i]) self.assertEqual(outer_user.name, names[i])
@@ -4273,7 +4117,7 @@ class QuerySetTest(unittest.TestCase):
inner_count = 0 inner_count = 0
# Calling len might disrupt the inner loop if there are bugs # 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): for j, inner_user in enumerate(users):
self.assertEqual(inner_user.name, names[j]) self.assertEqual(inner_user.name, names[j])
@@ -4557,27 +4401,6 @@ class QuerySetTest(unittest.TestCase):
self.Person.objects().delete() self.Person.objects().delete()
self.assertEqual(self.Person.objects().skip(1).delete(), 0) # test Document delete without existing documents 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__': if __name__ == '__main__':
unittest.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]]]}) 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]]]}}}) 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__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

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

View File

@@ -19,12 +19,9 @@ settings.configure(
AUTHENTICATION_BACKENDS = ('mongoengine.django.auth.MongoEngineBackend',) AUTHENTICATION_BACKENDS = ('mongoengine.django.auth.MongoEngineBackend',)
) )
try: # For Django >= 1.7
# For Django >= 1.7 if hasattr(django, 'setup'):
if hasattr(django, 'setup'): django.setup()
django.setup()
except RuntimeError:
pass
try: try:
from django.contrib.auth import authenticate, get_user_model from django.contrib.auth import authenticate, get_user_model
@@ -37,6 +34,7 @@ try:
DJ15 = True DJ15 = True
except Exception: except Exception:
DJ15 = False DJ15 = False
from django.contrib.sessions.tests import SessionTestsMixin
from mongoengine.django.sessions import SessionStore, MongoSession from mongoengine.django.sessions import SessionStore, MongoSession
from mongoengine.django.tests import MongoTestCase from mongoengine.django.tests import MongoTestCase
from datetime import tzinfo, timedelta from datetime import tzinfo, timedelta
@@ -172,13 +170,8 @@ class QuerySetTest(unittest.TestCase):
"""Ensure that a queryset and filters work as expected """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): class Note(Document):
meta = dict(queryset_class=LimitCountQuerySet) text = StringField()
name = StringField()
Note.drop_collection() Note.drop_collection()
@@ -228,13 +221,13 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(t.render(c), "10") self.assertEqual(t.render(c), "10")
class _BaseMongoDBSessionTest(unittest.TestCase): class MongoDBSessionTest(SessionTestsMixin, unittest.TestCase):
backend = SessionStore backend = SessionStore
def setUp(self): def setUp(self):
connect(db='mongoenginetest') connect(db='mongoenginetest')
MongoSession.drop_collection() MongoSession.drop_collection()
super(_BaseMongoDBSessionTest, self).setUp() super(MongoDBSessionTest, self).setUp()
def assertIn(self, first, second, msg=None): def assertIn(self, first, second, msg=None):
self.assertTrue(first in second, msg) 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') 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): class MongoAuthTest(unittest.TestCase):
user_data = { user_data = {
'username': 'user', 'username': 'user',