Compare commits

..

No commits in common. "master" and "v0.8.3" have entirely different histories.

37 changed files with 177 additions and 713 deletions

View file

@ -16,6 +16,7 @@ env:
install: install:
- if [[ $TRAVIS_PYTHON_VERSION == '2.'* ]]; then cp /usr/lib/*/libz.so $VIRTUAL_ENV/lib/; fi - if [[ $TRAVIS_PYTHON_VERSION == '2.'* ]]; then cp /usr/lib/*/libz.so $VIRTUAL_ENV/lib/; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.'* ]]; then pip install pil --use-mirrors ; true; fi - if [[ $TRAVIS_PYTHON_VERSION == '2.'* ]]; then pip install pil --use-mirrors ; true; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.'* ]]; then pip install django==$DJANGO --use-mirrors ; true; fi
- if [[ $PYMONGO == 'dev' ]]; then pip install https://github.com/mongodb/mongo-python-driver/tarball/master; true; fi - if [[ $PYMONGO == 'dev' ]]; then pip install https://github.com/mongodb/mongo-python-driver/tarball/master; true; fi
- if [[ $PYMONGO != 'dev' ]]; then pip install pymongo==$PYMONGO --use-mirrors; true; fi - if [[ $PYMONGO != 'dev' ]]; then pip install pymongo==$PYMONGO --use-mirrors; true; fi
- pip install https://pypi.python.org/packages/source/p/python-dateutil/python-dateutil-2.1.tar.gz#md5=1534bb15cf311f07afaa3aacba1c028b - pip install https://pypi.python.org/packages/source/p/python-dateutil/python-dateutil-2.1.tar.gz#md5=1534bb15cf311f07afaa3aacba1c028b

View file

@ -172,10 +172,3 @@ that much better:
* Alon Horev (https://github.com/alonho) * Alon Horev (https://github.com/alonho)
* Kelvin Hammond (https://github.com/kelvinhammond) * Kelvin Hammond (https://github.com/kelvinhammond)
* Jatin- (https://github.com/jatin-) * Jatin- (https://github.com/jatin-)
* Paul Uithol (https://github.com/PaulUithol)
* Thom Knowles (https://github.com/fleat)
* Paul (https://github.com/squamous)
* Olivier Cortès (https://github.com/Karmak23)
* crazyzubr (https://github.com/crazyzubr)
* FrankSomething (https://github.com/FrankSomething)
* Alexandr Morozov (https://github.com/LK4D4)

View file

@ -5,10 +5,6 @@
@import url("basic.css"); @import url("basic.css");
#changelog p.first {margin-bottom: 0 !important;}
#changelog p {margin-top: 0 !important;
margin-bottom: 0 !important;}
/* -- page layout ----------------------------------------------------------- */ /* -- page layout ----------------------------------------------------------- */
body { body {

View file

@ -44,21 +44,17 @@ Context Managers
Querying Querying
======== ========
.. automodule:: mongoengine.queryset .. autoclass:: mongoengine.queryset.QuerySet
:synopsis: Queryset level operations :members:
.. autoclass:: mongoengine.queryset.QuerySet .. automethod:: mongoengine.queryset.QuerySet.__call__
:members:
:inherited-members:
.. automethod:: QuerySet.__call__ .. autoclass:: mongoengine.queryset.QuerySetNoCache
:members:
.. autoclass:: mongoengine.queryset.QuerySetNoCache .. automethod:: mongoengine.queryset.QuerySetNoCache.__call__
:members:
.. automethod:: mongoengine.queryset.QuerySetNoCache.__call__ .. autofunction:: mongoengine.queryset.queryset_manager
.. autofunction:: mongoengine.queryset.queryset_manager
Fields Fields
====== ======

View file

@ -2,26 +2,6 @@
Changelog Changelog
========= =========
Changes in 0.8.4
================
- Remove database name necessity in uri connection schema (#452)
- Fixed "$pull" semantics for nested ListFields (#447)
- Allow fields to be named the same as query operators (#445)
- Updated field filter logic - can now exclude subclass fields (#443)
- Fixed dereference issue with embedded listfield referencefields (#439)
- Fixed slice when using inheritance causing fields to be excluded (#437)
- Fixed ._get_db() attribute after a Document.switch_db() (#441)
- Dynamic Fields store and recompose Embedded Documents / Documents correctly (#449)
- Handle dynamic fieldnames that look like digits (#434)
- Added get_user_document and improve mongo_auth module (#423)
- Added str representation of GridFSProxy (#424)
- Update transform to handle docs erroneously passed to unset (#416)
- Fixed indexing - turn off _cls (#414)
- Fixed dereference threading issue in ComplexField.__get__ (#412)
- Fixed QuerySetNoCache.count() caching (#410)
- Don't follow references in _get_changed_fields (#422, #417)
- Allow args and kwargs to be passed through to_json (#420)
Changes in 0.8.3 Changes in 0.8.3
================ ================
- Fixed EmbeddedDocuments with `id` also storing `_id` (#402) - Fixed EmbeddedDocuments with `id` also storing `_id` (#402)
@ -32,9 +12,6 @@ Changes in 0.8.3
- Document.select_related() now respects `db_alias` (#377) - Document.select_related() now respects `db_alias` (#377)
- Reload uses shard_key if applicable (#384) - Reload uses shard_key if applicable (#384)
- Dynamic fields are ordered based on creation and stored in _fields_ordered (#396) - Dynamic fields are ordered based on creation and stored in _fields_ordered (#396)
**Potential breaking change:** http://docs.mongoengine.org/en/latest/upgrade.html#to-0-8-3
- Fixed pickling dynamic documents `_dynamic_fields` (#387) - Fixed pickling dynamic documents `_dynamic_fields` (#387)
- Fixed ListField setslice and delslice dirty tracking (#390) - Fixed ListField setslice and delslice dirty tracking (#390)
- Added Django 1.5 PY3 support (#392) - Added Django 1.5 PY3 support (#392)
@ -43,8 +20,6 @@ Changes in 0.8.3
- Fixed queryset.get() respecting no_dereference (#373) - Fixed queryset.get() respecting no_dereference (#373)
- Added full_result kwarg to update (#380) - Added full_result kwarg to update (#380)
Changes in 0.8.2 Changes in 0.8.2
================ ================
- Added compare_indexes helper (#361) - Added compare_indexes helper (#361)

View file

@ -45,7 +45,7 @@ The :mod:`~mongoengine.django.auth` module also contains a
Custom User model Custom User model
================= =================
Django 1.5 introduced `Custom user Models Django 1.5 introduced `Custom user Models
<https://docs.djangoproject.com/en/dev/topics/auth/customizing/#auth-custom-user>`_ <https://docs.djangoproject.com/en/dev/topics/auth/customizing/#auth-custom-user>`
which can be used as an alternative to the MongoEngine authentication backend. which can be used as an alternative to the MongoEngine authentication backend.
The main advantage of this option is that other components relying on The main advantage of this option is that other components relying on
@ -74,7 +74,7 @@ An additional ``MONGOENGINE_USER_DOCUMENT`` setting enables you to replace the
The custom :class:`User` must be a :class:`~mongoengine.Document` class, but The custom :class:`User` must be a :class:`~mongoengine.Document` class, but
otherwise has the same requirements as a standard custom user model, otherwise has the same requirements as a standard custom user model,
as specified in the `Django Documentation as specified in the `Django Documentation
<https://docs.djangoproject.com/en/dev/topics/auth/customizing/>`_. <https://docs.djangoproject.com/en/dev/topics/auth/customizing/>`.
In particular, the custom class must define :attr:`USERNAME_FIELD` and In particular, the custom class must define :attr:`USERNAME_FIELD` and
:attr:`REQUIRED_FIELDS` attributes. :attr:`REQUIRED_FIELDS` attributes.
@ -128,7 +128,7 @@ appended to the filename until the generated filename doesn't exist. The
>>> fs.listdir() >>> fs.listdir()
([], [u'hello.txt']) ([], [u'hello.txt'])
All files will be saved and retrieved in GridFS via the :class:`FileDocument` All files will be saved and retrieved in GridFS via the :class::`FileDocument`
document, allowing easy access to the files without the GridFSStorage document, allowing easy access to the files without the GridFSStorage
backend.:: backend.::
@ -137,36 +137,3 @@ backend.::
[<FileDocument: FileDocument object>] [<FileDocument: FileDocument object>]
.. versionadded:: 0.4 .. versionadded:: 0.4
Shortcuts
=========
Inspired by the `Django shortcut get_object_or_404
<https://docs.djangoproject.com/en/dev/topics/http/shortcuts/#get-object-or-404>`_,
the :func:`~mongoengine.django.shortcuts.get_document_or_404` method returns
a document or raises an Http404 exception if the document does not exist::
from mongoengine.django.shortcuts import get_document_or_404
admin_user = get_document_or_404(User, username='root')
The first argument may be a Document or QuerySet object. All other passed arguments
and keyword arguments are used in the query::
foo_email = get_document_or_404(User.objects.only('email'), username='foo', is_active=True).email
.. note:: Like with :func:`get`, a MultipleObjectsReturned will be raised if more than one
object is found.
Also inspired by the `Django shortcut get_list_or_404
<https://docs.djangoproject.com/en/dev/topics/http/shortcuts/#get-list-or-404>`_,
the :func:`~mongoengine.django.shortcuts.get_list_or_404` method returns a list of
documents or raises an Http404 exception if the list is empty::
from mongoengine.django.shortcuts import get_list_or_404
active_users = get_list_or_404(User, is_active=True)
The first argument may be a Document or QuerySet object. All other passed
arguments and keyword arguments are used to filter the query.

View file

@ -23,15 +23,12 @@ 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 as long as you include the database
the :attr:`host` to name - just supply the uri as 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 that database name from uri has priority over name
in ::func:`~mongoengine.connect`
ReplicaSets ReplicaSets
=========== ===========

View file

@ -442,8 +442,6 @@ The following example shows a :class:`Log` document that will be limited to
ip_address = StringField() ip_address = StringField()
meta = {'max_documents': 1000, 'max_size': 2000000} meta = {'max_documents': 1000, 'max_size': 2000000}
.. defining-indexes_
Indexes Indexes
======= =======
@ -487,35 +485,6 @@ If a dictionary is passed then the following options are available:
Inheritance adds extra fields indices see: :ref:`document-inheritance`. Inheritance adds extra fields indices see: :ref:`document-inheritance`.
Global index default options
----------------------------
There are a few top level defaults for all indexes that can be set::
class Page(Document):
title = StringField()
rating = StringField()
meta = {
'index_options': {},
'index_background': True,
'index_drop_dups': True,
'index_cls': False
}
:attr:`index_options` (Optional)
Set any default index options - see the `full options list <http://docs.mongodb.org/manual/reference/method/db.collection.ensureIndex/#db.collection.ensureIndex>`_
:attr:`index_background` (Optional)
Set the default value for if an index should be indexed in the background
:attr:`index_drop_dups` (Optional)
Set the default value for if an index should drop duplicates
:attr:`index_cls` (Optional)
A way to turn off a specific index for _cls.
Compound Indexes and Indexing sub documents Compound Indexes and Indexing sub documents
------------------------------------------- -------------------------------------------
@ -589,11 +558,6 @@ documentation for more information. A common usecase might be session data::
] ]
} }
.. warning:: TTL indexes happen on the MongoDB server and not in the application
code, therefore no signals will be fired on document deletion.
If you need signals to be fired on deletion, then you must handle the
deletion of Documents in your application code.
Comparing Indexes Comparing Indexes
----------------- -----------------
@ -689,6 +653,7 @@ document.::
.. note:: From 0.8 onwards you must declare :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
@ -708,25 +673,3 @@ defining all possible field types.
If you use :class:`~mongoengine.Document` and the database contains data that If you use :class:`~mongoengine.Document` and the database contains data that
isn't defined then that data will be stored in the `document._data` dictionary. isn't defined then that data will be stored in the `document._data` dictionary.
Abstract classes
================
If you want to add some extra functionality to a group of Document classes but
you don't need or want the overhead of inheritance you can use the
:attr:`abstract` attribute of :attr:`-mongoengine.Document.meta`.
This won't turn on :ref:`document-inheritance` but will allow you to keep your
code DRY::
class BaseDocument(Document):
meta = {
'abstract': True,
}
def check_permissions(self):
...
class User(BaseDocument):
...
Now the User class will have access to the inherited `check_permissions` method
and won't store any of the extra `_cls` information.

View file

@ -17,8 +17,8 @@ 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 behavour you can call :class:`~mongoengine.QuerySet.no_cache` desired behavour you can call :class:`~mongoengine.QuerySet.no_cache` to
(version **0.8.3+**) to return a non-caching queryset. return a non-caching queryset.
Filtering queries Filtering queries
================= =================
@ -497,6 +497,7 @@ that you may use with these methods:
* ``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
* ``pop`` -- remove the last item from a list
* ``push`` -- append a value to a list * ``push`` -- append a value to a list
* ``push_all`` -- append several values to a list * ``push_all`` -- append several values to a list
* ``pop`` -- remove the first or last element of a list * ``pop`` -- remove the first or last element of a list

View file

@ -3,7 +3,7 @@ Upgrading
######### #########
0.8.2 to 0.8.3 0.8.2 to 0.8.2
************** **************
Minor change that may impact users: Minor change that may impact users:
@ -16,8 +16,8 @@ fields. Previously they were stored alphabetically.
********** **********
There have been numerous backwards breaking changes in 0.8. The reasons for There have been numerous backwards breaking changes in 0.8. The reasons for
these are to ensure that MongoEngine has sane defaults going forward and that it these are ensure that MongoEngine has sane defaults going forward and
performs the best it can out of the box. Where possible there have been performs the best it can out the box. Where possible there have been
FutureWarnings to help get you ready for the change, but that hasn't been FutureWarnings to help get you ready for the change, but that hasn't been
possible for the whole of the release. possible for the whole of the release.
@ -71,7 +71,7 @@ inherited classes like so: ::
Document Definition Document Definition
------------------- -------------------
The default for inheritance has changed - it is now off by default and The default for inheritance has changed - its now off by default and
:attr:`_cls` will not be stored automatically with the class. So if you extend :attr:`_cls` will not be stored automatically with the class. So if you extend
your :class:`~mongoengine.Document` or :class:`~mongoengine.EmbeddedDocuments` your :class:`~mongoengine.Document` or :class:`~mongoengine.EmbeddedDocuments`
you will need to declare :attr:`allow_inheritance` in the meta data like so: :: you will need to declare :attr:`allow_inheritance` in the meta data like so: ::
@ -81,7 +81,7 @@ you will need to declare :attr:`allow_inheritance` in the meta data like so: ::
meta = {'allow_inheritance': True} meta = {'allow_inheritance': True}
Previously, if you had data in the database that wasn't defined in the Document Previously, if you had data the database that wasn't defined in the Document
definition, it would set it as an attribute on the document. This is no longer definition, it would set it as an attribute on the document. This is no longer
the case and the data is set only in the ``document._data`` dictionary: :: the case and the data is set only in the ``document._data`` dictionary: ::
@ -102,8 +102,8 @@ the case and the data is set only in the ``document._data`` dictionary: ::
AttributeError: 'Animal' object has no attribute 'size' AttributeError: 'Animal' object has no attribute 'size'
The Document class has introduced a reserved function `clean()`, which will be The Document class has introduced a reserved function `clean()`, which will be
called before saving the document. If your document class happens to have a method called before saving the document. If your document class happen to have a method
with the same name, please try to rename it. with the same name, please try rename it.
def clean(self): def clean(self):
pass pass
@ -111,7 +111,7 @@ with the same name, please try to rename it.
ReferenceField ReferenceField
-------------- --------------
ReferenceFields now store ObjectIds by default - this is more efficient than ReferenceFields now store ObjectId's by default - this is more efficient than
DBRefs as we already know what Document types they reference:: DBRefs as we already know what Document types they reference::
# Old code # Old code
@ -157,7 +157,7 @@ UUIDFields now default to storing binary values::
class Animal(Document): class Animal(Document):
uuid = UUIDField(binary=False) uuid = UUIDField(binary=False)
To migrate all the uuids you need to touch each object and mark it as dirty To migrate all the uuid's you need to touch each object and mark it as dirty
eg:: eg::
# Doc definition # Doc definition
@ -175,7 +175,7 @@ eg::
DecimalField DecimalField
------------ ------------
DecimalFields now store floats - previously it was storing strings and that DecimalField now store floats - previous it was storing strings and that
made it impossible to do comparisons when querying correctly.:: made it impossible to do comparisons when querying correctly.::
# Old code # Old code
@ -186,7 +186,7 @@ made it impossible to do comparisons when querying correctly.::
class Person(Document): class Person(Document):
balance = DecimalField(force_string=True) balance = DecimalField(force_string=True)
To migrate all the DecimalFields you need to touch each object and mark it as dirty To migrate all the uuid's you need to touch each object and mark it as dirty
eg:: eg::
# Doc definition # Doc definition
@ -198,7 +198,7 @@ eg::
p._mark_as_changed('balance') p._mark_as_changed('balance')
p.save() p.save()
.. note:: DecimalFields have also been improved with the addition of precision .. note:: DecimalField's have also been improved with the addition of precision
and rounding. See :class:`~mongoengine.fields.DecimalField` for more information. and rounding. See :class:`~mongoengine.fields.DecimalField` for more information.
`An example test migration for DecimalFields is available on github `An example test migration for DecimalFields is available on github
@ -207,7 +207,7 @@ eg::
Cascading Saves Cascading Saves
--------------- ---------------
To improve performance document saves will no longer automatically cascade. To improve performance document saves will no longer automatically cascade.
Any changes to a Document's references will either have to be saved manually or Any changes to a Documents references will either have to be saved manually or
you will have to explicitly tell it to cascade on save:: you will have to explicitly tell it to cascade on save::
# At the class level: # At the class level:
@ -249,7 +249,7 @@ update your code like so: ::
# Update example a) assign queryset after a change: # Update example a) assign queryset after a change:
mammals = Animal.objects(type="mammal") mammals = Animal.objects(type="mammal")
carnivores = mammals.filter(order="Carnivora") # Reassign the new queryset so filter can be applied carnivores = mammals.filter(order="Carnivora") # Reassign the new queryset so fitler can be applied
[m for m in carnivores] # This will return all carnivores [m for m in carnivores] # This will return all carnivores
# Update example b) chain the queryset: # Update example b) chain the queryset:
@ -276,7 +276,7 @@ queryset you should upgrade to use count::
.only() now inline with .exclude() .only() now inline with .exclude()
---------------------------------- ----------------------------------
The behaviour of `.only()` was highly ambiguous, now it works in mirror fashion The behaviour of `.only()` was highly ambious, now it works in the mirror fashion
to `.exclude()`. Chaining `.only()` calls will increase the fields required:: to `.exclude()`. Chaining `.only()` calls will increase the fields required::
# Old code # Old code
@ -440,7 +440,7 @@ main areas of changed are: choices in fields, map_reduce and collection names.
Choice options: Choice options:
=============== ===============
Are now expected to be an iterable of tuples, with the first element in each Are now expected to be an iterable of tuples, with the first element in each
tuple being the actual value to be stored. The second element is the tuple being the actual value to be stored. The second element is the
human-readable name for the option. human-readable name for the option.
@ -462,8 +462,8 @@ such the following have been changed:
Default collection naming Default collection naming
========================= =========================
Previously it was just lowercase, it's now much more pythonic and readable as Previously it was just lowercase, its now much more pythonic and readable as
it's lowercase and underscores, previously :: its lowercase and underscores, previously ::
class MyAceDocument(Document): class MyAceDocument(Document):
pass pass
@ -530,5 +530,5 @@ Alternatively, you can rename your collections eg ::
mongodb 1.8 > 2.0 + mongodb 1.8 > 2.0 +
=================== ===================
It's been reported that indexes may need to be recreated to the newer version of indexes. Its been reported that indexes may need to be recreated to the newer version of indexes.
To do this drop indexes and call ``ensure_indexes`` on each model. To do this drop indexes and call ``ensure_indexes`` on each model.

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, 8, 4) VERSION = (0, 8, 3)
def get_version(): def get_version():

View file

@ -4,7 +4,7 @@ import numbers
from functools import partial from functools import partial
import pymongo import pymongo
from bson import json_util, ObjectId from bson import json_util
from bson.dbref import DBRef from bson.dbref import DBRef
from bson.son import SON from bson.son import SON
@ -321,9 +321,9 @@ class BaseDocument(object):
message = "ValidationError (%s:%s) " % (self._class_name, pk) message = "ValidationError (%s:%s) " % (self._class_name, pk)
raise ValidationError(message, errors=errors) raise ValidationError(message, errors=errors)
def to_json(self, *args, **kwargs): def to_json(self):
"""Converts a document to JSON""" """Converts a document to JSON"""
return json_util.dumps(self.to_mongo(), *args, **kwargs) return json_util.dumps(self.to_mongo())
@classmethod @classmethod
def from_json(cls, json_data): def from_json(cls, json_data):
@ -395,7 +395,6 @@ class BaseDocument(object):
""" """
EmbeddedDocument = _import_class("EmbeddedDocument") EmbeddedDocument = _import_class("EmbeddedDocument")
DynamicEmbeddedDocument = _import_class("DynamicEmbeddedDocument") DynamicEmbeddedDocument = _import_class("DynamicEmbeddedDocument")
ReferenceField = _import_class("ReferenceField")
_changed_fields = [] _changed_fields = []
_changed_fields += getattr(self, '_changed_fields', []) _changed_fields += getattr(self, '_changed_fields', [])
@ -406,36 +405,31 @@ class BaseDocument(object):
inspected.add(self.id) inspected.add(self.id)
for field_name in self._fields_ordered: for field_name in self._fields_ordered:
db_field_name = self._db_field_map.get(field_name, field_name) db_field_name = self._db_field_map.get(field_name, field_name)
key = '%s.' % db_field_name key = '%s.' % db_field_name
data = self._data.get(field_name, None) field = self._data.get(field_name, None)
field = self._fields.get(field_name) if hasattr(field, 'id'):
if field.id in inspected:
if hasattr(data, 'id'):
if data.id in inspected:
continue continue
inspected.add(data.id) inspected.add(field.id)
if isinstance(field, ReferenceField):
continue if (isinstance(field, (EmbeddedDocument, DynamicEmbeddedDocument))
elif (isinstance(data, (EmbeddedDocument, DynamicEmbeddedDocument))
and db_field_name not in _changed_fields): and db_field_name not in _changed_fields):
# Find all embedded fields that have been changed # Find all embedded fields that have been changed
changed = data._get_changed_fields(inspected) changed = field._get_changed_fields(inspected)
_changed_fields += ["%s%s" % (key, k) for k in changed if k] _changed_fields += ["%s%s" % (key, k) for k in changed if k]
elif (isinstance(data, (list, tuple, dict)) and elif (isinstance(field, (list, tuple, dict)) and
db_field_name not in _changed_fields): db_field_name not in _changed_fields):
# Loop list / dict fields as they contain documents # Loop list / dict fields as they contain documents
# Determine the iterator to use # Determine the iterator to use
if not hasattr(data, 'items'): if not hasattr(field, 'items'):
iterator = enumerate(data) iterator = enumerate(field)
else: else:
iterator = data.iteritems() iterator = field.iteritems()
for index, value in iterator: for index, value in iterator:
if not hasattr(value, '_get_changed_fields'): if not hasattr(value, '_get_changed_fields'):
continue continue
if (hasattr(field, 'field') and
isinstance(field.field, ReferenceField)):
continue
list_key = "%s%s." % (key, index) list_key = "%s%s." % (key, index)
changed = value._get_changed_fields(inspected) changed = value._get_changed_fields(inspected)
_changed_fields += ["%s%s" % (list_key, k) _changed_fields += ["%s%s" % (list_key, k)
@ -460,7 +454,7 @@ class BaseDocument(object):
d = doc d = doc
new_path = [] new_path = []
for p in parts: for p in parts:
if isinstance(d, (ObjectId, DBRef)): if isinstance(d, DBRef):
break break
elif isinstance(d, list) and p.isdigit(): elif isinstance(d, list) and p.isdigit():
d = d[int(p)] d = d[int(p)]
@ -629,10 +623,8 @@ class BaseDocument(object):
# Check to see if we need to include _cls # Check to see if we need to include _cls
allow_inheritance = cls._meta.get('allow_inheritance', allow_inheritance = cls._meta.get('allow_inheritance',
ALLOW_INHERITANCE) ALLOW_INHERITANCE)
include_cls = (allow_inheritance and not spec.get('sparse', False) and include_cls = allow_inheritance and not spec.get('sparse', False)
spec.get('cls', True))
if "cls" in spec:
spec.pop('cls')
for key in spec['fields']: for key in spec['fields']:
# If inherited spec continue # If inherited spec continue
if isinstance(key, (list, tuple)): if isinstance(key, (list, tuple)):
@ -762,7 +754,7 @@ class BaseDocument(object):
for field_name in parts: for field_name in parts:
# Handle ListField indexing: # Handle ListField indexing:
if field_name.isdigit() and hasattr(field, 'field'): if field_name.isdigit():
new_field = field.field new_field = field.field
fields.append(field_name) fields.append(field_name)
continue continue

View file

@ -186,6 +186,7 @@ class ComplexBaseField(BaseField):
""" """
field = None field = None
__dereference = False
def __get__(self, instance, owner): def __get__(self, instance, owner):
"""Descriptor to automatically dereference references. """Descriptor to automatically dereference references.
@ -200,11 +201,9 @@ class ComplexBaseField(BaseField):
(self.field is None or isinstance(self.field, (self.field is None or isinstance(self.field,
(GenericReferenceField, ReferenceField)))) (GenericReferenceField, ReferenceField))))
_dereference = _import_class("DeReference")()
self._auto_dereference = instance._fields[self.name]._auto_dereference self._auto_dereference = instance._fields[self.name]._auto_dereference
if instance._initialised and dereference: if not self.__dereference and instance._initialised and dereference:
instance._data[self.name] = _dereference( instance._data[self.name] = self._dereference(
instance._data.get(self.name), max_depth=1, instance=instance, instance._data.get(self.name), max_depth=1, instance=instance,
name=self.name name=self.name
) )
@ -223,7 +222,7 @@ class ComplexBaseField(BaseField):
if (self._auto_dereference and instance._initialised and if (self._auto_dereference and instance._initialised and
isinstance(value, (BaseList, BaseDict)) isinstance(value, (BaseList, BaseDict))
and not value._dereferenced): and not value._dereferenced):
value = _dereference( value = self._dereference(
value, max_depth=1, instance=instance, name=self.name value, max_depth=1, instance=instance, name=self.name
) )
value._dereferenced = True value._dereferenced = True
@ -383,6 +382,13 @@ class ComplexBaseField(BaseField):
owner_document = property(_get_owner_document, _set_owner_document) owner_document = property(_get_owner_document, _set_owner_document)
@property
def _dereference(self,):
if not self.__dereference:
DeReference = _import_class("DeReference")
self.__dereference = DeReference() # Cached
return self.__dereference
class ObjectIdField(BaseField): class ObjectIdField(BaseField):
"""A field wrapper around MongoDB's ObjectIds. """A field wrapper around MongoDB's ObjectIds.

View file

@ -23,9 +23,8 @@ def _import_class(cls_name):
field_classes = ('DictField', 'DynamicField', 'EmbeddedDocumentField', field_classes = ('DictField', 'DynamicField', 'EmbeddedDocumentField',
'FileField', 'GenericReferenceField', 'FileField', 'GenericReferenceField',
'GenericEmbeddedDocumentField', 'GeoPointField', 'GenericEmbeddedDocumentField', 'GeoPointField',
'PointField', 'LineStringField', 'ListField', 'PointField', 'LineStringField', 'PolygonField',
'PolygonField', 'ReferenceField', 'StringField', 'ReferenceField', 'StringField', 'ComplexBaseField')
'ComplexBaseField')
queryset_classes = ('OperationError',) queryset_classes = ('OperationError',)
deref_classes = ('DeReference',) deref_classes = ('DeReference',)

View file

@ -55,9 +55,12 @@ def register_connection(alias, name, host='localhost', port=27017,
# Handle uri style connections # Handle uri style connections
if "://" in host: if "://" in host:
uri_dict = uri_parser.parse_uri(host) uri_dict = uri_parser.parse_uri(host)
if uri_dict.get('database') is None:
raise ConnectionError("If using URI style connection include "\
"database name in string")
conn_settings.update({ conn_settings.update({
'host': host, 'host': host,
'name': uri_dict.get('database') or name, 'name': uri_dict.get('database'),
'username': uri_dict.get('username'), 'username': uri_dict.get('username'),
'password': uri_dict.get('password'), 'password': uri_dict.get('password'),
'read_preference': read_preference, 'read_preference': read_preference,

View file

@ -4,7 +4,7 @@ from base import (BaseDict, BaseList, 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
from document import Document, EmbeddedDocument from document import Document
class DeReference(object): class DeReference(object):
@ -33,8 +33,7 @@ class DeReference(object):
self.max_depth = max_depth self.max_depth = max_depth
doc_type = None doc_type = None
if instance and isinstance(instance, (Document, EmbeddedDocument, if instance and isinstance(instance, (Document, TopLevelDocumentMetaclass)):
TopLevelDocumentMetaclass)):
doc_type = instance._fields.get(name) doc_type = instance._fields.get(name)
if hasattr(doc_type, 'field'): if hasattr(doc_type, 'field'):
doc_type = doc_type.field doc_type = doc_type.field

View file

@ -6,29 +6,10 @@ from django.utils.importlib import import_module
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
__all__ = (
'get_user_document',
)
MONGOENGINE_USER_DOCUMENT = getattr( MONGOENGINE_USER_DOCUMENT = getattr(
settings, 'MONGOENGINE_USER_DOCUMENT', 'mongoengine.django.auth.User') settings, 'MONGOENGINE_USER_DOCUMENT', 'mongoengine.django.auth.User')
def get_user_document():
"""Get the user document class used for authentication.
This is the class defined in settings.MONGOENGINE_USER_DOCUMENT, which
defaults to `mongoengine.django.auth.User`.
"""
name = MONGOENGINE_USER_DOCUMENT
dot = name.rindex('.')
module = import_module(name[:dot])
return getattr(module, name[dot + 1:])
class MongoUserManager(UserManager): class MongoUserManager(UserManager):
"""A User manager wich allows the use of MongoEngine documents in Django. """A User manager wich allows the use of MongoEngine documents in Django.
@ -63,7 +44,7 @@ class MongoUserManager(UserManager):
def contribute_to_class(self, model, name): def contribute_to_class(self, model, name):
super(MongoUserManager, self).contribute_to_class(model, name) super(MongoUserManager, self).contribute_to_class(model, name)
self.dj_model = self.model self.dj_model = self.model
self.model = get_user_document() self.model = self._get_user_document()
self.dj_model.USERNAME_FIELD = self.model.USERNAME_FIELD self.dj_model.USERNAME_FIELD = self.model.USERNAME_FIELD
username = models.CharField(_('username'), max_length=30, unique=True) username = models.CharField(_('username'), max_length=30, unique=True)
@ -74,6 +55,16 @@ class MongoUserManager(UserManager):
field = models.CharField(_(name), max_length=30) field = models.CharField(_(name), max_length=30)
field.contribute_to_class(self.dj_model, name) field.contribute_to_class(self.dj_model, name)
def _get_user_document(self):
try:
name = MONGOENGINE_USER_DOCUMENT
dot = name.rindex('.')
module = import_module(name[:dot])
return getattr(module, name[dot + 1:])
except ImportError:
raise ImproperlyConfigured("Error importing %s, please check "
"settings.MONGOENGINE_USER_DOCUMENT"
% name)
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
try: try:
@ -94,14 +85,5 @@ class MongoUserManager(UserManager):
class MongoUser(models.Model): class MongoUser(models.Model):
""""Dummy user model for Django.
MongoUser is used to replace Django's UserManager with MongoUserManager.
The actual user document class is mongoengine.django.auth.User or any
other document class specified in MONGOENGINE_USER_DOCUMENT.
To get the user document class, use `get_user_document()`.
"""
objects = MongoUserManager() objects = MongoUserManager()

View file

@ -76,7 +76,7 @@ class GridFSStorage(Storage):
"""Find the documents in the store with the given name """Find the documents in the store with the given name
""" """
docs = self.document.objects docs = self.document.objects
doc = [d for d in docs if hasattr(getattr(d, self.field), 'name') and getattr(d, self.field).name == name] doc = [d for d in docs if getattr(d, self.field).name == name]
if doc: if doc:
return doc[0] return doc[0]
else: else:

View file

@ -400,7 +400,7 @@ class Document(BaseDocument):
""" """
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()
db = cls._get_db() db = cls._get_db
self._get_collection = lambda: collection self._get_collection = lambda: collection
self._get_db = lambda: db self._get_db = lambda: db
self._collection = collection self._collection = collection
@ -536,8 +536,6 @@ class Document(BaseDocument):
def ensure_indexes(cls): def ensure_indexes(cls):
"""Checks the document meta data and ensures all the indexes exist. """Checks the document meta data and ensures all the indexes exist.
Global defaults can be set in the meta - see :doc:`guide/defining-documents`
.. note:: You can disable automatic index creation by setting .. note:: You can disable automatic index creation by setting
`auto_create_index` to False in the documents meta data `auto_create_index` to False in the documents meta data
""" """

View file

@ -624,9 +624,7 @@ class DynamicField(BaseField):
cls = value.__class__ cls = value.__class__
val = value.to_mongo() val = value.to_mongo()
# If we its a document thats not inherited add _cls # If we its a document thats not inherited add _cls
if (isinstance(value, Document)): if (isinstance(value, (Document, EmbeddedDocument))):
val = {"_ref": value.to_dbref(), "_cls": cls.__name__}
if (isinstance(value, EmbeddedDocument)):
val['_cls'] = cls.__name__ val['_cls'] = cls.__name__
return val return val
@ -647,15 +645,6 @@ class DynamicField(BaseField):
value = [v for k, v in sorted(data.iteritems(), key=itemgetter(0))] value = [v for k, v in sorted(data.iteritems(), key=itemgetter(0))]
return value return value
def to_python(self, value):
if isinstance(value, dict) and '_cls' in value:
doc_cls = get_document(value['_cls'])
if '_ref' in value:
value = doc_cls._get_db().dereference(value['_ref'])
return doc_cls._from_son(value)
return super(DynamicField, self).to_python(value)
def lookup_member(self, member_name): def lookup_member(self, member_name):
return member_name return member_name
@ -780,10 +769,6 @@ class DictField(ComplexBaseField):
if op in match_operators and isinstance(value, basestring): if op in match_operators and isinstance(value, basestring):
return StringField().prepare_query_value(op, value) return StringField().prepare_query_value(op, value)
if hasattr(self.field, 'field'):
return self.field.prepare_query_value(op, value)
return super(DictField, self).prepare_query_value(op, value) return super(DictField, self).prepare_query_value(op, value)
@ -1098,10 +1083,6 @@ class GridFSProxy(object):
def __repr__(self): def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.grid_id) return '<%s: %s>' % (self.__class__.__name__, self.grid_id)
def __str__(self):
name = getattr(self.get(), 'filename', self.grid_id) if self.get() else '(no file)'
return '<%s: %s>' % (self.__class__.__name__, name)
def __eq__(self, other): def __eq__(self, other):
if isinstance(other, GridFSProxy): if isinstance(other, GridFSProxy):
return ((self.grid_id == other.grid_id) and return ((self.grid_id == other.grid_id) and

View file

@ -14,9 +14,8 @@ from pymongo.common import validate_read_preference
from mongoengine import signals from mongoengine import signals
from mongoengine.common import _import_class from mongoengine.common import _import_class
from mongoengine.base.common import get_document
from mongoengine.errors import (OperationError, NotUniqueError, from mongoengine.errors import (OperationError, NotUniqueError,
InvalidQueryError, LookUpError) InvalidQueryError)
from mongoengine.queryset import transform from mongoengine.queryset import transform
from mongoengine.queryset.field_list import QueryFieldList from mongoengine.queryset.field_list import QueryFieldList
@ -61,6 +60,7 @@ class BaseQuerySet(object):
self._none = False self._none = False
self._as_pymongo = False self._as_pymongo = False
self._as_pymongo_coerce = False self._as_pymongo_coerce = False
self._len = None
# 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
@ -331,9 +331,14 @@ class BaseQuerySet(object):
:meth:`skip` that has been applied to this cursor into account when :meth:`skip` that has been applied to this cursor into account when
getting the count getting the count
""" """
if self._limit == 0 and with_limit_and_skip: if self._limit == 0:
return 0 return 0
return self._cursor.count(with_limit_and_skip=with_limit_and_skip) if with_limit_and_skip and self._len is not None:
return self._len
count = self._cursor.count(with_limit_and_skip=with_limit_and_skip)
if with_limit_and_skip:
self._len = count
return count
def delete(self, write_concern=None, _from_doc_delete=False): def delete(self, write_concern=None, _from_doc_delete=False):
"""Delete the documents matched by the query. """Delete the documents matched by the query.
@ -822,9 +827,9 @@ class BaseQuerySet(object):
# JSON Helpers # JSON Helpers
def to_json(self, *args, **kwargs): def to_json(self):
"""Converts a queryset to JSON""" """Converts a queryset to JSON"""
return json_util.dumps(self.as_pymongo(), *args, **kwargs) return json_util.dumps(self.as_pymongo())
def from_json(self, json_data): def from_json(self, json_data):
"""Converts json data to unsaved objects""" """Converts json data to unsaved objects"""
@ -1334,33 +1339,13 @@ class BaseQuerySet(object):
return frequencies return frequencies
def _fields_to_dbfields(self, fields, subdoc=False): def _fields_to_dbfields(self, fields):
"""Translate fields paths to its db equivalents""" """Translate fields paths to its db equivalents"""
ret = [] ret = []
subclasses = []
document = self._document
if document._meta['allow_inheritance']:
subclasses = [get_document(x)
for x in document._subclasses][1:]
for field in fields: for field in fields:
try: field = ".".join(f.db_field for f in
field = ".".join(f.db_field for f in self._document._lookup_field(field.split('.')))
document._lookup_field(field.split('.'))) ret.append(field)
ret.append(field)
except LookUpError, err:
found = False
for subdoc in subclasses:
try:
subfield = ".".join(f.db_field for f in
subdoc._lookup_field(field.split('.')))
ret.append(subfield)
found = True
break
except LookUpError, e:
pass
if not found:
raise err
return ret return ret
def _get_order_by(self, keys): def _get_order_by(self, keys):

View file

@ -55,8 +55,7 @@ class QueryFieldList(object):
if self.always_include: if self.always_include:
if self.value is self.ONLY and self.fields: if self.value is self.ONLY and self.fields:
if sorted(self.slice.keys()) != sorted(self.fields): self.fields = self.fields.union(self.always_include)
self.fields = self.fields.union(self.always_include)
else: else:
self.fields -= self.always_include self.fields -= self.always_include

View file

@ -94,26 +94,8 @@ class QuerySet(BaseQuerySet):
except StopIteration: except StopIteration:
self._has_more = False self._has_more = False
def count(self, with_limit_and_skip=True):
"""Count the selected elements in the query.
:param with_limit_and_skip (optional): take any :meth:`limit` or
:meth:`skip` that has been applied to this cursor into account when
getting the count
"""
if with_limit_and_skip is False:
return super(QuerySet, self).count(with_limit_and_skip)
if self._len is None:
self._len = super(QuerySet, self).count(with_limit_and_skip)
return self._len
def no_cache(self): def no_cache(self):
"""Convert to a non_caching queryset """Convert to a non_caching queryset"""
.. versionadded:: 0.8.3 Convert to non caching queryset
"""
if self._result_cache is not None: if self._result_cache is not None:
raise OperationError("QuerySet already cached") raise OperationError("QuerySet already cached")
return self.clone_into(QuerySetNoCache(self._document, self._collection)) return self.clone_into(QuerySetNoCache(self._document, self._collection))
@ -123,10 +105,7 @@ class QuerySetNoCache(BaseQuerySet):
"""A non caching QuerySet""" """A non caching QuerySet"""
def cache(self): def cache(self):
"""Convert to a caching queryset """Convert to a caching queryset"""
.. versionadded:: 0.8.3 Convert to caching queryset
"""
return self.clone_into(QuerySet(self._document, self._collection)) return self.clone_into(QuerySet(self._document, self._collection))
def __repr__(self): def __repr__(self):

View file

@ -43,11 +43,11 @@ def query(_doc_cls=None, _field_operation=False, **query):
parts = [part for part in parts if not part.isdigit()] parts = [part for part in parts if not part.isdigit()]
# Check for an operator and transform to mongo-style if there is # Check for an operator and transform to mongo-style if there is
op = None op = None
if len(parts) > 1 and parts[-1] in MATCH_OPERATORS: if parts[-1] in MATCH_OPERATORS:
op = parts.pop() op = parts.pop()
negate = False negate = False
if len(parts) > 1 and parts[-1] == 'not': if parts[-1] == 'not':
parts.pop() parts.pop()
negate = True negate = True
@ -182,7 +182,6 @@ def update(_doc_cls=None, **update):
parts = [] parts = []
cleaned_fields = [] cleaned_fields = []
appended_sub_field = False
for field in fields: for field in fields:
append_field = True append_field = True
if isinstance(field, basestring): if isinstance(field, basestring):
@ -194,30 +193,21 @@ def update(_doc_cls=None, **update):
else: else:
parts.append(field.db_field) parts.append(field.db_field)
if append_field: if append_field:
appended_sub_field = False
cleaned_fields.append(field) cleaned_fields.append(field)
if hasattr(field, 'field'):
cleaned_fields.append(field.field)
appended_sub_field = True
# Convert value to proper value # Convert value to proper value
if appended_sub_field: field = cleaned_fields[-1]
field = cleaned_fields[-2]
else:
field = cleaned_fields[-1]
if op in (None, 'set', 'push', 'pull'): if op in (None, 'set', 'push', 'pull'):
if field.required or value is not None: if field.required or value is not None:
value = field.prepare_query_value(op, value) value = field.prepare_query_value(op, value)
elif op in ('pushAll', 'pullAll'): elif op in ('pushAll', 'pullAll'):
value = [field.prepare_query_value(op, v) for v in value] value = [field.prepare_query_value(op, v) for v in value]
elif op in ('addToSet', 'setOnInsert'): elif op == 'addToSet':
if isinstance(value, (list, tuple, set)): if isinstance(value, (list, tuple, set)):
value = [field.prepare_query_value(op, v) for v in value] value = [field.prepare_query_value(op, v) for v in value]
elif field.required or value is not None: elif field.required or value is not None:
value = field.prepare_query_value(op, value) value = field.prepare_query_value(op, value)
elif op == "unset":
value = 1
if match: if match:
match = '$' + match match = '$' + match
@ -231,24 +221,11 @@ def update(_doc_cls=None, **update):
if 'pull' in op and '.' in key: if 'pull' in op and '.' in key:
# Dot operators don't work on pull operations # Dot operators don't work on pull operations
# unless they point to a list field # it uses nested dict syntax
# Otherwise it uses nested dict syntax
if op == 'pullAll': if op == 'pullAll':
raise InvalidQueryError("pullAll operations only support " raise InvalidQueryError("pullAll operations only support "
"a single field depth") "a single field depth")
# Look for the last list field and use dot notation until there
field_classes = [c.__class__ for c in cleaned_fields]
field_classes.reverse()
ListField = _import_class('ListField')
if ListField in field_classes:
# Join all fields via dot notation to the last ListField
# Then process as normal
last_listField = len(cleaned_fields) - field_classes.index(ListField)
key = ".".join(parts[:last_listField])
parts = parts[last_listField:]
parts.insert(0, key)
parts.reverse() parts.reverse()
for key in parts: for key in parts:
value = {key: value} value = {key: value}

View file

@ -5,7 +5,7 @@
%define srcname mongoengine %define srcname mongoengine
Name: python-%{srcname} Name: python-%{srcname}
Version: 0.8.4 Version: 0.8.3
Release: 1%{?dist} Release: 1%{?dist}
Summary: A Python Document-Object Mapper for working with MongoDB Summary: A Python Document-Object Mapper for working with MongoDB

View file

@ -48,15 +48,17 @@ CLASSIFIERS = [
'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Python Modules',
] ]
extra_opts = {"packages": find_packages(exclude=["tests", "tests.*"])} extra_opts = {}
if sys.version_info[0] == 3: if sys.version_info[0] == 3:
extra_opts['use_2to3'] = True extra_opts['use_2to3'] = True
extra_opts['tests_require'] = ['nose', 'coverage', 'blinker', 'jinja2==2.6', 'django>=1.5.1'] extra_opts['tests_require'] = ['nose', 'coverage', 'blinker', 'jinja2==2.6', 'django>=1.5.1']
extra_opts['packages'] = find_packages(exclude=('tests',))
if "test" in sys.argv or "nosetests" in sys.argv: if "test" in sys.argv or "nosetests" in sys.argv:
extra_opts['packages'] = find_packages() extra_opts['packages'].append("tests")
extra_opts['package_data'] = {"tests": ["fields/mongoengine.png", "fields/mongodb_leaf.png"]} extra_opts['package_data'] = {"tests": ["fields/mongoengine.png", "fields/mongodb_leaf.png"]}
else: else:
extra_opts['tests_require'] = ['nose', 'coverage', 'blinker', 'django>=1.4.2', 'PIL', 'jinja2>=2.6', 'python-dateutil'] extra_opts['tests_require'] = ['nose', 'coverage', 'blinker', 'django>=1.4.2', 'PIL', 'jinja2==2.6', 'python-dateutil']
extra_opts['packages'] = find_packages(exclude=('tests',))
setup(name='mongoengine', setup(name='mongoengine',
version=VERSION, version=VERSION,

View file

@ -313,24 +313,29 @@ class DeltaTest(unittest.TestCase):
self.circular_reference_deltas_2(DynamicDocument, Document) self.circular_reference_deltas_2(DynamicDocument, Document)
self.circular_reference_deltas_2(DynamicDocument, DynamicDocument) self.circular_reference_deltas_2(DynamicDocument, DynamicDocument)
def circular_reference_deltas_2(self, DocClass1, DocClass2, dbref=True): def circular_reference_deltas_2(self, DocClass1, DocClass2):
class Person(DocClass1): class Person(DocClass1):
name = StringField() name = StringField()
owns = ListField(ReferenceField('Organization', dbref=dbref)) owns = ListField(ReferenceField('Organization'))
employer = ReferenceField('Organization', dbref=dbref) employer = ReferenceField('Organization')
class Organization(DocClass2): class Organization(DocClass2):
name = StringField() name = StringField()
owner = ReferenceField('Person', dbref=dbref) owner = ReferenceField('Person')
employees = ListField(ReferenceField('Person', dbref=dbref)) employees = ListField(ReferenceField('Person'))
Person.drop_collection() Person.drop_collection()
Organization.drop_collection() Organization.drop_collection()
person = Person(name="owner").save() person = Person(name="owner")
employee = Person(name="employee").save() person.save()
organization = Organization(name="company").save()
employee = Person(name="employee")
employee.save()
organization = Organization(name="company")
organization.save()
person.owns.append(organization) person.owns.append(organization)
organization.owner = person organization.owner = person
@ -350,8 +355,6 @@ class DeltaTest(unittest.TestCase):
self.assertEqual(o.owner, p) self.assertEqual(o.owner, p)
self.assertEqual(e.employer, o) self.assertEqual(e.employer, o)
return person, organization, employee
def test_delta_db_field(self): def test_delta_db_field(self):
self.delta_db_field(Document) self.delta_db_field(Document)
self.delta_db_field(DynamicDocument) self.delta_db_field(DynamicDocument)
@ -683,36 +686,6 @@ class DeltaTest(unittest.TestCase):
self.assertEqual(doc._get_changed_fields(), ['list_field']) self.assertEqual(doc._get_changed_fields(), ['list_field'])
self.assertEqual(doc._delta(), ({}, {'list_field': 1})) self.assertEqual(doc._delta(), ({}, {'list_field': 1}))
def test_delta_with_dbref_true(self):
person, organization, employee = self.circular_reference_deltas_2(Document, Document, True)
employee.name = 'test'
self.assertEqual(organization._get_changed_fields(), [])
updates, removals = organization._delta()
self.assertEqual({}, removals)
self.assertEqual({}, updates)
organization.employees.append(person)
updates, removals = organization._delta()
self.assertEqual({}, removals)
self.assertTrue('employees' in updates)
def test_delta_with_dbref_false(self):
person, organization, employee = self.circular_reference_deltas_2(Document, Document, False)
employee.name = 'test'
self.assertEqual(organization._get_changed_fields(), [])
updates, removals = organization._delta()
self.assertEqual({}, removals)
self.assertEqual({}, updates)
organization.employees.append(person)
updates, removals = organization._delta()
self.assertEqual({}, removals)
self.assertTrue('employees' in updates)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View file

@ -156,25 +156,6 @@ class IndexesTest(unittest.TestCase):
self.assertEqual([{'fields': [('_cls', 1), ('title', 1)]}], self.assertEqual([{'fields': [('_cls', 1), ('title', 1)]}],
A._meta['index_specs']) A._meta['index_specs'])
def test_index_no_cls(self):
"""Ensure index specs are inhertited correctly"""
class A(Document):
title = StringField()
meta = {
'indexes': [
{'fields': ('title',), 'cls': False},
],
'allow_inheritance': True,
'index_cls': False
}
self.assertEqual([('title', 1)], A._meta['index_specs'][0]['fields'])
A._get_collection().drop_indexes()
A.ensure_indexes()
info = A._get_collection().index_information()
self.assertEqual(len(info.keys()), 2)
def test_build_index_spec_is_not_destructive(self): def test_build_index_spec_is_not_destructive(self):
class MyDoc(Document): class MyDoc(Document):

View file

@ -31,10 +31,6 @@ class TestJson(unittest.TestCase):
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=(',', ':'))
expected_json = """{"embedded_field":{"string":"Hi"},"string":"Hi"}"""
self.assertEqual(doc_json, expected_json)
self.assertEqual(doc, Doc.from_json(doc.to_json())) self.assertEqual(doc, Doc.from_json(doc.to_json()))
def test_json_complex(self): def test_json_complex(self):

View file

@ -2506,46 +2506,5 @@ class FieldTest(unittest.TestCase):
self.assertTrue(tuple(x.items[0]) in tuples) self.assertTrue(tuple(x.items[0]) in tuples)
self.assertTrue(x.items[0] in tuples) self.assertTrue(x.items[0] in tuples)
def test_dynamic_fields_class(self):
class Doc2(Document):
field_1 = StringField(db_field='f')
class Doc(Document):
my_id = IntField(required=True, unique=True, primary_key=True)
embed_me = DynamicField(db_field='e')
field_x = StringField(db_field='x')
Doc.drop_collection()
Doc2.drop_collection()
doc2 = Doc2(field_1="hello")
doc = Doc(my_id=1, embed_me=doc2, field_x="x")
self.assertRaises(OperationError, doc.save)
doc2.save()
doc.save()
doc = Doc.objects.get()
self.assertEqual(doc.embed_me.field_1, "hello")
def test_dynamic_fields_embedded_class(self):
class Embed(EmbeddedDocument):
field_1 = StringField(db_field='f')
class Doc(Document):
my_id = IntField(required=True, unique=True, primary_key=True)
embed_me = DynamicField(db_field='e')
field_x = StringField(db_field='x')
Doc.drop_collection()
Doc(my_id=1, embed_me=Embed(field_1="hello"), field_x="x").save()
doc = Doc.objects.get()
self.assertEqual(doc.embed_me.field_1, "hello")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View file

@ -53,12 +53,11 @@ class FileTest(unittest.TestCase):
content_type = 'text/plain' content_type = 'text/plain'
putfile = PutFile() putfile = PutFile()
putfile.the_file.put(text, content_type=content_type, filename="hello") putfile.the_file.put(text, content_type=content_type)
putfile.save() putfile.save()
result = PutFile.objects.first() result = PutFile.objects.first()
self.assertTrue(putfile == result) self.assertTrue(putfile == result)
self.assertEqual("%s" % result.the_file, "<GridFSProxy: hello>")
self.assertEqual(result.the_file.read(), text) self.assertEqual(result.the_file.read(), text)
self.assertEqual(result.the_file.content_type, content_type) self.assertEqual(result.the_file.content_type, content_type)
result.the_file.delete() # Remove file from GridFS result.the_file.delete() # Remove file from GridFS

View file

@ -162,10 +162,6 @@ class OnlyExcludeAllTest(unittest.TestCase):
self.assertEqual(obj.name, person.name) self.assertEqual(obj.name, person.name)
self.assertEqual(obj.age, person.age) self.assertEqual(obj.age, person.age)
obj = self.Person.objects.only(*('id', 'name',)).get()
self.assertEqual(obj.name, person.name)
self.assertEqual(obj.age, None)
# Check polymorphism still works # Check polymorphism still works
class Employee(self.Person): class Employee(self.Person):
salary = IntField(db_field='wage') salary = IntField(db_field='wage')
@ -399,28 +395,5 @@ class OnlyExcludeAllTest(unittest.TestCase):
numbers = Numbers.objects.fields(embedded__n={"$slice": [-5, 10]}).get() numbers = Numbers.objects.fields(embedded__n={"$slice": [-5, 10]}).get()
self.assertEqual(numbers.embedded.n, [-5, -4, -3, -2, -1]) self.assertEqual(numbers.embedded.n, [-5, -4, -3, -2, -1])
def test_exclude_from_subclasses_docs(self):
class Base(Document):
username = StringField()
meta = {'allow_inheritance': True}
class Anon(Base):
anon = BooleanField()
class User(Base):
password = StringField()
wibble = StringField()
Base.drop_collection()
User(username="mongodb", password="secret").save()
user = Base.objects().exclude("password", "wibble").first()
self.assertEqual(user.password, None)
self.assertRaises(LookUpError, Base.objects.exclude, "made_up")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View file

@ -1497,6 +1497,9 @@ class QuerySetTest(unittest.TestCase):
def test_pull_nested(self): def test_pull_nested(self):
class User(Document):
name = StringField()
class Collaborator(EmbeddedDocument): class Collaborator(EmbeddedDocument):
user = StringField() user = StringField()
@ -1511,7 +1514,8 @@ class QuerySetTest(unittest.TestCase):
Site.drop_collection() Site.drop_collection()
c = Collaborator(user='Esteban') c = Collaborator(user='Esteban')
s = Site(name="test", collaborators=[c]).save() s = Site(name="test", collaborators=[c])
s.save()
Site.objects(id=s.id).update_one(pull__collaborators__user='Esteban') Site.objects(id=s.id).update_one(pull__collaborators__user='Esteban')
self.assertEqual(Site.objects.first().collaborators, []) self.assertEqual(Site.objects.first().collaborators, [])
@ -1521,71 +1525,6 @@ class QuerySetTest(unittest.TestCase):
self.assertRaises(InvalidQueryError, pull_all) self.assertRaises(InvalidQueryError, pull_all)
def test_pull_from_nested_embedded(self):
class User(EmbeddedDocument):
name = StringField()
def __unicode__(self):
return '%s' % self.name
class Collaborator(EmbeddedDocument):
helpful = ListField(EmbeddedDocumentField(User))
unhelpful = ListField(EmbeddedDocumentField(User))
class Site(Document):
name = StringField(max_length=75, unique=True, required=True)
collaborators = EmbeddedDocumentField(Collaborator)
Site.drop_collection()
c = User(name='Esteban')
f = User(name='Frank')
s = Site(name="test", collaborators=Collaborator(helpful=[c], unhelpful=[f])).save()
Site.objects(id=s.id).update_one(pull__collaborators__helpful=c)
self.assertEqual(Site.objects.first().collaborators['helpful'], [])
Site.objects(id=s.id).update_one(pull__collaborators__unhelpful={'name': 'Frank'})
self.assertEqual(Site.objects.first().collaborators['unhelpful'], [])
def pull_all():
Site.objects(id=s.id).update_one(pull_all__collaborators__helpful__name=['Ross'])
self.assertRaises(InvalidQueryError, pull_all)
def test_pull_from_nested_mapfield(self):
class Collaborator(EmbeddedDocument):
user = StringField()
def __unicode__(self):
return '%s' % self.user
class Site(Document):
name = StringField(max_length=75, unique=True, required=True)
collaborators = MapField(ListField(EmbeddedDocumentField(Collaborator)))
Site.drop_collection()
c = Collaborator(user='Esteban')
f = Collaborator(user='Frank')
s = Site(name="test", collaborators={'helpful':[c],'unhelpful':[f]})
s.save()
Site.objects(id=s.id).update_one(pull__collaborators__helpful__user='Esteban')
self.assertEqual(Site.objects.first().collaborators['helpful'], [])
Site.objects(id=s.id).update_one(pull__collaborators__unhelpful={'user':'Frank'})
self.assertEqual(Site.objects.first().collaborators['unhelpful'], [])
def pull_all():
Site.objects(id=s.id).update_one(pull_all__collaborators__helpful__user=['Ross'])
self.assertRaises(InvalidQueryError, pull_all)
def test_update_one_pop_generic_reference(self): def test_update_one_pop_generic_reference(self):
class BlogTag(Document): class BlogTag(Document):
@ -3360,13 +3299,6 @@ class QuerySetTest(unittest.TestCase):
Test.objects(test='foo').update_one(upsert=True, set__test='foo') Test.objects(test='foo').update_one(upsert=True, set__test='foo')
self.assertTrue('_cls' in Test._collection.find_one()) self.assertTrue('_cls' in Test._collection.find_one())
def test_update_upsert_looks_like_a_digit(self):
class MyDoc(DynamicDocument):
pass
MyDoc.drop_collection()
self.assertEqual(1, MyDoc.objects.update_one(upsert=True, inc__47=1))
self.assertEqual(MyDoc.objects.get()['47'], 1)
def test_read_preference(self): def test_read_preference(self):
class Bar(Document): class Bar(Document):
pass pass
@ -3395,7 +3327,7 @@ class QuerySetTest(unittest.TestCase):
Doc(string="Bye", embedded_field=Embedded(string="Bye")).save() Doc(string="Bye", embedded_field=Embedded(string="Bye")).save()
Doc().save() Doc().save()
json_data = Doc.objects.to_json(sort_keys=True, separators=(',', ':')) json_data = Doc.objects.to_json()
doc_objects = list(Doc.objects) doc_objects = list(Doc.objects)
self.assertEqual(doc_objects, Doc.objects.from_json(json_data)) self.assertEqual(doc_objects, Doc.objects.from_json(json_data))
@ -3551,27 +3483,6 @@ class QuerySetTest(unittest.TestCase):
people.count() # count is cached people.count() # count is cached
self.assertEqual(q, 1) self.assertEqual(q, 1)
def test_no_cached_queryset(self):
class Person(Document):
name = StringField()
Person.drop_collection()
for i in xrange(100):
Person(name="No: %s" % i).save()
with query_counter() as q:
self.assertEqual(q, 0)
people = Person.objects.no_cache()
[x for x in people]
self.assertEqual(q, 1)
list(people)
self.assertEqual(q, 2)
people.count()
self.assertEqual(q, 3)
def test_cache_not_cloned(self): def test_cache_not_cloned(self):
class User(Document): class User(Document):
@ -3752,23 +3663,6 @@ class QuerySetTest(unittest.TestCase):
'_cls': 'Animal.Cat' '_cls': 'Animal.Cat'
}) })
def test_can_have_field_same_name_as_query_operator(self):
class Size(Document):
name = StringField()
class Example(Document):
size = ReferenceField(Size)
Size.drop_collection()
Example.drop_collection()
instance_size = Size(name="Large").save()
Example(size=instance_size).save()
self.assertEqual(Example.objects(size=instance_size).count(), 1)
self.assertEqual(Example.objects(size__in=[instance_size]).count(), 1)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View file

@ -31,31 +31,6 @@ class TransformTest(unittest.TestCase):
self.assertEqual(transform.query(name__exists=True), self.assertEqual(transform.query(name__exists=True),
{'name': {'$exists': True}}) {'name': {'$exists': True}})
def test_transform_update(self):
class DicDoc(Document):
dictField = DictField()
class Doc(Document):
pass
DicDoc.drop_collection()
Doc.drop_collection()
doc = Doc().save()
dic_doc = DicDoc().save()
for k, v in (("set", "$set"), ("set_on_insert", "$setOnInsert"), ("push", "$push")):
update = transform.update(DicDoc, **{"%s__dictField__test" % k: doc})
self.assertTrue(isinstance(update[v]["dictField.test"], dict))
# Update special cases
update = transform.update(DicDoc, unset__dictField__test=doc)
self.assertEqual(update["$unset"]["dictField.test"], 1)
update = transform.update(DicDoc, pull__dictField__test=doc)
self.assertTrue(isinstance(update["$pull"]["dictField"]["test"], dict))
def test_query_field_name(self): def test_query_field_name(self):
"""Ensure that the correct field name is used when querying. """Ensure that the correct field name is used when querying.
""" """

View file

@ -59,32 +59,6 @@ class ConnectionTest(unittest.TestCase):
c.admin.system.users.remove({}) c.admin.system.users.remove({})
c.mongoenginetest.system.users.remove({}) c.mongoenginetest.system.users.remove({})
def test_connect_uri_without_db(self):
"""Ensure that the connect() method works properly with uri's
without database_name
"""
c = connect(db='mongoenginetest', alias='admin')
c.admin.system.users.remove({})
c.mongoenginetest.system.users.remove({})
c.admin.add_user("admin", "password")
c.admin.authenticate("admin", "password")
c.mongoenginetest.add_user("username", "password")
self.assertRaises(ConnectionError, connect, "testdb_uri_bad", host='mongodb://test:password@localhost')
connect("mongoenginetest", host='mongodb://localhost/')
conn = get_connection()
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
db = get_db()
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertEqual(db.name, 'mongoenginetest')
c.admin.system.users.remove({})
c.mongoenginetest.system.users.remove({})
def test_register_connection(self): def test_register_connection(self):
"""Ensure that connections with different aliases may be registered. """Ensure that connections with different aliases may be registered.
""" """

View file

@ -1171,30 +1171,6 @@ class FieldTest(unittest.TestCase):
self.assertEqual(2, len([brand for bg in brand_groups for brand in bg.brands])) self.assertEqual(2, len([brand for bg in brand_groups for brand in bg.brands]))
def test_dereferencing_embedded_listfield_referencefield(self):
class Tag(Document):
meta = {'collection': 'tags'}
name = StringField()
class Post(EmbeddedDocument):
body = StringField()
tags = ListField(ReferenceField("Tag", dbref=True))
class Page(Document):
meta = {'collection': 'pages'}
tags = ListField(ReferenceField("Tag", dbref=True))
posts = ListField(EmbeddedDocumentField(Post))
Tag.drop_collection()
Page.drop_collection()
tag = Tag(name='test').save()
post = Post(body='test body', tags=[tag])
Page(tags=[tag], posts=[post]).save()
page = Page.objects.first()
self.assertEqual(page.tags[0], page.posts[0].tags[0])
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View file

@ -21,16 +21,14 @@ settings.configure(
try: try:
from django.contrib.auth import authenticate, get_user_model from django.contrib.auth import authenticate, get_user_model
from mongoengine.django.auth import User from mongoengine.django.auth import User
from mongoengine.django.mongo_auth.models import ( from mongoengine.django.mongo_auth.models import MongoUser, MongoUserManager
MongoUser,
MongoUserManager,
get_user_document,
)
DJ15 = True DJ15 = True
except Exception: except Exception:
DJ15 = False DJ15 = False
from django.contrib.sessions.tests import SessionTestsMixin from django.contrib.sessions.tests import SessionTestsMixin
from mongoengine.django.sessions import SessionStore, MongoSession from mongoengine.django.sessions import SessionStore, MongoSession
from datetime import tzinfo, timedelta from datetime import tzinfo, timedelta
ZERO = timedelta(0) ZERO = timedelta(0)
@ -167,8 +165,6 @@ class QuerySetTest(unittest.TestCase):
class Note(Document): class Note(Document):
text = StringField() text = StringField()
Note.drop_collection()
for i in xrange(1, 101): for i in xrange(1, 101):
Note(name="Note: %s" % i).save() Note(name="Note: %s" % i).save()
@ -262,12 +258,9 @@ class MongoAuthTest(unittest.TestCase):
User.drop_collection() User.drop_collection()
super(MongoAuthTest, self).setUp() super(MongoAuthTest, self).setUp()
def test_get_user_model(self): def test_user_model(self):
self.assertEqual(get_user_model(), MongoUser) self.assertEqual(get_user_model(), MongoUser)
def test_get_user_document(self):
self.assertEqual(get_user_document(), User)
def test_user_manager(self): def test_user_manager(self):
manager = get_user_model()._default_manager manager = get_user_model()._default_manager
self.assertTrue(isinstance(manager, MongoUserManager)) self.assertTrue(isinstance(manager, MongoUserManager))