Merge remote branch 'hmarr/master'
Conflicts: mongoengine/fields.py
This commit is contained in:
commit
0a074e52e0
@ -178,7 +178,7 @@ either a single field name, or a list or tuple of field names::
|
|||||||
class User(Document):
|
class User(Document):
|
||||||
username = StringField(unique=True)
|
username = StringField(unique=True)
|
||||||
first_name = StringField()
|
first_name = StringField()
|
||||||
last_name = StringField(unique_with='last_name')
|
last_name = StringField(unique_with='first_name')
|
||||||
|
|
||||||
Document collections
|
Document collections
|
||||||
====================
|
====================
|
||||||
|
@ -135,8 +135,8 @@ additional keyword argument, :attr:`defaults` may be provided, which will be
|
|||||||
used as default values for the new document, in the case that it should need
|
used as default values for the new document, in the case that it should need
|
||||||
to be created::
|
to be created::
|
||||||
|
|
||||||
>>> a = User.objects.get_or_create(name='User A', defaults={'age': 30})
|
>>> a, created = User.objects.get_or_create(name='User A', defaults={'age': 30})
|
||||||
>>> b = User.objects.get_or_create(name='User A', defaults={'age': 40})
|
>>> b, created = User.objects.get_or_create(name='User A', defaults={'age': 40})
|
||||||
>>> a.name == b.name and a.age == b.age
|
>>> a.name == b.name and a.age == b.age
|
||||||
True
|
True
|
||||||
|
|
||||||
@ -172,7 +172,7 @@ custom manager methods as you like::
|
|||||||
|
|
||||||
@queryset_manager
|
@queryset_manager
|
||||||
def live_posts(doc_cls, queryset):
|
def live_posts(doc_cls, queryset):
|
||||||
return queryset.order_by('-date')
|
return queryset.filter(published=True)
|
||||||
|
|
||||||
BlogPost(title='test1', published=False).save()
|
BlogPost(title='test1', published=False).save()
|
||||||
BlogPost(title='test2', published=True).save()
|
BlogPost(title='test2', published=True).save()
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
from queryset import QuerySet, QuerySetManager
|
from queryset import QuerySet, QuerySetManager
|
||||||
|
from queryset import DoesNotExist, MultipleObjectsReturned
|
||||||
|
|
||||||
|
import sys
|
||||||
import pymongo
|
import pymongo
|
||||||
|
|
||||||
|
|
||||||
@ -167,9 +169,24 @@ class DocumentMetaclass(type):
|
|||||||
for field in new_class._fields.values():
|
for field in new_class._fields.values():
|
||||||
field.owner_document = new_class
|
field.owner_document = new_class
|
||||||
|
|
||||||
|
module = attrs.pop('__module__')
|
||||||
|
|
||||||
|
new_class.add_to_class('DoesNotExist', subclass_exception('DoesNotExist',
|
||||||
|
tuple(x.DoesNotExist
|
||||||
|
for k,x in superclasses.items())
|
||||||
|
or (DoesNotExist,), module))
|
||||||
|
|
||||||
|
new_class.add_to_class('MultipleObjectsReturned', subclass_exception('MultipleObjectsReturned',
|
||||||
|
tuple(x.MultipleObjectsReturned
|
||||||
|
for k,x in superclasses.items())
|
||||||
|
or (MultipleObjectsReturned,), module))
|
||||||
return new_class
|
return new_class
|
||||||
|
|
||||||
|
|
||||||
|
def add_to_class(self, name, value):
|
||||||
|
setattr(self, name, value)
|
||||||
|
|
||||||
|
|
||||||
class TopLevelDocumentMetaclass(DocumentMetaclass):
|
class TopLevelDocumentMetaclass(DocumentMetaclass):
|
||||||
"""Metaclass for top-level documents (i.e. documents that have their own
|
"""Metaclass for top-level documents (i.e. documents that have their own
|
||||||
collection in the database.
|
collection in the database.
|
||||||
@ -417,3 +434,11 @@ class BaseDocument(object):
|
|||||||
if self.id == other.id:
|
if self.id == other.id:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if sys.version_info < (2, 5):
|
||||||
|
# Prior to Python 2.5, Exception was an old-style class
|
||||||
|
def subclass_exception(name, parents, unused):
|
||||||
|
return types.ClassType(name, parents, {})
|
||||||
|
else:
|
||||||
|
def subclass_exception(name, parents, module):
|
||||||
|
return type(name, parents, {'__module__': module})
|
@ -1,6 +1,7 @@
|
|||||||
from base import BaseField, ObjectIdField, ValidationError, get_document
|
from base import BaseField, ObjectIdField, ValidationError, get_document
|
||||||
from document import Document, EmbeddedDocument
|
from document import Document, EmbeddedDocument
|
||||||
from connection import _get_db
|
from connection import _get_db
|
||||||
|
from operator import itemgetter
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import pymongo
|
import pymongo
|
||||||
@ -12,7 +13,7 @@ __all__ = ['StringField', 'IntField', 'FloatField', 'BooleanField',
|
|||||||
'DateTimeField', 'EmbeddedDocumentField', 'ListField', 'DictField',
|
'DateTimeField', 'EmbeddedDocumentField', 'ListField', 'DictField',
|
||||||
'ObjectIdField', 'ReferenceField', 'ValidationError',
|
'ObjectIdField', 'ReferenceField', 'ValidationError',
|
||||||
'DecimalField', 'URLField', 'GenericReferenceField',
|
'DecimalField', 'URLField', 'GenericReferenceField',
|
||||||
'BinaryField', 'EmailField', 'GeoLocationField']
|
'BinaryField', 'SortedListField', 'EmailField', 'GeoLocationField']
|
||||||
|
|
||||||
RECURSIVE_REFERENCE_CONSTANT = 'self'
|
RECURSIVE_REFERENCE_CONSTANT = 'self'
|
||||||
|
|
||||||
@ -169,6 +170,9 @@ class DecimalField(BaseField):
|
|||||||
value = unicode(value)
|
value = unicode(value)
|
||||||
return decimal.Decimal(value)
|
return decimal.Decimal(value)
|
||||||
|
|
||||||
|
def to_mongo(self, value):
|
||||||
|
return unicode(value)
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
if not isinstance(value, decimal.Decimal):
|
if not isinstance(value, decimal.Decimal):
|
||||||
if not isinstance(value, basestring):
|
if not isinstance(value, basestring):
|
||||||
@ -320,6 +324,23 @@ class ListField(BaseField):
|
|||||||
def lookup_member(self, member_name):
|
def lookup_member(self, member_name):
|
||||||
return self.field.lookup_member(member_name)
|
return self.field.lookup_member(member_name)
|
||||||
|
|
||||||
|
class SortedListField(ListField):
|
||||||
|
"""A ListField that sorts the contents of its list before writing to
|
||||||
|
the database in order to ensure that a sorted list is always
|
||||||
|
retrieved.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_ordering = None
|
||||||
|
|
||||||
|
def __init__(self, field, **kwargs):
|
||||||
|
if 'ordering' in kwargs.keys():
|
||||||
|
self._ordering = kwargs.pop('ordering')
|
||||||
|
super(SortedListField, self).__init__(field, **kwargs)
|
||||||
|
|
||||||
|
def to_mongo(self, value):
|
||||||
|
if self._ordering is not None:
|
||||||
|
return sorted([self.field.to_mongo(item) for item in value], key=itemgetter(self._ordering))
|
||||||
|
return sorted([self.field.to_mongo(item) for item in value])
|
||||||
|
|
||||||
class DictField(BaseField):
|
class DictField(BaseField):
|
||||||
"""A dictionary field that wraps a standard Python dictionary. This is
|
"""A dictionary field that wraps a standard Python dictionary. This is
|
||||||
|
@ -14,7 +14,6 @@ REPR_OUTPUT_SIZE = 20
|
|||||||
class DoesNotExist(Exception):
|
class DoesNotExist(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MultipleObjectsReturned(Exception):
|
class MultipleObjectsReturned(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -26,6 +25,8 @@ class InvalidQueryError(Exception):
|
|||||||
class OperationError(Exception):
|
class OperationError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class InvalidCollectionError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
RE_TYPE = type(re.compile(''))
|
RE_TYPE = type(re.compile(''))
|
||||||
|
|
||||||
@ -345,8 +346,9 @@ class QuerySet(object):
|
|||||||
def get(self, *q_objs, **query):
|
def get(self, *q_objs, **query):
|
||||||
"""Retrieve the the matching object raising
|
"""Retrieve the the matching object raising
|
||||||
:class:`~mongoengine.queryset.MultipleObjectsReturned` or
|
:class:`~mongoengine.queryset.MultipleObjectsReturned` or
|
||||||
:class:`~mongoengine.queryset.DoesNotExist` exceptions if multiple or
|
`DocumentName.MultipleObjectsReturned` exception if multiple results and
|
||||||
no results are found.
|
:class:`~mongoengine.queryset.DoesNotExist` or `DocumentName.DoesNotExist`
|
||||||
|
if no results are found.
|
||||||
|
|
||||||
.. versionadded:: 0.3
|
.. versionadded:: 0.3
|
||||||
"""
|
"""
|
||||||
@ -356,16 +358,20 @@ class QuerySet(object):
|
|||||||
return self[0]
|
return self[0]
|
||||||
elif count > 1:
|
elif count > 1:
|
||||||
message = u'%d items returned, instead of 1' % count
|
message = u'%d items returned, instead of 1' % count
|
||||||
raise MultipleObjectsReturned(message)
|
raise self._document.MultipleObjectsReturned(message)
|
||||||
else:
|
else:
|
||||||
raise DoesNotExist('Document not found')
|
raise self._document.DoesNotExist("%s matching query does not exist."
|
||||||
|
% self._document._class_name)
|
||||||
|
|
||||||
def get_or_create(self, *q_objs, **query):
|
def get_or_create(self, *q_objs, **query):
|
||||||
"""Retreive unique object or create, if it doesn't exist. Raises
|
"""Retrieve unique object or create, if it doesn't exist. Returns a tuple of
|
||||||
:class:`~mongoengine.queryset.MultipleObjectsReturned` if multiple
|
``(object, created)``, where ``object`` is the retrieved or created object
|
||||||
results are found. A new document will be created if the document
|
and ``created`` is a boolean specifying whether a new object was created. Raises
|
||||||
doesn't exists; a dictionary of default values for the new document
|
:class:`~mongoengine.queryset.MultipleObjectsReturned` or
|
||||||
may be provided as a keyword argument called :attr:`defaults`.
|
`DocumentName.MultipleObjectsReturned` if multiple results are found.
|
||||||
|
A new document will be created if the document doesn't exists; a
|
||||||
|
dictionary of default values for the new document may be provided as a
|
||||||
|
keyword argument called :attr:`defaults`.
|
||||||
|
|
||||||
.. versionadded:: 0.3
|
.. versionadded:: 0.3
|
||||||
"""
|
"""
|
||||||
@ -379,12 +385,12 @@ class QuerySet(object):
|
|||||||
query.update(defaults)
|
query.update(defaults)
|
||||||
doc = self._document(**query)
|
doc = self._document(**query)
|
||||||
doc.save()
|
doc.save()
|
||||||
return doc
|
return doc, True
|
||||||
elif count == 1:
|
elif count == 1:
|
||||||
return self.first()
|
return self.first(), False
|
||||||
else:
|
else:
|
||||||
message = u'%d items returned, instead of 1' % count
|
message = u'%d items returned, instead of 1' % count
|
||||||
raise MultipleObjectsReturned(message)
|
raise self._document.MultipleObjectsReturned(message)
|
||||||
|
|
||||||
def first(self):
|
def first(self):
|
||||||
"""Retrieve the first object matching the query.
|
"""Retrieve the first object matching the query.
|
||||||
@ -873,10 +879,6 @@ class QuerySet(object):
|
|||||||
return repr(data)
|
return repr(data)
|
||||||
|
|
||||||
|
|
||||||
class InvalidCollectionError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class QuerySetManager(object):
|
class QuerySetManager(object):
|
||||||
|
|
||||||
def __init__(self, manager_func=None):
|
def __init__(self, manager_func=None):
|
||||||
|
@ -136,12 +136,16 @@ class FieldTest(unittest.TestCase):
|
|||||||
height = DecimalField(min_value=Decimal('0.1'),
|
height = DecimalField(min_value=Decimal('0.1'),
|
||||||
max_value=Decimal('3.5'))
|
max_value=Decimal('3.5'))
|
||||||
|
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
person = Person()
|
person = Person()
|
||||||
person.height = Decimal('1.89')
|
person.height = Decimal('1.89')
|
||||||
person.validate()
|
person.save()
|
||||||
|
person.reload()
|
||||||
|
self.assertEqual(person.height, Decimal('1.89'))
|
||||||
|
|
||||||
person.height = '2.0'
|
person.height = '2.0'
|
||||||
person.validate()
|
person.save()
|
||||||
person.height = 0.01
|
person.height = 0.01
|
||||||
self.assertRaises(ValidationError, person.validate)
|
self.assertRaises(ValidationError, person.validate)
|
||||||
person.height = Decimal('0.01')
|
person.height = Decimal('0.01')
|
||||||
@ -149,6 +153,8 @@ class FieldTest(unittest.TestCase):
|
|||||||
person.height = Decimal('4.0')
|
person.height = Decimal('4.0')
|
||||||
self.assertRaises(ValidationError, person.validate)
|
self.assertRaises(ValidationError, person.validate)
|
||||||
|
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
def test_boolean_validation(self):
|
def test_boolean_validation(self):
|
||||||
"""Ensure that invalid values cannot be assigned to boolean fields.
|
"""Ensure that invalid values cannot be assigned to boolean fields.
|
||||||
"""
|
"""
|
||||||
@ -212,6 +218,37 @@ class FieldTest(unittest.TestCase):
|
|||||||
post.comments = 'yay'
|
post.comments = 'yay'
|
||||||
self.assertRaises(ValidationError, post.validate)
|
self.assertRaises(ValidationError, post.validate)
|
||||||
|
|
||||||
|
def test_sorted_list_sorting(self):
|
||||||
|
"""Ensure that a sorted list field properly sorts values.
|
||||||
|
"""
|
||||||
|
class Comment(EmbeddedDocument):
|
||||||
|
order = IntField()
|
||||||
|
content = StringField()
|
||||||
|
|
||||||
|
class BlogPost(Document):
|
||||||
|
content = StringField()
|
||||||
|
comments = SortedListField(EmbeddedDocumentField(Comment), ordering='order')
|
||||||
|
tags = SortedListField(StringField())
|
||||||
|
|
||||||
|
post = BlogPost(content='Went for a walk today...')
|
||||||
|
post.save()
|
||||||
|
|
||||||
|
post.tags = ['leisure', 'fun']
|
||||||
|
post.save()
|
||||||
|
post.reload()
|
||||||
|
self.assertEqual(post.tags, ['fun', 'leisure'])
|
||||||
|
|
||||||
|
comment1 = Comment(content='Good for you', order=1)
|
||||||
|
comment2 = Comment(content='Yay.', order=0)
|
||||||
|
comments = [comment1, comment2]
|
||||||
|
post.comments = comments
|
||||||
|
post.save()
|
||||||
|
post.reload()
|
||||||
|
self.assertEqual(post.comments[0].content, comment2.content)
|
||||||
|
self.assertEqual(post.comments[1].content, comment1.content)
|
||||||
|
|
||||||
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
def test_dict_validation(self):
|
def test_dict_validation(self):
|
||||||
"""Ensure that dict types work as expected.
|
"""Ensure that dict types work as expected.
|
||||||
"""
|
"""
|
||||||
|
@ -147,6 +147,7 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
# Try retrieving when no objects exists
|
# Try retrieving when no objects exists
|
||||||
self.assertRaises(DoesNotExist, self.Person.objects.get)
|
self.assertRaises(DoesNotExist, self.Person.objects.get)
|
||||||
|
self.assertRaises(self.Person.DoesNotExist, self.Person.objects.get)
|
||||||
|
|
||||||
person1 = self.Person(name="User A", age=20)
|
person1 = self.Person(name="User A", age=20)
|
||||||
person1.save()
|
person1.save()
|
||||||
@ -155,6 +156,7 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
|
|
||||||
# Retrieve the first person from the database
|
# Retrieve the first person from the database
|
||||||
self.assertRaises(MultipleObjectsReturned, self.Person.objects.get)
|
self.assertRaises(MultipleObjectsReturned, self.Person.objects.get)
|
||||||
|
self.assertRaises(self.Person.MultipleObjectsReturned, self.Person.objects.get)
|
||||||
|
|
||||||
# Use a query to filter the people found to just person2
|
# Use a query to filter the people found to just person2
|
||||||
person = self.Person.objects.get(age=30)
|
person = self.Person.objects.get(age=30)
|
||||||
@ -163,6 +165,9 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
person = self.Person.objects.get(age__lt=30)
|
person = self.Person.objects.get(age__lt=30)
|
||||||
self.assertEqual(person.name, "User A")
|
self.assertEqual(person.name, "User A")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_or_create(self):
|
def test_get_or_create(self):
|
||||||
"""Ensure that ``get_or_create`` returns one result or creates a new
|
"""Ensure that ``get_or_create`` returns one result or creates a new
|
||||||
document.
|
document.
|
||||||
@ -175,16 +180,21 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
# Retrieve the first person from the database
|
# Retrieve the first person from the database
|
||||||
self.assertRaises(MultipleObjectsReturned,
|
self.assertRaises(MultipleObjectsReturned,
|
||||||
self.Person.objects.get_or_create)
|
self.Person.objects.get_or_create)
|
||||||
|
self.assertRaises(self.Person.MultipleObjectsReturned,
|
||||||
|
self.Person.objects.get_or_create)
|
||||||
|
|
||||||
# Use a query to filter the people found to just person2
|
# Use a query to filter the people found to just person2
|
||||||
person = self.Person.objects.get_or_create(age=30)
|
person, created = self.Person.objects.get_or_create(age=30)
|
||||||
self.assertEqual(person.name, "User B")
|
self.assertEqual(person.name, "User B")
|
||||||
|
self.assertEqual(created, False)
|
||||||
|
|
||||||
person = self.Person.objects.get_or_create(age__lt=30)
|
person, created = self.Person.objects.get_or_create(age__lt=30)
|
||||||
self.assertEqual(person.name, "User A")
|
self.assertEqual(person.name, "User A")
|
||||||
|
self.assertEqual(created, False)
|
||||||
|
|
||||||
# Try retrieving when no objects exists - new doc should be created
|
# Try retrieving when no objects exists - new doc should be created
|
||||||
self.Person.objects.get_or_create(age=50, defaults={'name': 'User C'})
|
person, created = self.Person.objects.get_or_create(age=50, defaults={'name': 'User C'})
|
||||||
|
self.assertEqual(created, True)
|
||||||
|
|
||||||
person = self.Person.objects.get(age=50)
|
person = self.Person.objects.get(age=50)
|
||||||
self.assertEqual(person.name, "User C")
|
self.assertEqual(person.name, "User C")
|
||||||
@ -616,6 +626,27 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
|
|
||||||
BlogPost.drop_collection()
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
|
def test_update_pull(self):
|
||||||
|
"""Ensure that the 'pull' update operation works correctly.
|
||||||
|
"""
|
||||||
|
class Comment(EmbeddedDocument):
|
||||||
|
content = StringField()
|
||||||
|
|
||||||
|
class BlogPost(Document):
|
||||||
|
slug = StringField()
|
||||||
|
comments = ListField(EmbeddedDocumentField(Comment))
|
||||||
|
|
||||||
|
comment1 = Comment(content="test1")
|
||||||
|
comment2 = Comment(content="test2")
|
||||||
|
|
||||||
|
post = BlogPost(slug="test", comments=[comment1, comment2])
|
||||||
|
post.save()
|
||||||
|
self.assertTrue(comment2 in post.comments)
|
||||||
|
|
||||||
|
BlogPost.objects(slug="test").update(pull__comments__content="test2")
|
||||||
|
post.reload()
|
||||||
|
self.assertTrue(comment2 not in post.comments)
|
||||||
|
|
||||||
def test_order_by(self):
|
def test_order_by(self):
|
||||||
"""Ensure that QuerySets may be ordered.
|
"""Ensure that QuerySets may be ordered.
|
||||||
"""
|
"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user