Compare commits

..

58 Commits

Author SHA1 Message Date
Ross Lawley
6a02ac7e80 Fix django auth import (#531) 2013-12-04 13:32:56 +00:00
Ross Lawley
d1b86fdef5 Fix for import 2013-12-04 13:31:54 +00:00
Ross Lawley
57ac38ddca Version bump 0.8.5 2013-12-04 10:02:05 +00:00
Ross Lawley
7a73a92074 Update changelog 2013-12-04 10:01:16 +00:00
Ross Lawley
d1b30f4792 Fix auth to use get_user_document #527 2013-12-04 10:00:12 +00:00
Ross Lawley
16dcf78cab Ensure id is Hashable 2013-12-02 14:14:46 +00:00
Ross Lawley
d868cfdeb0 Fix multi level nested fields getting marked as changed (#523) 2013-11-29 16:24:32 +00:00
Ross Lawley
c074f4d925 Django 1.6 login fix (#522) 2013-11-29 13:19:52 +00:00
Ross Lawley
453024c58d Django 1.6 session fix (#509) 2013-11-29 13:11:56 +00:00
Ross Lawley
fe8340617a Merge pull request #509 from j0hnsmith/bson_serializer
django 1.6 session problem
2013-11-29 05:10:02 -08:00
Ross Lawley
b024dd913d EmbeddedDocument._instance is now set when settng the attribute (#506) 2013-11-29 13:09:11 +00:00
Ross Lawley
a2a698ab0e Fixed EmbeddedDocument with ReferenceField equality issue (#502) 2013-11-29 12:46:18 +00:00
Ross Lawley
bb56f92213 Updated changelog and AUTHORS (#499) 2013-11-29 12:07:33 +00:00
Ross Lawley
8dcd998945 Merge pull request #499 from drudim/master
Error in GenericReferenceField serialization was fixed
2013-11-29 04:06:04 -08:00
Ross Lawley
bcbbbe4046 Added test, updated AUTHORS and changelog (#498) 2013-11-29 12:04:30 +00:00
Ross Lawley
7200a8cb84 Merge pull request #498 from woakas/patch-1
Fixed bug for count method when _none is True
2013-11-29 04:01:45 -08:00
Ross Lawley
6925344807 Test updates 2013-11-29 12:01:14 +00:00
Ross Lawley
60ceeb0ddd Update change log and Authors (#496) 2013-11-29 11:55:03 +00:00
Ross Lawley
06caabf333 Import fix 2013-11-29 11:53:24 +00:00
Ross Lawley
954131bd51 Merge remote-tracking branch 'origin/pr/496' 2013-11-29 11:49:54 +00:00
Ross Lawley
855efe7fe8 Added user_permissions to Django User object (#491, #492) 2013-11-29 11:48:14 +00:00
Ross Lawley
d902a74ab0 Merge remote-tracking branch 'origin/pr/492' 2013-11-29 11:45:06 +00:00
Ross Lawley
499e11f730 Updated AUTHORS (#488) 2013-11-29 11:42:30 +00:00
Ross Lawley
6db59a9c31 Fix setting Geo Location fields (#488) 2013-11-29 11:41:54 +00:00
Ross Lawley
6465726008 Fix handling invalid dict field value (#485) 2013-11-29 10:33:36 +00:00
Ross Lawley
3a3b96e0be Merge remote-tracking branch 'origin/pr/485' 2013-11-29 10:32:49 +00:00
Ross Lawley
992c91dc0c Merge remote-tracking branch 'origin/pr/484'
Conflicts:
	AUTHORS
2013-11-29 10:17:15 +00:00
Ross Lawley
809473c15c Merge branch 'master' of github.com:MongoEngine/mongoengine 2013-11-29 10:14:54 +00:00
Ross Lawley
d79a5ec3d6 Updated Authors and changelog (#483) 2013-11-29 10:12:02 +00:00
Ross Lawley
237469ceaf Merge pull request #483 from grampajoe/connection-defaults
Use defaults when host and port are passed as None
2013-11-29 02:10:05 -08:00
Ross Lawley
c28d9135d9 Fixed distinct casting issue with ListField of EmbeddedDocuments (#470) 2013-11-29 09:48:53 +00:00
Ross Lawley
48a5679087 Added Django 1.6 to travis Refs: #480, #454 2013-11-29 09:13:37 +00:00
Ross Lawley
7c938712f2 Updates to docs ref: #454, #480 2013-11-29 09:11:08 +00:00
Ross Lawley
4df12bebc2 Merge branch 'master' of github.com:MongoEngine/mongoengine 2013-11-29 09:09:44 +00:00
Ross Lawley
dfe8987aaa Updated geo tests 2013-11-29 09:09:16 +00:00
Ross Lawley
02dbe401d8 Merge pull request #480 from mishudark/master
fix(session Issue #454): session serializer to json
2013-11-29 01:08:30 -08:00
j0hnsmith
c18f8c92e7 added BSONSerializer 2013-11-11 15:13:41 +00:00
Dmytro Popovych
11d4f6499a Python 2.5-2.6 compatibility fix 2013-10-20 20:21:57 +03:00
Dmytro Popovych
f2c25b4744 Error in GenericReferenceField serialization was fixed 2013-10-20 20:08:00 +03:00
Gustavo Andrés Angulo
27b846717f Fixed bug for count method when _none is True
If my queryset have elements example:

qs.all().count() => 10
q = qs.all().none()

the count in queryset "q" must be 0
q.count() => 0
2013-10-17 16:37:31 -05:00
Loic Raucy
9ed138f896 Fixed bug with numeric valuedict keys and BaseDocument._lookup_field(). 2013-10-17 16:32:47 +02:00
Loic Raucy
1978dc80eb Added regression test for bug with DictField and numeric keys.
When a DictField has numeric fields, BaseDocument._lookup_field doesn't
work correclty.
2013-10-17 16:26:19 +02:00
rfkrocktk
fc4b247f4f Patch for #491
Fix for issue #491, a bug in the permissions system.
2013-10-14 13:13:34 -07:00
Mitar
ebf7056f4a Test for testing invalid dict field value. 2013-10-06 02:55:02 -07:00
Dan Ward
eb975d7e13 Added entry to AUTHORS 2013-10-05 20:46:51 +01:00
Dan Ward
a2dd8cb6b9 Added app_label Meta attribute to MongoUser class 2013-10-05 20:39:41 +01:00
Joe Friedl
7c254c6136 Use defaults when host and port are passed as None 2013-10-04 14:18:36 -04:00
mishudark
c8a33b83f1 fix(session): session serializer to json 2013-09-29 20:25:26 +02:00
Ross Lawley
1145c72b01 Merge pull request #462 from bool-dev/master
Fixes #458, DecimalField now ignores incorrect values until validate is called just like FloatField
2013-09-27 08:41:32 -07:00
Ross Lawley
7fc45fb711 Merge pull request #468 from elasticsales/fix-dict-validation
Fix validation for a nested DictField
2013-09-27 08:33:02 -07:00
Ross Lawley
e146262c38 Merge pull request #467 from adamsc64/doc_foreignkey_push_pull_example
Expanded example of using ListFields for one-to-many relationships.
2013-09-27 08:31:59 -07:00
Ross Lawley
6f808bd06e Merge pull request #469 from jyapayne/master
Fixes AttributeError when using storage.exists() on a non-existing file.
2013-09-27 08:21:49 -07:00
Ross Lawley
0b6ab49325 Merge pull request #472 from swistakm/fix/docs-map-reduce-mongodb.org-link
fix broken external docs link on map_reduce
2013-09-27 07:58:49 -07:00
swistakm
66d9182e50 fix broken external docs link 2013-09-20 12:43:26 +02:00
Joey Payne
654cca82a9 Fixes AttributeError when using storage.exists() on a non-existing file. 2013-09-18 11:38:38 -06:00
Stefan Wojcik
89785da1c5 fix validation for a nested DictField 2013-09-16 23:50:13 -07:00
Christopher Adams
2f9964e46e Expanded example of using ListFields for one-to-many relationships.
- Added an example of how to use the atomic update operations to
  pull and push to a ListField containing ReferenceFields.
2013-09-17 00:02:38 -04:00
bool.dev
168ecd67b0 Fixes #458, DecimalField now ignores incorrect values until validate is called,
just like FloatField.
2013-09-06 22:05:31 +05:30
28 changed files with 453 additions and 84 deletions

View File

@@ -7,12 +7,16 @@ python:
- "3.2"
- "3.3"
env:
- PYMONGO=dev DJANGO=1.5.1
- PYMONGO=dev DJANGO=1.4.2
- PYMONGO=2.5 DJANGO=1.5.1
- PYMONGO=2.5 DJANGO=1.4.2
- PYMONGO=3.2 DJANGO=1.5.1
- PYMONGO=3.3 DJANGO=1.5.1
- PYMONGO=dev DJANGO=1.6
- PYMONGO=dev DJANGO=1.5.5
- PYMONGO=dev DJANGO=1.4.10
- PYMONGO=2.5 DJANGO=1.6
- PYMONGO=2.5 DJANGO=1.5.5
- PYMONGO=2.5 DJANGO=1.4.10
- PYMONGO=3.2 DJANGO=1.6
- PYMONGO=3.2 DJANGO=1.5.5
- PYMONGO=3.3 DJANGO=1.6
- PYMONGO=3.3 DJANGO=1.5.5
install:
- 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

12
AUTHORS
View File

@@ -134,7 +134,7 @@ that much better:
* Paul Swartz
* Sundar Raman
* Benoit Louy
* lraucy
* Loic Raucy (https://github.com/lraucy)
* hellysmile
* Jaepil Jeong
* Daniil Sharou
@@ -179,3 +179,13 @@ that much better:
* crazyzubr (https://github.com/crazyzubr)
* FrankSomething (https://github.com/FrankSomething)
* Alexandr Morozov (https://github.com/LK4D4)
* mishudark (https://github.com/mishudark)
* Joe Friedl (https://github.com/grampajoe)
* Daniel Ward (https://github.com/danielward)
* Aniket Deshpande (https://github.com/anicake)
* rfkrocktk (https://github.com/rfkrocktk)
* Gustavo Andrés Angulo (https://github.com/woakas)
* Dmytro Popovych (https://github.com/drudim)
* Tom (https://github.com/tomprimozic)
* j0hnsmith (https://github.com/j0hnsmith)
* Damien Churchill (https://github.com/damoxc)

View File

@@ -2,6 +2,28 @@
Changelog
=========
Changes in 0.8.6
================
- Fix django auth import (#531)
Changes in 0.8.5
================
- Fix multi level nested fields getting marked as changed (#523)
- Django 1.6 login fix (#522) (#527)
- Django 1.6 session fix (#509)
- EmbeddedDocument._instance is now set when settng the attribute (#506)
- Fixed EmbeddedDocument with ReferenceField equality issue (#502)
- Fixed GenericReferenceField serialization order (#499)
- Fixed count and none bug (#498)
- Fixed bug with .only() and DictField with digit keys (#496)
- Added user_permissions to Django User object (#491, #492)
- Fix updating Geo Location fields (#488)
- Fix handling invalid dict field value (#485)
- Added app_label to MongoUser (#484)
- Use defaults when host and port are passed as None (#483)
- Fixed distinct casting issue with ListField of EmbeddedDocuments (#470)
- Fixed Django 1.6 sessions (#454, #480)
Changes in 0.8.4
================
- Remove database name necessity in uri connection schema (#452)

View File

@@ -90,10 +90,15 @@ session backend, ensure that your settings module has
into your settings module::
SESSION_ENGINE = 'mongoengine.django.sessions'
SESSION_SERIALIZER = 'mongoengine.django.sessions.BSONSerializer'
Django provides session cookie, which expires after ```SESSION_COOKIE_AGE``` seconds, but doesn't delete cookie at sessions backend, so ``'mongoengine.django.sessions'`` supports `mongodb TTL
<http://docs.mongodb.org/manual/tutorial/expire-data/>`_.
.. note:: ``SESSION_SERIALIZER`` is only necessary in Django 1.6 as the default
serializer is based around JSON and doesn't know how to convert
``bson.objectid.ObjectId`` instances to strings.
.. versionadded:: 0.2.1
Storage

View File

@@ -290,6 +290,12 @@ instance of the object to the query::
# Find all pages that both Bob and John have authored
Page.objects(authors__all=[bob, john])
# Remove Bob from the authors for a page.
Page.objects(id='...').update_one(pull__authors=bob)
# Add John to the authors for a page.
Page.objects(id='...').update_one(push__authors=john)
Dealing with deletion of referred documents
'''''''''''''''''''''''''''''''''''''''''''

View File

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

View File

@@ -1,6 +1,7 @@
import copy
import operator
import numbers
from collections import Hashable
from functools import partial
import pymongo
@@ -375,20 +376,41 @@ class BaseDocument(object):
self._changed_fields.append(key)
def _clear_changed_fields(self):
"""Using get_changed_fields iterate and remove any fields that are
marked as changed"""
for changed in self._get_changed_fields():
parts = changed.split(".")
data = self
for part in parts:
if isinstance(data, list):
try:
data = data[int(part)]
except IndexError:
data = None
elif isinstance(data, dict):
data = data.get(part, None)
else:
data = getattr(data, part, None)
if hasattr(data, "_changed_fields"):
data._changed_fields = []
self._changed_fields = []
EmbeddedDocumentField = _import_class("EmbeddedDocumentField")
for field_name, field in self._fields.iteritems():
if (isinstance(field, ComplexBaseField) and
isinstance(field.field, EmbeddedDocumentField)):
field_value = getattr(self, field_name, None)
if field_value:
for idx in (field_value if isinstance(field_value, dict)
else xrange(len(field_value))):
field_value[idx]._clear_changed_fields()
elif isinstance(field, EmbeddedDocumentField):
field_value = getattr(self, field_name, None)
if field_value:
field_value._clear_changed_fields()
def _nestable_types_changed_fields(self, changed_fields, key, data, inspected):
# Loop list / dict fields as they contain documents
# Determine the iterator to use
if not hasattr(data, 'items'):
iterator = enumerate(data)
else:
iterator = data.iteritems()
for index, value in iterator:
list_key = "%s%s." % (key, index)
if hasattr(value, '_get_changed_fields'):
changed = value._get_changed_fields(inspected)
changed_fields += ["%s%s" % (list_key, k)
for k in changed if k]
elif isinstance(value, (list, tuple, dict)):
self._nestable_types_changed_fields(changed_fields, list_key, value, inspected)
def _get_changed_fields(self, inspected=None):
"""Returns a list of all fields that have explicitly been changed.
@@ -396,13 +418,12 @@ class BaseDocument(object):
EmbeddedDocument = _import_class("EmbeddedDocument")
DynamicEmbeddedDocument = _import_class("DynamicEmbeddedDocument")
ReferenceField = _import_class("ReferenceField")
_changed_fields = []
_changed_fields += getattr(self, '_changed_fields', [])
changed_fields = []
changed_fields += getattr(self, '_changed_fields', [])
inspected = inspected or set()
if hasattr(self, 'id'):
if hasattr(self, 'id') and isinstance(self.id, Hashable):
if self.id in inspected:
return _changed_fields
return changed_fields
inspected.add(self.id)
for field_name in self._fields_ordered:
@@ -418,29 +439,17 @@ class BaseDocument(object):
if isinstance(field, ReferenceField):
continue
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
changed = data._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
db_field_name not in _changed_fields):
# Loop list / dict fields as they contain documents
# Determine the iterator to use
if not hasattr(data, 'items'):
iterator = enumerate(data)
else:
iterator = data.iteritems()
for index, value in iterator:
if not hasattr(value, '_get_changed_fields'):
continue
if (hasattr(field, 'field') and
isinstance(field.field, ReferenceField)):
continue
list_key = "%s%s." % (key, index)
changed = value._get_changed_fields(inspected)
_changed_fields += ["%s%s" % (list_key, k)
for k in changed if k]
return _changed_fields
db_field_name not in changed_fields):
if (hasattr(field, 'field') and
isinstance(field.field, ReferenceField)):
continue
self._nestable_types_changed_fields(changed_fields, key, data, inspected)
return changed_fields
def _delta(self):
"""Returns the delta (set, unset) of the changes for a document.
@@ -755,6 +764,9 @@ class BaseDocument(object):
"""Lookup a field based on its attribute and return a list containing
the field's parents and the field.
"""
ListField = _import_class("ListField")
if not isinstance(parts, (list, tuple)):
parts = [parts]
fields = []
@@ -762,7 +774,7 @@ class BaseDocument(object):
for field_name in parts:
# Handle ListField indexing:
if field_name.isdigit() and hasattr(field, 'field'):
if field_name.isdigit() and isinstance(field, ListField):
new_field = field.field
fields.append(field_name)
continue

View File

@@ -89,12 +89,7 @@ class BaseField(object):
return self
# Get value from document instance if available
value = instance._data.get(self.name)
EmbeddedDocument = _import_class('EmbeddedDocument')
if isinstance(value, EmbeddedDocument) and value._instance is None:
value._instance = weakref.proxy(instance)
return value
return instance._data.get(self.name)
def __set__(self, instance, value):
"""Descriptor for assigning a value to a field in a document.
@@ -116,6 +111,10 @@ class BaseField(object):
# Values cant be compared eg: naive and tz datetimes
# So mark it as changed
instance._mark_as_changed(self.name)
EmbeddedDocument = _import_class('EmbeddedDocument')
if isinstance(value, EmbeddedDocument) and value._instance is None:
value._instance = weakref.proxy(instance)
instance._data[self.name] = value
def error(self, message="", errors=None, field_name=None):
@@ -203,7 +202,7 @@ class ComplexBaseField(BaseField):
_dereference = _import_class("DeReference")()
self._auto_dereference = instance._fields[self.name]._auto_dereference
if instance._initialised and dereference:
if instance._initialised and dereference and instance._data.get(self.name):
instance._data[self.name] = _dereference(
instance._data.get(self.name), max_depth=1, instance=instance,
name=self.name

View File

@@ -25,7 +25,7 @@ def _import_class(cls_name):
'GenericEmbeddedDocumentField', 'GeoPointField',
'PointField', 'LineStringField', 'ListField',
'PolygonField', 'ReferenceField', 'StringField',
'ComplexBaseField')
'ComplexBaseField', 'GeoJsonBaseField')
queryset_classes = ('OperationError',)
deref_classes = ('DeReference',)

View File

@@ -18,7 +18,7 @@ _connections = {}
_dbs = {}
def register_connection(alias, name, host='localhost', port=27017,
def register_connection(alias, name, host=None, port=None,
is_slave=False, read_preference=False, slaves=None,
username=None, password=None, **kwargs):
"""Add a connection.
@@ -43,8 +43,8 @@ def register_connection(alias, name, host='localhost', port=27017,
conn_settings = {
'name': name,
'host': host,
'port': port,
'host': host or 'localhost',
'port': port or 27017,
'is_slave': is_slave,
'slaves': slaves or [],
'username': username,
@@ -53,16 +53,15 @@ def register_connection(alias, name, host='localhost', port=27017,
}
# Handle uri style connections
if "://" in host:
uri_dict = uri_parser.parse_uri(host)
if "://" in conn_settings['host']:
uri_dict = uri_parser.parse_uri(conn_settings['host'])
conn_settings.update({
'host': host,
'name': uri_dict.get('database') or name,
'username': uri_dict.get('username'),
'password': uri_dict.get('password'),
'read_preference': read_preference,
})
if "replicaSet" in host:
if "replicaSet" in conn_settings['host']:
conn_settings['replicaSet'] = True
conn_settings.update(kwargs)

View File

@@ -8,6 +8,11 @@ from django.contrib import auth
from django.contrib.auth.models import AnonymousUser
from django.utils.translation import ugettext_lazy as _
from .utils import datetime_now
from .mongo_auth.models import get_user_document
REDIRECT_FIELD_NAME = 'next'
try:
from django.contrib.auth.hashers import check_password, make_password
except ImportError:
@@ -33,10 +38,6 @@ except ImportError:
hash = get_hexdigest(algo, salt, raw_password)
return '%s$%s$%s' % (algo, salt, hash)
from .utils import datetime_now
REDIRECT_FIELD_NAME = 'next'
class ContentType(Document):
name = StringField(max_length=100)
@@ -230,6 +231,9 @@ class User(Document):
date_joined = DateTimeField(default=datetime_now,
verbose_name=_('date joined'))
user_permissions = ListField(ReferenceField(Permission), verbose_name=_('user permissions'),
help_text=_('Permissions for the user.'))
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
@@ -380,7 +384,7 @@ class MongoEngineBackend(object):
supports_inactive_user = False
def authenticate(self, username=None, password=None):
user = User.objects(username=username).first()
user = get_user_document().objects(username=username).first()
if user:
if password and user.check_password(password):
backend = auth.get_backends()[0]
@@ -389,7 +393,7 @@ class MongoEngineBackend(object):
return None
def get_user(self, user_id):
return User.objects.with_id(user_id)
return get_user_document().objects.with_id(user_id)
def get_user(userid):

View File

@@ -1,4 +1,5 @@
from django.conf import settings
from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import UserManager
from django.core.exceptions import ImproperlyConfigured
from django.db import models
@@ -105,3 +106,10 @@ class MongoUser(models.Model):
"""
objects = MongoUserManager()
class Meta:
app_label = 'mongo_auth'
def set_password(self, password):
"""Doesn't do anything, but works around the issue with Django 1.6."""
make_password(password)

View File

@@ -1,3 +1,4 @@
from bson import json_util
from django.conf import settings
from django.contrib.sessions.backends.base import SessionBase, CreateError
from django.core.exceptions import SuspiciousOperation
@@ -55,6 +56,12 @@ class SessionStore(SessionBase):
"""A MongoEngine-based session store for Django.
"""
def _get_session(self, *args, **kwargs):
sess = super(SessionStore, self)._get_session(*args, **kwargs)
if sess.get('_auth_user_id', None):
sess['_auth_user_id'] = str(sess.get('_auth_user_id'))
return sess
def load(self):
try:
s = MongoSession.objects(session_key=self.session_key,
@@ -103,3 +110,15 @@ class SessionStore(SessionBase):
return
session_key = self.session_key
MongoSession.objects(session_key=session_key).delete()
class BSONSerializer(object):
"""
Serializer that can handle BSON types (eg ObjectId).
"""
def dumps(self, obj):
return json_util.dumps(obj, separators=(',', ':')).encode('ascii')
def loads(self, data):
return json_util.loads(data.decode('ascii'))

View File

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

View File

@@ -66,7 +66,7 @@ class EmbeddedDocument(BaseDocument):
def __eq__(self, other):
if isinstance(other, self.__class__):
return self._data == other._data
return self.to_mongo() == other.to_mongo()
return False
def __ne__(self, other):

View File

@@ -42,7 +42,8 @@ __all__ = ['StringField', 'URLField', 'EmailField', 'IntField', 'LongField',
'GenericReferenceField', 'BinaryField', 'GridFSError',
'GridFSProxy', 'FileField', 'ImageGridFsProxy',
'ImproperlyConfigured', 'ImageField', 'GeoPointField', 'PointField',
'LineStringField', 'PolygonField', 'SequenceField', 'UUIDField']
'LineStringField', 'PolygonField', 'SequenceField', 'UUIDField',
'GeoJsonBaseField']
RECURSIVE_REFERENCE_CONSTANT = 'self'
@@ -304,7 +305,10 @@ class DecimalField(BaseField):
return value
# Convert to string for python 2.6 before casting to Decimal
value = decimal.Decimal("%s" % value)
try:
value = decimal.Decimal("%s" % value)
except decimal.InvalidOperation:
return value
return value.quantize(self.precision, rounding=self.rounding)
def to_mongo(self, value):
@@ -735,6 +739,21 @@ class SortedListField(ListField):
reverse=self._order_reverse)
return sorted(value, reverse=self._order_reverse)
def key_not_string(d):
""" Helper function to recursively determine if any key in a dictionary is
not a string.
"""
for k, v in d.items():
if not isinstance(k, basestring) or (isinstance(v, dict) and key_not_string(v)):
return True
def key_has_dot_or_dollar(d):
""" Helper function to recursively determine if any key in a dictionary
contains a dot or a dollar sign.
"""
for k, v in d.items():
if ('.' in k or '$' in k) or (isinstance(v, dict) and key_has_dot_or_dollar(v)):
return True
class DictField(ComplexBaseField):
"""A dictionary field that wraps a standard Python dictionary. This is
@@ -761,11 +780,11 @@ class DictField(ComplexBaseField):
if not isinstance(value, dict):
self.error('Only dictionaries may be used in a DictField')
if any(k for k in value.keys() if not isinstance(k, basestring)):
if key_not_string(value):
msg = ("Invalid dictionary key - documents must "
"have only string keys")
self.error(msg)
if any(('.' in k or '$' in k) for k in value.keys()):
if key_has_dot_or_dollar(value):
self.error('Invalid dictionary key name - keys may not contain "."'
' or "$" characters')
super(DictField, self).validate(value)
@@ -1004,7 +1023,10 @@ class GenericReferenceField(BaseField):
id_ = id_field.to_mongo(id_)
collection = document._get_collection_name()
ref = DBRef(collection, id_)
return {'_cls': document._class_name, '_ref': ref}
return SON((
('_cls', document._class_name),
('_ref', ref)
))
def prepare_query_value(self, op, value):
if value is None:

View File

@@ -331,7 +331,7 @@ class BaseQuerySet(object):
:meth:`skip` that has been applied to this cursor into account when
getting the count
"""
if self._limit == 0 and with_limit_and_skip:
if self._limit == 0 and with_limit_and_skip or self._none:
return 0
return self._cursor.count(with_limit_and_skip=with_limit_and_skip)
@@ -621,8 +621,15 @@ class BaseQuerySet(object):
try:
field = self._fields_to_dbfields([field]).pop()
finally:
return self._dereference(queryset._cursor.distinct(field), 1,
name=field, instance=self._document)
distinct = self._dereference(queryset._cursor.distinct(field), 1,
name=field, instance=self._document)
# We may need to cast to the correct type eg. ListField(EmbeddedDocumentField)
doc_field = getattr(self._document._fields.get(field), "field", None)
instance = getattr(doc_field, "document_type", False)
if instance:
distinct = [instance(**doc) for doc in distinct]
return distinct
def only(self, *fields):
"""Load only a subset of this document's fields. ::
@@ -850,7 +857,7 @@ class BaseQuerySet(object):
:param output: output collection name, if set to 'inline' will try to
use :class:`~pymongo.collection.Collection.inline_map_reduce`
This can also be a dictionary containing output options
see: http://docs.mongodb.org/manual/reference/commands/#mapReduce
see: http://docs.mongodb.org/manual/reference/command/mapReduce/#dbcmd.mapReduce
:param finalize_f: finalize function, an optional function that
performs any post-reduction processing.
:param scope: values to insert into map/reduce global scope. Optional.
@@ -1491,4 +1498,4 @@ class BaseQuerySet(object):
msg = ("Doc.objects()._ensure_indexes() is deprecated. "
"Use Doc.ensure_indexes() instead.")
warnings.warn(msg, DeprecationWarning)
self._document.__class__.ensure_indexes()
self._document.__class__.ensure_indexes()

View File

@@ -206,6 +206,10 @@ def update(_doc_cls=None, **update):
else:
field = cleaned_fields[-1]
GeoJsonBaseField = _import_class("GeoJsonBaseField")
if isinstance(field, GeoJsonBaseField):
value = field.to_mongo(value)
if op in (None, 'set', 'push', 'pull'):
if field.required or value is not None:
value = field.prepare_query_value(op, value)

View File

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

View File

@@ -713,6 +713,27 @@ class DeltaTest(unittest.TestCase):
self.assertEqual({}, removals)
self.assertTrue('employees' in updates)
def test_nested_nested_fields_mark_as_changed(self):
class EmbeddedDoc(EmbeddedDocument):
name = StringField()
class MyDoc(Document):
subs = MapField(MapField(EmbeddedDocumentField(EmbeddedDoc)))
name = StringField()
MyDoc.drop_collection()
mydoc = MyDoc(name='testcase1', subs={'a': {'b': EmbeddedDoc(name='foo')}}).save()
mydoc = MyDoc.objects.first()
subdoc = mydoc.subs['a']['b']
subdoc.name = 'bar'
self.assertEqual(["name"], subdoc._get_changed_fields())
self.assertEqual(["subs.a.b.name"], mydoc._get_changed_fields())
mydoc._clear_changed_fields()
self.assertEqual([], mydoc._get_changed_fields())
if __name__ == '__main__':
unittest.main()

View File

@@ -490,6 +490,26 @@ class InstanceTest(unittest.TestCase):
doc = Doc.objects.get()
self.assertEqual(doc, doc.embedded_field[0]._instance)
def test_instance_is_set_on_setattr(self):
class Email(EmbeddedDocument):
email = EmailField()
def clean(self):
print "instance:"
print self._instance
class Account(Document):
email = EmbeddedDocumentField(Email)
Account.drop_collection()
acc = Account()
acc.email = Email(email='test@example.com')
self.assertTrue(hasattr(acc._data["email"], "_instance"))
acc.save()
acc1 = Account.objects.first()
self.assertTrue(hasattr(acc1._data["email"], "_instance"))
def test_document_clean(self):
class TestDocument(Document):
status = StringField()
@@ -2392,6 +2412,24 @@ class InstanceTest(unittest.TestCase):
system = System.objects.first()
self.assertEqual("UNDEFINED", system.nodes["node"].parameters["param"].macros["test"].value)
def test_embedded_document_equality(self):
class Test(Document):
field = StringField(required=True)
class Embedded(EmbeddedDocument):
ref = ReferenceField(Test)
Test.drop_collection()
test = Test(field='123').save() # has id
e = Embedded(ref=test)
f1 = Embedded._from_son(e.to_mongo())
f2 = Embedded._from_son(e.to_mongo())
self.assertEqual(f1, f2)
f1.ref # Dereferences lazily
self.assertEqual(f1, f2)
if __name__ == '__main__':
unittest.main()

View File

@@ -384,6 +384,9 @@ class FieldTest(unittest.TestCase):
person.height = 4.0
self.assertRaises(ValidationError, person.validate)
person_2 = Person(height='something invalid')
self.assertRaises(ValidationError, person_2.validate)
def test_decimal_validation(self):
"""Ensure that invalid values cannot be assigned to decimal fields.
"""
@@ -405,6 +408,11 @@ class FieldTest(unittest.TestCase):
self.assertRaises(ValidationError, person.validate)
person.height = Decimal('4.0')
self.assertRaises(ValidationError, person.validate)
person.height = 'something invalid'
self.assertRaises(ValidationError, person.validate)
person_2 = Person(height='something invalid')
self.assertRaises(ValidationError, person_2.validate)
Person.drop_collection()
@@ -1109,9 +1117,15 @@ class FieldTest(unittest.TestCase):
post.info = {'$title': 'test'}
self.assertRaises(ValidationError, post.validate)
post.info = {'nested': {'$title': 'test'}}
self.assertRaises(ValidationError, post.validate)
post.info = {'the.title': 'test'}
self.assertRaises(ValidationError, post.validate)
post.info = {'nested': {'the.title': 'test'}}
self.assertRaises(ValidationError, post.validate)
post.info = {1: 'test'}
self.assertRaises(ValidationError, post.validate)
@@ -1890,6 +1904,37 @@ class FieldTest(unittest.TestCase):
Post.drop_collection()
User.drop_collection()
def test_generic_reference_list_item_modification(self):
"""Ensure that modifications of related documents (through generic reference) don't influence on querying
"""
class Post(Document):
title = StringField()
class User(Document):
username = StringField()
bookmarks = ListField(GenericReferenceField())
Post.drop_collection()
User.drop_collection()
post_1 = Post(title="Behind the Scenes of the Pavement Reunion")
post_1.save()
user = User(bookmarks=[post_1])
user.save()
post_1.title = "Title was modified"
user.username = "New username"
user.save()
user = User.objects(bookmarks__all=[post_1]).first()
self.assertNotEqual(user, None)
self.assertEqual(user.bookmarks[0], post_1)
Post.drop_collection()
User.drop_collection()
def test_binary_fields(self):
"""Ensure that binary fields can be stored and retrieved.
"""
@@ -2546,6 +2591,20 @@ class FieldTest(unittest.TestCase):
doc = Doc.objects.get()
self.assertEqual(doc.embed_me.field_1, "hello")
def test_invalid_dict_value(self):
class DictFieldTest(Document):
dictionary = DictField(required=True)
DictFieldTest.drop_collection()
test = DictFieldTest(dictionary=None)
test.dictionary # Just access to test getter
self.assertRaises(ValidationError, test.validate)
test = DictFieldTest(dictionary=False)
test.dictionary # Just access to test getter
self.assertRaises(ValidationError, test.validate)
if __name__ == '__main__':
unittest.main()

View File

@@ -279,7 +279,7 @@ class FileTest(unittest.TestCase):
t.image.put(f)
self.fail("Should have raised an invalidation error")
except ValidationError, e:
self.assertEquals("%s" % e, "Invalid image: cannot identify image file")
self.assertEqual("%s" % e, "Invalid image: cannot identify image file")
t = TestImage()
t.image.put(open(TEST_IMAGE_PATH, 'rb'))

View File

@@ -75,6 +75,12 @@ class GeoFieldTest(unittest.TestCase):
self._test_for_expected_error(Location, coord, expected)
Location(loc=[1, 2]).validate()
Location(loc={
"type": "Point",
"coordinates": [
81.4471435546875,
23.61432859499169
]}).validate()
def test_linestring_validation(self):
class Location(Document):

View File

@@ -414,5 +414,47 @@ class GeoQueriesTest(unittest.TestCase):
roads = Road.objects.filter(poly__geo_intersects={"$geometry": polygon}).count()
self.assertEqual(1, roads)
def test_2dsphere_point_sets_correctly(self):
class Location(Document):
loc = PointField()
Location.drop_collection()
Location(loc=[1,2]).save()
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["loc"], {"type": "Point", "coordinates": [1, 2]})
Location.objects.update(set__loc=[2,1])
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["loc"], {"type": "Point", "coordinates": [2, 1]})
def test_2dsphere_linestring_sets_correctly(self):
class Location(Document):
line = LineStringField()
Location.drop_collection()
Location(line=[[1, 2], [2, 2]]).save()
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["line"], {"type": "LineString", "coordinates": [[1, 2], [2, 2]]})
Location.objects.update(set__line=[[2, 1], [1, 2]])
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["line"], {"type": "LineString", "coordinates": [[2, 1], [1, 2]]})
def test_geojson_PolygonField(self):
class Location(Document):
poly = PolygonField()
Location.drop_collection()
Location(poly=[[[40, 5], [40, 6], [41, 6], [40, 5]]]).save()
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["poly"], {"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]})
Location.objects.update(set__poly=[[[40, 4], [40, 6], [41, 6], [40, 4]]])
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["poly"], {"type": "Polygon", "coordinates": [[[40, 4], [40, 6], [41, 6], [40, 4]]]})
if __name__ == '__main__':
unittest.main()

View File

@@ -2519,6 +2519,27 @@ class QuerySetTest(unittest.TestCase):
Product.drop_collection()
def test_distinct_ListField_EmbeddedDocumentField(self):
class Author(EmbeddedDocument):
name = StringField()
class Book(Document):
title = StringField()
authors = ListField(EmbeddedDocumentField(Author))
Book.drop_collection()
mark_twain = Author(name="Mark Twain")
john_tolkien = Author(name="John Ronald Reuel Tolkien")
book = Book(title="Tom Sawyer", authors=[mark_twain]).save()
book = Book(title="The Lord of the Rings", authors=[john_tolkien]).save()
book = Book(title="The Stories", authors=[mark_twain, john_tolkien]).save()
authors = Book.objects.distinct("authors")
self.assertEqual(authors, [mark_twain, john_tolkien])
def test_custom_manager(self):
"""Ensure that custom QuerySetManager instances work as expected.
"""
@@ -2861,6 +2882,19 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(10, Post.objects.limit(5).skip(5).count(with_limit_and_skip=False))
def test_count_and_none(self):
"""Test count works with None()"""
class MyDoc(Document):
pass
MyDoc.drop_collection()
for i in xrange(0, 10):
MyDoc().save()
self.assertEqual(MyDoc.objects.count(), 10)
self.assertEqual(MyDoc.objects.none().count(), 0)
def test_call_after_limits_set(self):
"""Ensure that re-filtering after slicing works
"""
@@ -3367,6 +3401,17 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(1, MyDoc.objects.update_one(upsert=True, inc__47=1))
self.assertEqual(MyDoc.objects.get()['47'], 1)
def test_dictfield_key_looks_like_a_digit(self):
"""Only should work with DictField even if they have numeric keys."""
class MyDoc(Document):
test = DictField()
MyDoc.drop_collection()
doc = MyDoc(test={'47': 1})
doc.save()
self.assertEqual(MyDoc.objects.only('test__47').get().test['47'], 1)
def test_read_preference(self):
class Bar(Document):
pass
@@ -3609,9 +3654,9 @@ class QuerySetTest(unittest.TestCase):
docs = Noddy.objects.no_cache()
counter = len([1 for i in docs])
self.assertEquals(counter, 100)
self.assertEqual(counter, 100)
self.assertEquals(len(list(docs)), 100)
self.assertEqual(len(list(docs)), 100)
self.assertRaises(TypeError, lambda: len(docs))
with query_counter() as q:

View File

@@ -167,6 +167,35 @@ class TransformTest(unittest.TestCase):
{'attachments.views.extracted': 'no'}]}
self.assertEqual(expected, raw_query)
def test_geojson_PointField(self):
class Location(Document):
loc = PointField()
update = transform.update(Location, set__loc=[1, 2])
self.assertEqual(update, {'$set': {'loc': {"type": "Point", "coordinates": [1,2]}}})
update = transform.update(Location, set__loc={"type": "Point", "coordinates": [1,2]})
self.assertEqual(update, {'$set': {'loc': {"type": "Point", "coordinates": [1,2]}}})
def test_geojson_LineStringField(self):
class Location(Document):
line = LineStringField()
update = transform.update(Location, set__line=[[1, 2], [2, 2]])
self.assertEqual(update, {'$set': {'line': {"type": "LineString", "coordinates": [[1, 2], [2, 2]]}}})
update = transform.update(Location, set__line={"type": "LineString", "coordinates": [[1, 2], [2, 2]]})
self.assertEqual(update, {'$set': {'line': {"type": "LineString", "coordinates": [[1, 2], [2, 2]]}}})
def test_geojson_PolygonField(self):
class Location(Document):
poly = PolygonField()
update = transform.update(Location, set__poly=[[[40, 5], [40, 6], [41, 6], [40, 5]]])
self.assertEqual(update, {'$set': {'poly': {"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]}}})
update = transform.update(Location, set__poly={"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]})
self.assertEqual(update, {'$set': {'poly': {"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]}}})
if __name__ == '__main__':
unittest.main()

View File

@@ -98,6 +98,14 @@ class ConnectionTest(unittest.TestCase):
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertEqual(db.name, 'mongoenginetest2')
def test_register_connection_defaults(self):
"""Ensure that defaults are used when the host and port are None.
"""
register_connection('testdb', 'mongoenginetest', host=None, port=None)
conn = get_connection('testdb')
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
def test_connection_kwargs(self):
"""Ensure that connection kwargs get passed to pymongo.
"""