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):
|
||||
username = StringField(unique=True)
|
||||
first_name = StringField()
|
||||
last_name = StringField(unique_with='last_name')
|
||||
last_name = StringField(unique_with='first_name')
|
||||
|
||||
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
|
||||
to be created::
|
||||
|
||||
>>> a = User.objects.get_or_create(name='User A', defaults={'age': 30})
|
||||
>>> b = User.objects.get_or_create(name='User A', defaults={'age': 40})
|
||||
>>> a, created = User.objects.get_or_create(name='User A', defaults={'age': 30})
|
||||
>>> b, created = User.objects.get_or_create(name='User A', defaults={'age': 40})
|
||||
>>> a.name == b.name and a.age == b.age
|
||||
True
|
||||
|
||||
@ -172,7 +172,7 @@ custom manager methods as you like::
|
||||
|
||||
@queryset_manager
|
||||
def live_posts(doc_cls, queryset):
|
||||
return queryset.order_by('-date')
|
||||
return queryset.filter(published=True)
|
||||
|
||||
BlogPost(title='test1', published=False).save()
|
||||
BlogPost(title='test2', published=True).save()
|
||||
|
@ -1,5 +1,7 @@
|
||||
from queryset import QuerySet, QuerySetManager
|
||||
from queryset import DoesNotExist, MultipleObjectsReturned
|
||||
|
||||
import sys
|
||||
import pymongo
|
||||
|
||||
|
||||
@ -167,9 +169,24 @@ class DocumentMetaclass(type):
|
||||
for field in new_class._fields.values():
|
||||
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
|
||||
|
||||
|
||||
def add_to_class(self, name, value):
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class TopLevelDocumentMetaclass(DocumentMetaclass):
|
||||
"""Metaclass for top-level documents (i.e. documents that have their own
|
||||
collection in the database.
|
||||
@ -417,3 +434,11 @@ class BaseDocument(object):
|
||||
if self.id == other.id:
|
||||
return True
|
||||
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 document import Document, EmbeddedDocument
|
||||
from connection import _get_db
|
||||
from operator import itemgetter
|
||||
|
||||
import re
|
||||
import pymongo
|
||||
@ -12,7 +13,7 @@ __all__ = ['StringField', 'IntField', 'FloatField', 'BooleanField',
|
||||
'DateTimeField', 'EmbeddedDocumentField', 'ListField', 'DictField',
|
||||
'ObjectIdField', 'ReferenceField', 'ValidationError',
|
||||
'DecimalField', 'URLField', 'GenericReferenceField',
|
||||
'BinaryField', 'EmailField', 'GeoLocationField']
|
||||
'BinaryField', 'SortedListField', 'EmailField', 'GeoLocationField']
|
||||
|
||||
RECURSIVE_REFERENCE_CONSTANT = 'self'
|
||||
|
||||
@ -169,6 +170,9 @@ class DecimalField(BaseField):
|
||||
value = unicode(value)
|
||||
return decimal.Decimal(value)
|
||||
|
||||
def to_mongo(self, value):
|
||||
return unicode(value)
|
||||
|
||||
def validate(self, value):
|
||||
if not isinstance(value, decimal.Decimal):
|
||||
if not isinstance(value, basestring):
|
||||
@ -320,6 +324,23 @@ class ListField(BaseField):
|
||||
def lookup_member(self, 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):
|
||||
"""A dictionary field that wraps a standard Python dictionary. This is
|
||||
|
@ -14,7 +14,6 @@ REPR_OUTPUT_SIZE = 20
|
||||
class DoesNotExist(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MultipleObjectsReturned(Exception):
|
||||
pass
|
||||
|
||||
@ -26,6 +25,8 @@ class InvalidQueryError(Exception):
|
||||
class OperationError(Exception):
|
||||
pass
|
||||
|
||||
class InvalidCollectionError(Exception):
|
||||
pass
|
||||
|
||||
RE_TYPE = type(re.compile(''))
|
||||
|
||||
@ -345,8 +346,9 @@ class QuerySet(object):
|
||||
def get(self, *q_objs, **query):
|
||||
"""Retrieve the the matching object raising
|
||||
:class:`~mongoengine.queryset.MultipleObjectsReturned` or
|
||||
:class:`~mongoengine.queryset.DoesNotExist` exceptions if multiple or
|
||||
no results are found.
|
||||
`DocumentName.MultipleObjectsReturned` exception if multiple results and
|
||||
:class:`~mongoengine.queryset.DoesNotExist` or `DocumentName.DoesNotExist`
|
||||
if no results are found.
|
||||
|
||||
.. versionadded:: 0.3
|
||||
"""
|
||||
@ -356,16 +358,20 @@ class QuerySet(object):
|
||||
return self[0]
|
||||
elif count > 1:
|
||||
message = u'%d items returned, instead of 1' % count
|
||||
raise MultipleObjectsReturned(message)
|
||||
raise self._document.MultipleObjectsReturned(message)
|
||||
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):
|
||||
"""Retreive unique object or create, if it doesn't exist. Raises
|
||||
:class:`~mongoengine.queryset.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`.
|
||||
"""Retrieve unique object or create, if it doesn't exist. Returns a tuple of
|
||||
``(object, created)``, where ``object`` is the retrieved or created object
|
||||
and ``created`` is a boolean specifying whether a new object was created. Raises
|
||||
:class:`~mongoengine.queryset.MultipleObjectsReturned` or
|
||||
`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
|
||||
"""
|
||||
@ -379,12 +385,12 @@ class QuerySet(object):
|
||||
query.update(defaults)
|
||||
doc = self._document(**query)
|
||||
doc.save()
|
||||
return doc
|
||||
return doc, True
|
||||
elif count == 1:
|
||||
return self.first()
|
||||
return self.first(), False
|
||||
else:
|
||||
message = u'%d items returned, instead of 1' % count
|
||||
raise MultipleObjectsReturned(message)
|
||||
raise self._document.MultipleObjectsReturned(message)
|
||||
|
||||
def first(self):
|
||||
"""Retrieve the first object matching the query.
|
||||
@ -873,10 +879,6 @@ class QuerySet(object):
|
||||
return repr(data)
|
||||
|
||||
|
||||
class InvalidCollectionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class QuerySetManager(object):
|
||||
|
||||
def __init__(self, manager_func=None):
|
||||
|
@ -136,12 +136,16 @@ class FieldTest(unittest.TestCase):
|
||||
height = DecimalField(min_value=Decimal('0.1'),
|
||||
max_value=Decimal('3.5'))
|
||||
|
||||
Person.drop_collection()
|
||||
|
||||
person = Person()
|
||||
person.height = Decimal('1.89')
|
||||
person.validate()
|
||||
person.save()
|
||||
person.reload()
|
||||
self.assertEqual(person.height, Decimal('1.89'))
|
||||
|
||||
person.height = '2.0'
|
||||
person.validate()
|
||||
person.save()
|
||||
person.height = 0.01
|
||||
self.assertRaises(ValidationError, person.validate)
|
||||
person.height = Decimal('0.01')
|
||||
@ -149,6 +153,8 @@ class FieldTest(unittest.TestCase):
|
||||
person.height = Decimal('4.0')
|
||||
self.assertRaises(ValidationError, person.validate)
|
||||
|
||||
Person.drop_collection()
|
||||
|
||||
def test_boolean_validation(self):
|
||||
"""Ensure that invalid values cannot be assigned to boolean fields.
|
||||
"""
|
||||
@ -212,6 +218,37 @@ class FieldTest(unittest.TestCase):
|
||||
post.comments = 'yay'
|
||||
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):
|
||||
"""Ensure that dict types work as expected.
|
||||
"""
|
||||
|
@ -147,6 +147,7 @@ class QuerySetTest(unittest.TestCase):
|
||||
"""
|
||||
# Try retrieving when no objects exists
|
||||
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.save()
|
||||
@ -155,6 +156,7 @@ class QuerySetTest(unittest.TestCase):
|
||||
|
||||
# Retrieve the first person from the database
|
||||
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
|
||||
person = self.Person.objects.get(age=30)
|
||||
@ -163,6 +165,9 @@ class QuerySetTest(unittest.TestCase):
|
||||
person = self.Person.objects.get(age__lt=30)
|
||||
self.assertEqual(person.name, "User A")
|
||||
|
||||
|
||||
|
||||
|
||||
def test_get_or_create(self):
|
||||
"""Ensure that ``get_or_create`` returns one result or creates a new
|
||||
document.
|
||||
@ -175,16 +180,21 @@ class QuerySetTest(unittest.TestCase):
|
||||
# Retrieve the first person from the database
|
||||
self.assertRaises(MultipleObjectsReturned,
|
||||
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
|
||||
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(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(created, False)
|
||||
|
||||
# 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)
|
||||
self.assertEqual(person.name, "User C")
|
||||
@ -616,6 +626,27 @@ class QuerySetTest(unittest.TestCase):
|
||||
|
||||
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):
|
||||
"""Ensure that QuerySets may be ordered.
|
||||
"""
|
||||
|
Loading…
x
Reference in New Issue
Block a user