Compare commits

...

49 Commits
v0.7rc1 ... 0.7

Author SHA1 Message Date
Ross Lawley
31ec7907b5 Fixing py3 compat 2012-10-01 20:01:43 +00:00
Ross Lawley
12f3f8c694 Added chaining regression test (MongoEngine/mongoengine#135) 2012-10-01 16:27:59 +00:00
Ross Lawley
79098e997e Updated test 2012-10-01 16:20:48 +00:00
Ross Lawley
dc1849bad5 Unicode fix for repr (MongoEngine/mongoengine#133) 2012-10-01 16:15:25 +00:00
Ross Lawley
e2d826c412 Allow updates with match operators (MongoEngine/mongoengine#144) 2012-10-01 15:26:34 +00:00
Ross Lawley
e6d796832e Unicode fix reverted but can have custom validator
MongoEngine/mongoengine#136
2012-10-01 14:48:53 +00:00
Ross Lawley
6f0a6df4f6 Fix loop 2012-10-01 14:23:05 +00:00
Ross Lawley
7a877a00d5 Updated URLField
handle unicode and custom validator (MongoEngine/mongoengine#136)
2012-10-01 13:59:15 +00:00
Ross Lawley
e8604d100e Added Garry Polley to contributors list
hmarr/mongoengine#573
2012-10-01 10:20:28 +00:00
Ross Lawley
1647441ce8 Merge branch 'master' of github.com:hmarr/mongoengine 2012-10-01 10:17:01 +00:00
Ross Lawley
9f8d6b3a00 Merge pull request #573 from garrypolley/master
Allow Django  AuthenticationBackends to work with Django user

Thanks @garrypolley
2012-10-01 00:24:52 -07:00
Garry Polley
4b2ad25405 can now use AuthenticationBackends with permissions. 2012-09-27 10:21:05 -05:00
Ross Lawley
3ce163b1a0 Updates to the readme 2012-09-27 10:29:24 +00:00
Ross Lawley
7c1ee28f13 Added CONTRIBUTING.rst 2012-09-27 10:26:22 +00:00
Ross Lawley
2645e43da1 Merge branch 'master' into 0.7 2012-09-27 08:30:36 +00:00
Ross Lawley
59bfe551a3 Updated docs thanks @mitar 2012-09-27 08:29:49 +00:00
Ross Lawley
e2c78047b1 Moved injection of Exceptions to top level 2012-09-26 10:43:14 +00:00
Ross Lawley
6a4351e44f Fixed reload issue with ReferenceField where dbref=False (MongoEngine/mongoengine#138) 2012-09-24 18:49:29 +00:00
Ross Lawley
adb60ef1ac Improved import cache 2012-09-24 18:45:02 +00:00
Ross Lawley
3090adac04 Fixed objectId for DBRef 2012-09-24 11:37:54 +00:00
Ross Lawley
b9253d86cc Updated travis.yml 2012-09-19 18:53:31 +00:00
Ross Lawley
ab4d4e6230 Fix ReferenceField dbref = False 2012-09-18 21:37:45 +00:00
Ross Lawley
7cd38c56c6 Merge branch 'master' of github.com:MongoEngine/mongoengine into 0.7 2012-09-14 10:19:04 +00:00
Ross Lawley
864053615b Updated docs 2012-09-14 10:18:44 +00:00
Ross Lawley
db2366f112 Merge pull request #126 from mahmoudhossam/patch-1
Update docs/index.rst
2012-09-13 06:18:06 -07:00
Ross Lawley
4defc82192 Version Bump 2012-09-11 15:16:39 +00:00
Ross Lawley
5949970a95 Fixed index inheritance issues
firmed up testcases (MongoEngine/mongoengine#123) (MongoEngine/mongoengine#125)
2012-09-11 15:14:37 +00:00
Mahmoud Hossam
0ea4abda81 Update docs/index.rst
Correct link for the source.
2012-09-11 12:38:40 +03:00
Ross Lawley
5c6035d636 Updated upgrade docs for BinaryFields 2012-09-11 08:56:32 +00:00
Ross Lawley
a2183e3dcc Reverted EmbeddedDocuments meta handling.
You now can turn off inheritance (MongoEngine/mongoengine#119)
2012-09-07 13:23:46 +01:00
Ross Lawley
99637151b5 Updated version 2012-09-05 10:24:25 +01:00
Ross Lawley
a8e787c120 Update index spec generation so its not destructive (MongoEngine/mongoengine#113) 2012-09-04 14:39:19 +01:00
Ross Lawley
53339c7c72 Version bump 2012-09-04 08:21:44 +01:00
Ross Lawley
3534bf7d70 Fixed index spec inheritance (MongoEngine/mongoengine#111) 2012-09-04 08:20:19 +01:00
Ross Lawley
1cf3989664 Updated setup.py 2012-09-04 08:10:56 +01:00
Ross Lawley
fd296918da Fixing readme for pypi 2012-09-03 15:51:36 +01:00
Ross Lawley
8ad1f03dc5 Updated travis 2012-09-03 15:23:30 +01:00
Ross Lawley
fe7e17dbd5 Updated 0.7 branch 2012-09-03 15:21:52 +01:00
Ross Lawley
d582394a42 Merge branch 'master' into 0.7 2012-09-03 15:20:17 +01:00
Ross Lawley
02ef0df019 Updated travis.yml 2012-09-03 13:29:29 +01:00
Ross Lawley
0dfd6aa518 Updated travis.yml - now testing multiple pymongos 2012-09-03 13:22:08 +01:00
Ross Lawley
0b23bc9cf2 python 2.6.4 and lower cannot handle unicode keys passed to __init__ (MongoEngine/mongoengine#101) 2012-09-03 13:10:06 +01:00
Ross Lawley
f108c4288e Updated queryset.delete so you can use with skip / limit (MongoEngine/mongoengine#107) 2012-09-03 12:53:50 +01:00
Ross Lawley
9b9696aefd Updated index creation allows kwargs to be passed through refs (MongoEngine/mongoengine#104) 2012-09-03 12:29:58 +01:00
Ross Lawley
576e198ece Fixed Q object merge edge case (MongoEngine/mongoengine#109) 2012-09-03 11:33:30 +01:00
Ross Lawley
52f85aab18 Merge branch 'master' of https://github.com/dimonb/mongoengine
Conflicts:
	mongoengine/base.py
2012-09-03 11:00:41 +01:00
Dmitry Balabanov
ab60fd0490 sharded collection document reload testcase 2012-08-30 14:37:11 +04:00
Dmitry Balabanov
d79ae30f31 fix object reload with shard_key in meta 2012-08-30 14:20:53 +04:00
Dmitry Balabanov
f27debe7f9 Respect sharding key when delete object from collection 2012-08-30 12:40:44 +04:00
23 changed files with 612 additions and 131 deletions

View File

@@ -1,15 +1,29 @@
# http://travis-ci.org/#!/MongoEngine/mongoengine
language: python
services: mongodb
python:
- 2.5
- 2.6
- 2.7
- 3.1
- 3.2
env:
- PYMONGO=dev
- PYMONGO=2.3
- PYMONGO=2.2
install:
- if [[ $TRAVIS_PYTHON_VERSION == '2.'* ]]; then sudo apt-get install zlib1g zlib1g-dev; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.'* ]]; then sudo ln -s /usr/lib/i386-linux-gnu/libz.so /usr/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 [[ $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
- python setup.py install
script:
- python setup.py test
- python setup.py test
notifications:
irc: "irc.freenode.org#mongoengine"
branches:
only:
- master
- 0.7

View File

@@ -122,3 +122,5 @@ that much better:
* Sergey Nikitin
* psychogenic
* Stefan Wójcik
* dimonb
* Garry Polley

61
CONTRIBUTING.rst Normal file
View File

@@ -0,0 +1,61 @@
Contributing to MongoEngine
===========================
MongoEngine has a large `community
<https://raw.github.com/MongoEngine/mongoengine/master/AUTHORS>`_ and
contributions are always encouraged. Contributions can be as simple as
minor tweaks to the documentation. Please read these guidelines before
sending a pull request.
Bugfixes and New Features
-------------------------
Before starting to write code, look for existing `tickets
<https://github.com/MongoEngine/mongoengine/issues?state=open>`_ or `create one
<https://github.com/MongoEngine/mongoengine/issues>`_ for your specific
issue or feature request. That way you avoid working on something
that might not be of interest or that has already been addressed. If in doubt
post to the `user group <http://groups.google.com/group/mongoengine-users>`
Supported Interpreters
----------------------
PyMongo supports CPython 2.5 and newer. Language
features not supported by all interpreters can not be used.
Please also ensure that your code is properly converted by
`2to3 <http://docs.python.org/library/2to3.html>`_ for Python 3 support.
Style Guide
-----------
MongoEngine aims to follow `PEP8 <http://www.python.org/dev/peps/pep-0008/>`_
including 4 space indents and 79 character line limits.
Testing
-------
All tests are run on `Travis <http://travis-ci.org/MongoEngine/mongoengine>`_
and any pull requests are automatically tested by Travis. Any pull requests
without tests will take longer to be integrated and might be refused.
General Guidelines
------------------
- Avoid backward breaking changes if at all possible.
- Write inline documentation for new classes and methods.
- Write tests and make sure they pass (make sure you have a mongod
running on the default port, then execute ``python setup.py test``
from the cmd line to run the test suite).
- Add yourself to AUTHORS.rst :)
Documentation
-------------
To contribute to the `API documentation
<http://docs.mongoengine.org/en/latest/apireference.html>`_
just make your changes to the inline documentation of the appropriate
`source code <https://github.com/MongoEngine/mongoengine>`_ or `rst file
<https://github.com/MongoEngine/mongoengine/tree/master/docs>`_ in a
branch and submit a `pull request <https://help.github.com/articles/using-pull-requests>`_.
You might also use the github `Edit <https://github.com/blog/844-forking-with-the-edit-button>`_
button.

View File

@@ -63,11 +63,6 @@ Some simple examples of what MongoEngine code looks like::
... print 'Link:', post.url
... print
...
=== Using MongoEngine ===
See the tutorial
=== MongoEngine Docs ===
Link: hmarr.com/mongoengine
>>> len(BlogPost.objects)
2
@@ -85,7 +80,7 @@ Some simple examples of what MongoEngine code looks like::
Tests
=====
To run the test suite, ensure you are running a local instance of MongoDB on
the standard port, and run ``python setup.py test``.
the standard port, and run: ``python setup.py test``.
Community
=========
@@ -93,11 +88,8 @@ Community
<http://groups.google.com/group/mongoengine-users>`_
- `MongoEngine Developers mailing list
<http://groups.google.com/group/mongoengine-dev>`_
- `#mongoengine IRC channel <irc://irc.freenode.net/mongoengine>`_
- `#mongoengine IRC channel <http://webchat.freenode.net/?channels=mongoengine>`_
Contributing
============
The source is available on `GitHub <http://github.com/MongoEngine/mongoengine>`_ - to
contribute to the project, fork it on GitHub and send a pull request, all
contributions and suggestions are welcome!
We welcome contributions! see the`Contribution guidelines <https://github.com/MongoEngine/mongoengine/blob/master/CONTRIBUTING.rst>`_

View File

@@ -3,7 +3,40 @@ Changelog
=========
Changes in 0.7.X
================
- Unicode fix for repr (MongoEngine/mongoengine#133)
- Allow updates with match operators (MongoEngine/mongoengine#144)
- Updated URLField - now can have a override the regex (MongoEngine/mongoengine#136)
- Allow Django AuthenticationBackends to work with Django user (hmarr/mongoengine#573)
- Fixed reload issue with ReferenceField where dbref=False (MongoEngine/mongoengine#138)
Changes in 0.7.5
================
- ReferenceFields with dbref=False use ObjectId instead of strings (MongoEngine/mongoengine#134)
See ticket for upgrade notes (https://github.com/MongoEngine/mongoengine/issues/134)
Changes in 0.7.4
================
- Fixed index inheritance issues - firmed up testcases (MongoEngine/mongoengine#123) (MongoEngine/mongoengine#125)
Changes in 0.7.3
================
- Reverted EmbeddedDocuments meta handling - now can turn off inheritance (MongoEngine/mongoengine#119)
Changes in 0.7.2
================
- Update index spec generation so its not destructive (MongoEngine/mongoengine#113)
Changes in 0.7.1
=================
- Fixed index spec inheritance (MongoEngine/mongoengine#111)
Changes in 0.7.0
=================
- Updated queryset.delete so you can use with skip / limit (MongoEngine/mongoengine#107)
- Updated index creation allows kwargs to be passed through refs (MongoEngine/mongoengine#104)
- Fixed Q object merge edge case (MongoEngine/mongoengine#109)
- Fixed reloading on sharded documents (hmarr/mongoengine#569)
- Added NotUniqueError for duplicate keys (MongoEngine/mongoengine#62)
- Added custom collection / sequence naming for SequenceFields (MongoEngine/mongoengine#92)
- Fixed UnboundLocalError in composite index with pk field (MongoEngine/mongoengine#88)

View File

@@ -344,6 +344,10 @@ Its value can take any of the following constants:
their :file:`models.py` in the :const:`INSTALLED_APPS` tuple.
.. warning::
Signals are not triggered when doing cascading updates / deletes - if this
is required you must manually handle the update / delete.
Generic reference fields
''''''''''''''''''''''''
A second kind of reference field also exists,
@@ -465,7 +469,7 @@ If a dictionary is passed then the following options are available:
Whether the index should be sparse.
:attr:`unique` (Default: False)
Whether the index should be sparse.
Whether the index should be unique.
.. note ::

View File

@@ -50,4 +50,11 @@ Example usage::
signals.post_save.connect(Author.post_save, sender=Author)
ReferenceFields and signals
---------------------------
Currently `reverse_delete_rules` do not trigger signals on the other part of
the relationship. If this is required you must manually handled the
reverse deletion.
.. _blinker: http://pypi.python.org/pypi/blinker

View File

@@ -34,10 +34,10 @@ To get help with using MongoEngine, use the `MongoEngine Users mailing list
Contributing
------------
The source is available on `GitHub <http://github.com/hmarr/mongoengine>`_ and
The source is available on `GitHub <http://github.com/MongoEngine/mongoengine>`_ and
contributions are always encouraged. Contributions can be as simple as
minor tweaks to this documentation. To contribute, fork the project on
`GitHub <http://github.com/hmarr/mongoengine>`_ and send a
`GitHub <http://github.com/MongoEngine/mongoengine>`_ and send a
pull request.
Also, you can join the developers' `mailing list

View File

@@ -61,6 +61,13 @@ stored in rather than as string representations. Your code may need to be
updated to handle native types rather than strings keys for the results of
item frequency queries.
BinaryFields
------------
Binary fields have been updated so that they are native binary types. If you
previously were doing `str` comparisons with binary field values you will have
to update and wrap the value in a `str`.
0.5 to 0.6
==========

View File

@@ -12,7 +12,7 @@ from signals import *
__all__ = (document.__all__ + fields.__all__ + connection.__all__ +
queryset.__all__ + signals.__all__)
VERSION = (0, '7rc1')
VERSION = (0, 7, 5)
def get_version():

View File

@@ -11,7 +11,7 @@ from queryset import DoesNotExist, MultipleObjectsReturned
from queryset import DO_NOTHING
from mongoengine import signals
from mongoengine.python_support import (PY3, PY25, txt_type,
from mongoengine.python_support import (PY3, UNICODE_KWARGS, txt_type,
to_str_keys_recursive)
import pymongo
@@ -53,7 +53,7 @@ class ValidationError(AssertionError):
self.message = message
def __str__(self):
return self.message
return txt_type(self.message)
def __repr__(self):
return '%s(%s,)' % (self.__class__.__name__, self.message)
@@ -235,7 +235,8 @@ class BaseField(object):
pass
def _validate(self, value):
from mongoengine import Document, EmbeddedDocument
Document = _import_class('Document')
EmbeddedDocument = _import_class('EmbeddedDocument')
# check choices
if self.choices:
is_cls = isinstance(value, (Document, EmbeddedDocument))
@@ -283,7 +284,9 @@ class ComplexBaseField(BaseField):
if instance is None:
# Document class being used rather than a document object
return self
from fields import GenericReferenceField, ReferenceField
ReferenceField = _import_class('ReferenceField')
GenericReferenceField = _import_class('GenericReferenceField')
dereference = self.field is None or isinstance(self.field,
(GenericReferenceField, ReferenceField))
if not self._dereference and instance._initialised and dereference:
@@ -310,6 +313,7 @@ class ComplexBaseField(BaseField):
)
value._dereferenced = True
instance._data[self.name] = value
return value
def __set__(self, instance, value):
@@ -321,7 +325,7 @@ class ComplexBaseField(BaseField):
def to_python(self, value):
"""Convert a MongoDB-compatible type to a Python type.
"""
from mongoengine import Document
Document = _import_class('Document')
if isinstance(value, basestring):
return value
@@ -363,7 +367,7 @@ class ComplexBaseField(BaseField):
def to_mongo(self, value):
"""Convert a Python type to a MongoDB-compatible type.
"""
from mongoengine import Document
Document = _import_class("Document")
if isinstance(value, basestring):
return value
@@ -399,7 +403,7 @@ class ComplexBaseField(BaseField):
meta.get('allow_inheritance', ALLOW_INHERITANCE)
== False)
if allow_inheritance and not self.field:
from fields import GenericReferenceField
GenericReferenceField = _import_class("GenericReferenceField")
value_dict[k] = GenericReferenceField().to_mongo(v)
else:
collection = v._get_collection_name()
@@ -460,7 +464,7 @@ class ComplexBaseField(BaseField):
@property
def _dereference(self,):
if not self.__dereference:
from dereference import DeReference
DeReference = _import_class("DeReference")
self.__dereference = DeReference() # Cached
return self.__dereference
@@ -508,6 +512,10 @@ class DocumentMetaclass(type):
attrs['_is_document'] = attrs.get('_is_document', False)
# EmbeddedDocuments could have meta data for inheritance
if 'meta' in attrs:
attrs['_meta'] = attrs.pop('meta')
# Handle document Fields
# Merge all fields from subclasses
@@ -571,6 +579,24 @@ class DocumentMetaclass(type):
superclasses[base._class_name] = base
superclasses.update(base._superclasses)
if hasattr(base, '_meta'):
# Warn if allow_inheritance isn't set and prevent
# inheritance of classes where inheritance is set to False
allow_inheritance = base._meta.get('allow_inheritance',
ALLOW_INHERITANCE)
if (not getattr(base, '_is_base_cls', True)
and allow_inheritance is None):
warnings.warn(
"%s uses inheritance, the default for "
"allow_inheritance is changing to off by default. "
"Please add it to the document meta." % name,
FutureWarning
)
elif (allow_inheritance == False and
not base._meta.get('abstract')):
raise ValueError('Document %s may not be subclassed' %
base.__name__)
attrs['_class_name'] = '.'.join(reversed(class_name))
attrs['_superclasses'] = superclasses
@@ -609,17 +635,6 @@ class DocumentMetaclass(type):
"field name" % field.name)
raise InvalidDocumentError(msg)
# Merge in exceptions with parent hierarchy
exceptions_to_merge = (DoesNotExist, MultipleObjectsReturned)
module = attrs.get('__module__')
for exc in exceptions_to_merge:
name = exc.__name__
parents = tuple(getattr(base, name) for base in flattened_bases
if hasattr(base, name)) or (exc,)
# Create new exception and set to new_class
exception = type(name, parents, {'__module__': module})
setattr(new_class, name, exception)
# Add class to the _document_registry
_document_registry[new_class._class_name] = new_class
@@ -745,21 +760,6 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
if hasattr(base, 'meta'):
meta.merge(base.meta)
elif hasattr(base, '_meta'):
# Warn if allow_inheritance isn't set and prevent
# inheritance of classes where inheritance is set to False
allow_inheritance = base._meta.get('allow_inheritance',
ALLOW_INHERITANCE)
if not base._is_base_cls and allow_inheritance is None:
warnings.warn(
"%s uses inheritance, the default for "
"allow_inheritance is changing to off by default. "
"Please add it to the document meta." % name,
FutureWarning
)
elif (allow_inheritance == False and
not base._meta.get('abstract')):
raise ValueError('Document %s may not be subclassed' %
base.__name__)
meta.merge(base._meta)
# Set collection in the meta if its callable
@@ -825,6 +825,17 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
new_class._fields['id'] = ObjectIdField(db_field='_id')
new_class.id = new_class._fields['id']
# Merge in exceptions with parent hierarchy
exceptions_to_merge = (DoesNotExist, MultipleObjectsReturned)
module = attrs.get('__module__')
for exc in exceptions_to_merge:
name = exc.__name__
parents = tuple(getattr(base, name) for base in flattened_bases
if hasattr(base, name)) or (exc,)
# Create new exception and set to new_class
exception = type(name, parents, {'__module__': module})
setattr(new_class, name, exception)
return new_class
@classmethod
@@ -936,7 +947,7 @@ class BaseDocument(object):
field = None
if not hasattr(self, name) and not name.startswith('_'):
from fields import DynamicField
DynamicField = _import_class("DynamicField")
field = DynamicField(db_field=name)
field.name = name
self._dynamic_fields[name] = field
@@ -949,8 +960,10 @@ class BaseDocument(object):
self._data[name] = value
if hasattr(self, '_changed_fields'):
self._mark_as_changed(name)
if (self._is_document and not self._created and
name in self._meta.get('shard_key', tuple())):
name in self._meta.get('shard_key', tuple()) and
self._data.get(name) != value):
OperationError = _import_class('OperationError')
msg = "Shard Keys are immutable. Tried to update %s" % name
raise OperationError(msg)
@@ -1052,9 +1065,9 @@ class BaseDocument(object):
# class if unavailable
class_name = son.get('_cls', cls._class_name)
data = dict(("%s" % key, value) for key, value in son.items())
if PY25:
# PY25 cannot handle unicode keys passed to class constructor
# example: cls(**data)
if not UNICODE_KWARGS:
# python 2.6.4 and lower cannot handle unicode keys
# passed to class constructor example: cls(**data)
to_str_keys_recursive(data)
if '_types' in data:
@@ -1112,7 +1125,8 @@ class BaseDocument(object):
def _get_changed_fields(self, key='', inspected=None):
"""Returns a list of all fields that have explicitly been changed.
"""
from mongoengine import EmbeddedDocument, DynamicEmbeddedDocument
EmbeddedDocument = _import_class("EmbeddedDocument")
DynamicEmbeddedDocument = _import_class("DynamicEmbeddedDocument")
_changed_fields = []
_changed_fields += getattr(self, '_changed_fields', [])
@@ -1243,7 +1257,9 @@ class BaseDocument(object):
geo_indices = []
inspected.append(cls)
from fields import EmbeddedDocumentField, GeoPointField
EmbeddedDocumentField = _import_class("EmbeddedDocumentField")
GeoPointField = _import_class("GeoPointField")
for field in cls._fields.values():
if not isinstance(field, (EmbeddedDocumentField, GeoPointField)):
continue
@@ -1317,10 +1333,11 @@ class BaseDocument(object):
def __repr__(self):
try:
u = txt_type(self)
u = self.__str__()
except (UnicodeEncodeError, UnicodeDecodeError):
u = '[Bad Unicode data]'
return '<%s: %s>' % (self.__class__.__name__, u)
repr_type = type(u)
return repr_type('<%s: %s>' % (self.__class__.__name__, u))
def __str__(self):
if hasattr(self, '__unicode__'):
@@ -1328,7 +1345,7 @@ class BaseDocument(object):
return self.__unicode__()
else:
return unicode(self).encode('utf-8')
return '%s object' % self.__class__.__name__
return txt_type('%s object' % self.__class__.__name__)
def __eq__(self, other):
if isinstance(other, self.__class__) and hasattr(other, 'id'):
@@ -1477,14 +1494,30 @@ def _import_class(cls_name):
"""Cached mechanism for imports"""
if cls_name in _class_registry:
return _class_registry.get(cls_name)
if cls_name == 'Document':
from mongoengine.document import Document as cls
elif cls_name == 'EmbeddedDocument':
from mongoengine.document import EmbeddedDocument as cls
elif cls_name == 'DictField':
from mongoengine.fields import DictField as cls
elif cls_name == 'OperationError':
from queryset import OperationError as cls
_class_registry[cls_name] = cls
return cls
doc_classes = ['Document', 'DynamicEmbeddedDocument', 'EmbeddedDocument']
field_classes = ['DictField', 'DynamicField', 'EmbeddedDocumentField',
'GenericReferenceField', 'GeoPointField',
'ReferenceField']
queryset_classes = ['OperationError']
deref_classes = ['DeReference']
if cls_name in doc_classes:
from mongoengine import document as module
import_classes = doc_classes
elif cls_name in field_classes:
from mongoengine import fields as module
import_classes = field_classes
elif cls_name in queryset_classes:
from mongoengine import queryset as module
import_classes = queryset_classes
elif cls_name in deref_classes:
from mongoengine import dereference as module
import_classes = deref_classes
else:
raise ValueError('No import set for: ' % cls_name)
for cls in import_classes:
_class_registry[cls] = getattr(module, cls)
return _class_registry.get(cls_name)

View File

@@ -31,10 +31,10 @@ class DeReference(object):
items = [i for i in items]
self.max_depth = max_depth
doc_type = None
if instance and instance._fields:
doc_type = instance._fields[name]
doc_type = instance._fields.get(name)
if hasattr(doc_type, 'field'):
doc_type = doc_type.field
@@ -134,7 +134,7 @@ class DeReference(object):
elif doc_type is None:
doc = get_document(
''.join(x.capitalize()
for x in col.split('_')))._from_son(ref)
for x in col.split('_')))._from_son(ref)
else:
doc = doc_type._from_son(ref)
object_map[doc.id] = doc
@@ -166,7 +166,7 @@ class DeReference(object):
return self.object_map.get(items['_ref'].id, items)
elif '_types' in items and '_cls' in items:
doc = get_document(items['_cls'])._from_son(items)
doc._data = self._attach_objects(doc._data, depth, doc, name)
doc._data = self._attach_objects(doc._data, depth, doc, None)
return doc
if not hasattr(items, 'items'):

View File

@@ -3,6 +3,8 @@ import datetime
from mongoengine import *
from django.utils.encoding import smart_str
from django.contrib.auth.models import _user_get_all_permissions
from django.contrib.auth.models import _user_has_perm
from django.contrib.auth.models import AnonymousUser
from django.utils.translation import ugettext_lazy as _
@@ -104,6 +106,25 @@ class User(Document):
"""
return check_password(raw_password, self.password)
def get_all_permissions(self, obj=None):
return _user_get_all_permissions(self, obj)
def has_perm(self, perm, obj=None):
"""
Returns True if the user has the specified permission. This method
queries all available auth backends, but returns immediately if any
backend returns True. Thus, a user who has permission from a single
auth backend is assumed to have permission in general. If an object is
provided, permissions for this specific object are checked.
"""
# Active superusers have all permissions.
if self.is_active and self.is_superuser:
return True
# Otherwise we need to check the backends.
return _user_has_perm(self, perm, obj)
@classmethod
def create_user(cls, username, password, email=None):
"""Create (and save) a new user with the given username, password and

View File

@@ -25,6 +25,14 @@ class EmbeddedDocument(BaseDocument):
collection. :class:`~mongoengine.EmbeddedDocument`\ s should be used as
fields on :class:`~mongoengine.Document`\ s through the
:class:`~mongoengine.EmbeddedDocumentField` field type.
A :class:`~mongoengine.EmbeddedDocument` subclass may be itself subclassed,
to create a specialised version of the embedded document that will be
stored in the same collection. To facilitate this behaviour, `_cls` and
`_types` fields are added to documents (hidden though the MongoEngine
interface though). To disable this behaviour and remove the dependence on
the presence of `_cls` and `_types`, set :attr:`allow_inheritance` to
``False`` in the :attr:`meta` dictionary.
"""
# The __metaclass__ attribute is removed by 2to3 when running with Python3
@@ -295,6 +303,16 @@ class Document(BaseDocument):
ref.save(**kwargs)
ref._changed_fields = []
@property
def _object_key(self):
"""Dict to identify object in collection
"""
select_dict = {'pk': self.pk}
shard_key = self.__class__._meta.get('shard_key', tuple())
for k in shard_key:
select_dict[k] = getattr(self, k)
return select_dict
def update(self, **kwargs):
"""Performs an update on the :class:`~mongoengine.Document`
A convenience wrapper to :meth:`~mongoengine.QuerySet.update`.
@@ -306,11 +324,7 @@ class Document(BaseDocument):
raise OperationError('attempt to update a document not yet saved')
# Need to add shard key to query, or you get an error
select_dict = {'pk': self.pk}
shard_key = self.__class__._meta.get('shard_key', tuple())
for k in shard_key:
select_dict[k] = getattr(self, k)
return self.__class__.objects(**select_dict).update_one(**kwargs)
return self.__class__.objects(**self._object_key).update_one(**kwargs)
def delete(self, safe=False):
"""Delete the :class:`~mongoengine.Document` from the database. This
@@ -321,7 +335,7 @@ class Document(BaseDocument):
signals.pre_delete.send(self.__class__, document=self)
try:
self.__class__.objects(pk=self.pk).delete(safe=safe)
self.__class__.objects(**self._object_key).delete(safe=safe)
except pymongo.errors.OperationFailure, err:
message = u'Could not delete document (%s)' % err.message
raise OperationError(message)
@@ -347,7 +361,12 @@ class Document(BaseDocument):
id_field = self._meta['id_field']
obj = self.__class__.objects(
**{id_field: self[id_field]}
).first().select_related(max_depth=max_depth)
).limit(1).select_related(max_depth=max_depth)
if obj:
obj = obj[0]
else:
msg = "Reloaded document has been deleted"
raise OperationError(msg)
for field in self._fields:
setattr(self, field, self._reload(field, obj[field]))
if self._dynamic:

View File

@@ -1,10 +1,12 @@
import datetime
import decimal
import itertools
import re
import time
import urllib2
import urlparse
import uuid
import warnings
import itertools
from operator import itemgetter
import gridfs
@@ -101,25 +103,30 @@ class URLField(StringField):
.. versionadded:: 0.3
"""
URL_REGEX = re.compile(
r'^https?://'
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|'
r'localhost|'
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
r'(?::\d+)?'
r'(?:/?|[/?]\S+)$', re.IGNORECASE
)
_URL_REGEX = re.compile(
r'^(?:http|ftp)s?://' # http:// or https://
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' #domain...
r'localhost|' #localhost...
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
def __init__(self, verify_exists=False, **kwargs):
def __init__(self, verify_exists=False, url_regex=None, **kwargs):
self.verify_exists = verify_exists
self.url_regex = url_regex or self._URL_REGEX
super(URLField, self).__init__(**kwargs)
def validate(self, value):
if not URLField.URL_REGEX.match(value):
if not self.url_regex.match(value):
self.error('Invalid URL: %s' % value)
return
if self.verify_exists:
import urllib2
warnings.warn(
"The URLField verify_exists argument has intractable security "
"and performance issues. Accordingly, it has been deprecated.",
DeprecationWarning
)
try:
request = urllib2.Request(value)
urllib2.urlopen(request)
@@ -709,6 +716,10 @@ class ReferenceField(BaseField):
Bar.register_delete_rule(Foo, 'bar', NULLIFY)
.. note ::
`reverse_delete_rules` do not trigger pre / post delete signals to be
triggered.
.. versionchanged:: 0.5 added `reverse_delete_rule`
"""
@@ -766,7 +777,7 @@ class ReferenceField(BaseField):
def to_mongo(self, document):
if isinstance(document, DBRef):
if not self.dbref:
return "%s" % DBRef.id
return DBRef.id
return document
elif not self.dbref and isinstance(document, basestring):
return document
@@ -788,7 +799,7 @@ class ReferenceField(BaseField):
collection = self.document_type._get_collection_name()
return DBRef(collection, id_)
return "%s" % id_
return id_
def to_python(self, value):
"""Convert a MongoDB-compatible type to a Python type.

View File

@@ -4,6 +4,7 @@ import sys
PY3 = sys.version_info[0] == 3
PY25 = sys.version_info[:2] == (2, 5)
UNICODE_KWARGS = int(''.join([str(x) for x in sys.version_info[:3]])) > 264
if PY3:
import codecs

View File

@@ -218,7 +218,7 @@ class QNode(object):
def _combine(self, other, operation):
"""Combine this node with another node into a QCombination object.
"""
if other.empty:
if getattr(other, 'empty', True):
return self
if self.empty:
@@ -398,12 +398,13 @@ class QuerySet(object):
or a **-** to determine the index ordering
"""
index_spec = QuerySet._build_index_spec(self._document, key_or_list)
self._collection.ensure_index(
index_spec['fields'],
drop_dups=drop_dups,
background=background,
sparse=index_spec.get('sparse', False),
unique=index_spec.get('unique', False))
index_spec = index_spec.copy()
fields = index_spec.pop('fields')
index_spec['drop_dups'] = drop_dups
index_spec['background'] = background
index_spec.update(kwargs)
self._collection.ensure_index(fields, **index_spec)
return self
def __call__(self, q_obj=None, class_check=True, slave_okay=False, **query):
@@ -472,12 +473,14 @@ class QuerySet(object):
# Ensure document-defined indexes are created
if self._document._meta['index_specs']:
for spec in self._document._meta['index_specs']:
types_indexed = types_indexed or includes_types(spec['fields'])
index_spec = self._document._meta['index_specs']
for spec in index_spec:
spec = spec.copy()
fields = spec.pop('fields')
types_indexed = types_indexed or includes_types(fields)
opts = index_opts.copy()
opts['unique'] = spec.get('unique', False)
opts['sparse'] = spec.get('sparse', False)
self._collection.ensure_index(spec['fields'],
opts.update(spec)
self._collection.ensure_index(fields,
background=background, **opts)
# If _types is being used (for polymorphism), it needs an index,
@@ -498,8 +501,10 @@ class QuerySet(object):
"""
if isinstance(spec, basestring):
spec = {'fields': [spec]}
if isinstance(spec, (list, tuple)):
spec = {'fields': spec}
elif isinstance(spec, (list, tuple)):
spec = {'fields': list(spec)}
elif isinstance(spec, dict):
spec = dict(spec)
index_list = []
direction = None
@@ -510,6 +515,10 @@ class QuerySet(object):
use_types = allow_inheritance and not spec.get('sparse', False)
for key in spec['fields']:
# If inherited spec continue
if isinstance(key, (list, tuple)):
continue
# Get ASCENDING direction from +, DESCENDING from -, and GEO2D from *
direction = pymongo.ASCENDING
if key.startswith("-"):
@@ -1343,6 +1352,12 @@ class QuerySet(object):
"""
doc = self._document
# Handle deletes where skips or limits have been applied
if self._skip or self._limit:
for doc in self:
doc.delete()
return
delete_rules = doc._meta.get('delete_rules') or {}
# Check for DENY rules before actually deleting/nullifying any other
# references
@@ -1380,6 +1395,8 @@ class QuerySet(object):
"""
operators = ['set', 'unset', 'inc', 'dec', 'pop', 'push', 'push_all',
'pull', 'pull_all', 'add_to_set']
match_operators = ['ne', 'gt', 'gte', 'lt', 'lte', 'in', 'nin', 'mod',
'all', 'size', 'exists', 'not']
mongo_update = {}
for key, value in update.items():
@@ -1403,6 +1420,10 @@ class QuerySet(object):
elif op == 'add_to_set':
op = op.replace('_to_set', 'ToSet')
match = None
if parts[-1] in match_operators:
match = parts.pop()
if _doc_cls:
# Switch field names to proper names [set in Field(name='foo')]
fields = QuerySet._lookup_field(_doc_cls, parts)
@@ -1436,16 +1457,22 @@ class QuerySet(object):
elif field.required or value is not None:
value = field.prepare_query_value(op, value)
if match:
match = '$' + match
value = {match: value}
key = '.'.join(parts)
if not op:
raise InvalidQueryError("Updates must supply an operation eg: set__FIELD=value")
raise InvalidQueryError("Updates must supply an operation "
"eg: set__FIELD=value")
if 'pull' in op and '.' in key:
# Dot operators don't work on pull operations
# it uses nested dict syntax
if op == 'pullAll':
raise InvalidQueryError("pullAll operations only support a single field depth")
raise InvalidQueryError("pullAll operations only support "
"a single field depth")
parts.reverse()
for key in parts:

View File

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

View File

@@ -8,8 +8,8 @@ try:
except ImportError:
pass
DESCRIPTION = "A Python Document-Object Mapper for working with MongoDB"
DESCRIPTION = """MongoEngine is a Python Object-Document
Mapper for working with MongoDB."""
LONG_DESCRIPTION = None
try:
LONG_DESCRIPTION = open('README.rst').read()
@@ -68,6 +68,7 @@ setup(name='mongoengine',
maintainer="Ross Lawley",
maintainer_email="ross.lawley@{nospam}gmail.com",
url='http://mongoengine.org/',
download_url='https://github.com/MongoEngine/mongoengine/tarball/master',
license='MIT',
include_package_data=True,
description=DESCRIPTION,

View File

@@ -1,7 +1,7 @@
from __future__ import with_statement
import unittest
from bson import DBRef
from bson import DBRef, ObjectId
from mongoengine import *
from mongoengine.connection import get_db
@@ -84,6 +84,7 @@ class FieldTest(unittest.TestCase):
group = Group(members=User.objects)
group.save()
group.reload() # Confirm reload works
with query_counter() as q:
self.assertEqual(q, 0)
@@ -187,8 +188,8 @@ class FieldTest(unittest.TestCase):
self.assertEqual(group.members, [user])
raw_data = Group._get_collection().find_one()
self.assertTrue(isinstance(raw_data['author'], basestring))
self.assertTrue(isinstance(raw_data['members'][0], basestring))
self.assertTrue(isinstance(raw_data['author'], ObjectId))
self.assertTrue(isinstance(raw_data['members'][0], ObjectId))
def test_recursive_reference(self):
"""Ensure that ReferenceFields can reference their own documents.

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import with_statement
import bson
import os
@@ -16,7 +17,7 @@ from tests.fixtures import Base, Mixin, PickleEmbedded, PickleTest
from mongoengine import *
from mongoengine.base import NotRegistered, InvalidDocumentError
from mongoengine.queryset import InvalidQueryError
from mongoengine.connection import get_db
from mongoengine.connection import get_db, get_connection
TEST_IMAGE_PATH = os.path.join(os.path.dirname(__file__), 'mongoengine.png')
@@ -85,6 +86,22 @@ class DocumentTest(unittest.TestCase):
# Ensure Document isn't treated like an actual document
self.assertFalse(hasattr(Document, '_fields'))
def test_repr(self):
"""Ensure that unicode representation works
"""
class Article(Document):
title = StringField()
def __unicode__(self):
return self.title
Article.drop_collection()
Article(title=u'привет мир').save()
self.assertEqual('<Article: привет мир>', repr(Article.objects.first()))
self.assertEqual('[<Article: привет мир>]', repr(Article.objects.all()))
def test_collection_naming(self):
"""Ensure that a collection with a specified name may be used.
"""
@@ -338,7 +355,6 @@ class DocumentTest(unittest.TestCase):
meta = {'allow_inheritance': False}
self.assertRaises(ValueError, create_employee_class)
def test_allow_inheritance_abstract_document(self):
"""Ensure that abstract documents can set inheritance rules and that
_cls and _types will not be used.
@@ -366,6 +382,31 @@ class DocumentTest(unittest.TestCase):
Animal.drop_collection()
def test_allow_inheritance_embedded_document(self):
# Test the same for embedded documents
class Comment(EmbeddedDocument):
content = StringField()
meta = {'allow_inheritance': False}
def create_special_comment():
class SpecialComment(Comment):
pass
self.assertRaises(ValueError, create_special_comment)
comment = Comment(content='test')
self.assertFalse('_cls' in comment.to_mongo())
self.assertFalse('_types' in comment.to_mongo())
class Comment(EmbeddedDocument):
content = StringField()
meta = {'allow_inheritance': True}
comment = Comment(content='test')
self.assertTrue('_cls' in comment.to_mongo())
self.assertTrue('_types' in comment.to_mongo())
def test_document_inheritance(self):
"""Ensure mutliple inheritance of abstract docs works
"""
@@ -396,6 +437,9 @@ class DocumentTest(unittest.TestCase):
'indexes': ['name']
}
self.assertEqual(Animal._meta['index_specs'],
[{'fields': [('_types', 1), ('name', 1)]}])
Animal.drop_collection()
dog = Animal(name='dog')
@@ -417,6 +461,9 @@ class DocumentTest(unittest.TestCase):
'allow_inheritance': False,
'indexes': ['name']
}
self.assertEqual(Animal._meta['index_specs'],
[{'fields': [('name', 1)]}])
collection.update({}, {"$unset": {"_types": 1, "_cls": 1}}, multi=True)
# Confirm extra data is removed
@@ -634,6 +681,12 @@ class DocumentTest(unittest.TestCase):
'allow_inheritance': True
}
self.assertEqual(BlogPost._meta['index_specs'],
[{'fields': [('_types', 1), ('addDate', -1)]},
{'fields': [('tags', 1)]},
{'fields': [('_types', 1), ('category', 1),
('addDate', -1)]}])
BlogPost.drop_collection()
info = BlogPost.objects._collection.index_information()
@@ -657,6 +710,13 @@ class DocumentTest(unittest.TestCase):
title = StringField()
meta = {'indexes': ['title']}
self.assertEqual(ExtendedBlogPost._meta['index_specs'],
[{'fields': [('_types', 1), ('addDate', -1)]},
{'fields': [('tags', 1)]},
{'fields': [('_types', 1), ('category', 1),
('addDate', -1)]},
{'fields': [('_types', 1), ('title', 1)]}])
BlogPost.drop_collection()
list(ExtendedBlogPost.objects)
@@ -669,6 +729,46 @@ class DocumentTest(unittest.TestCase):
BlogPost.drop_collection()
def test_inherited_index(self):
"""Ensure index specs are inhertited correctly"""
class A(Document):
title = StringField()
meta = {
'indexes': [
{
'fields': ('title',),
},
],
'allow_inheritance': True,
}
class B(A):
description = StringField()
self.assertEqual(A._meta['index_specs'], B._meta['index_specs'])
self.assertEqual([{'fields': [('_types', 1), ('title', 1)]}],
A._meta['index_specs'])
def test_build_index_spec_is_not_destructive(self):
class MyDoc(Document):
keywords = StringField()
meta = {
'indexes': ['keywords'],
'allow_inheritance': False
}
self.assertEqual(MyDoc._meta['index_specs'],
[{'fields': [('keywords', 1)]}])
# Force index creation
MyDoc.objects._ensure_indexes()
self.assertEqual(MyDoc._meta['index_specs'],
[{'fields': [('keywords', 1)]}])
def test_db_field_load(self):
"""Ensure we load data correctly
"""
@@ -729,6 +829,9 @@ class DocumentTest(unittest.TestCase):
'allow_inheritance': False
}
self.assertEqual([{'fields': [('rank.title', 1)]}],
Person._meta['index_specs'])
Person.drop_collection()
# Indexes are lazy so use list() to perform query
@@ -747,6 +850,10 @@ class DocumentTest(unittest.TestCase):
'*location.point',
],
}
self.assertEqual([{'fields': [('location.point', '2d')]}],
Place._meta['index_specs'])
Place.drop_collection()
info = Place.objects._collection.index_information()
@@ -772,6 +879,10 @@ class DocumentTest(unittest.TestCase):
],
}
self.assertEqual([{'fields': [('addDate', -1)], 'unique': True,
'sparse': True, 'types': False}],
BlogPost._meta['index_specs'])
BlogPost.drop_collection()
info = BlogPost.objects._collection.index_information()
@@ -1102,6 +1213,32 @@ class DocumentTest(unittest.TestCase):
BlogPost.drop_collection()
def test_ttl_indexes(self):
class Log(Document):
created = DateTimeField(default=datetime.now)
meta = {
'indexes': [
{'fields': ['created'], 'expireAfterSeconds': 3600}
]
}
Log.drop_collection()
if pymongo.version_tuple[0] < 2 and pymongo.version_tuple[1] < 3:
raise SkipTest('pymongo needs to be 2.3 or higher for this test')
connection = get_connection()
version_array = connection.server_info()['versionArray']
if version_array[0] < 2 and version_array[1] < 2:
raise SkipTest('MongoDB needs to be 2.2 or higher for this test')
# Indexes are lazy so use list() to perform query
list(Log.objects)
info = Log.objects._collection.index_information()
self.assertEqual(3600,
info['_types_1_created_1']['expireAfterSeconds'])
def test_unique_and_indexes(self):
"""Ensure that 'unique' constraints aren't overridden by
meta.indexes.
@@ -1258,6 +1395,17 @@ class DocumentTest(unittest.TestCase):
self.assertEqual(person.name, "Mr Test User")
self.assertEqual(person.age, 21)
def test_reload_sharded(self):
class Animal(Document):
superphylum = StringField()
meta = {'shard_key': ('superphylum',)}
Animal.drop_collection()
doc = Animal(superphylum = 'Deuterostomia')
doc.save()
doc.reload()
Animal.drop_collection()
def test_reload_referencing(self):
"""Ensures reloading updates weakrefs correctly
"""

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import with_statement
import datetime
import os
@@ -7,7 +8,7 @@ import tempfile
from decimal import Decimal
from bson import Binary, DBRef
from bson import Binary, DBRef, ObjectId
import gridfs
from nose.plugins.skip import SkipTest
@@ -1104,7 +1105,7 @@ class FieldTest(unittest.TestCase):
p = Person.objects.get(name="Ross")
self.assertEqual(p.parent, p1)
def test_str_reference_fields(self):
def test_objectid_reference_fields(self):
class Person(Document):
name = StringField()
@@ -1117,7 +1118,7 @@ class FieldTest(unittest.TestCase):
col = Person._get_collection()
data = col.find_one({'name': 'Ross'})
self.assertEqual(data['parent'], "%s" % p1.pk)
self.assertEqual(data['parent'], p1.pk)
p = Person.objects.get(name="Ross")
self.assertEqual(p.parent, p1)

View File

@@ -230,6 +230,30 @@ class QuerySetTest(unittest.TestCase):
Blog.drop_collection()
def test_chaining(self):
class A(Document):
pass
class B(Document):
a = ReferenceField(A)
A.drop_collection()
B.drop_collection()
a1 = A().save()
a2 = A().save()
B(a=a1).save()
# Works
q1 = B.objects.filter(a__in=[a1, a2], a=a1)._query
# Doesn't work
q2 = B.objects.filter(a__in=[a1, a2])
q2 = q2.filter(a=a1)._query
self.assertEqual(q1, q2)
def test_update_write_options(self):
"""Test that passing write_options works"""
@@ -414,6 +438,30 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(post.comments[0].by, 'joe')
self.assertEqual(post.comments[0].votes.score, 4)
def test_updates_can_have_match_operators(self):
class Post(Document):
title = StringField(required=True)
tags = ListField(StringField())
comments = ListField(EmbeddedDocumentField("Comment"))
class Comment(EmbeddedDocument):
content = StringField()
name = StringField(max_length=120)
vote = IntField()
Post.drop_collection()
comm1 = Comment(content="very funny indeed", name="John S", vote=1)
comm2 = Comment(content="kind of funny", name="Mark P", vote=0)
Post(title='Fun with MongoEngine', tags=['mongodb', 'mongoengine'],
comments=[comm1, comm2]).save()
Post.objects().update_one(pull__comments__vote__lt=1)
self.assertEqual(1, len(Post.objects.first().comments))
def test_mapfield_update(self):
"""Ensure that the MapField can be updated."""
class Member(EmbeddedDocument):
@@ -1347,6 +1395,21 @@ class QuerySetTest(unittest.TestCase):
query = Foo.objects(Q(__raw__=q1) & Q(c=1))._query
self.assertEqual(query, {'$or': [{'a': 1}, {'b': 1}], 'c': 1})
def test_q_merge_queries_edge_case(self):
class User(Document):
email = EmailField(required=False)
name = StringField()
User.drop_collection()
pk = ObjectId()
User(email='example@example.com', pk=pk).save()
self.assertEqual(1, User.objects.filter(
Q(email='example@example.com') |
Q(name='John Doe')
).limit(2).filter(pk=pk).count())
def test_exec_js_query(self):
"""Ensure that queries are properly formed for use in exec_js.
"""
@@ -1486,7 +1549,8 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(1, BlogPost.objects.count())
def test_reverse_delete_rule_cascade_self_referencing(self):
"""Ensure self-referencing CASCADE deletes do not result in infinite loop
"""Ensure self-referencing CASCADE deletes do not result in infinite
loop
"""
class Category(Document):
name = StringField()
@@ -1592,6 +1656,40 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(post.authors, [me])
self.assertEqual(another.authors, [])
def test_delete_with_limits(self):
class Log(Document):
pass
Log.drop_collection()
for i in xrange(10):
Log().save()
Log.objects()[3:5].delete()
self.assertEqual(8, Log.objects.count())
def test_delete_with_limit_handles_delete_rules(self):
"""Ensure cascading deletion of referring documents from the database.
"""
class BlogPost(Document):
content = StringField()
author = ReferenceField(self.Person, reverse_delete_rule=CASCADE)
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()[:1].delete()
self.assertEqual(1, BlogPost.objects.count())
def test_update(self):
"""Ensure that atomic updates work properly.
"""
@@ -2519,30 +2617,30 @@ class QuerySetTest(unittest.TestCase):
"""Ensure that index_types will, when disabled, prevent _types
being added to all indices.
"""
class BlogPost(Document):
class BloggPost(Document):
date = DateTimeField()
meta = {'index_types': False,
'indexes': ['-date']}
# Indexes are lazy so use list() to perform query
list(BlogPost.objects)
info = BlogPost.objects._collection.index_information()
list(BloggPost.objects)
info = BloggPost.objects._collection.index_information()
info = [value['key'] for key, value in info.iteritems()]
self.assertTrue([('_types', 1)] not in info)
self.assertTrue([('date', -1)] in info)
BlogPost.drop_collection()
BloggPost.drop_collection()
class BlogPost(Document):
class BloggPost(Document):
title = StringField()
meta = {'allow_inheritance': False}
# _types is not used on objects where allow_inheritance is False
list(BlogPost.objects)
info = BlogPost.objects._collection.index_information()
list(BloggPost.objects)
info = BloggPost.objects._collection.index_information()
self.assertFalse([('_types', 1)] in info.values())
BlogPost.drop_collection()
BloggPost.drop_collection()
def test_types_index_with_pk(self):