Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f9284d20ca | ||
|
9050869781 | ||
|
54975de0f3 | ||
|
a7aead5138 | ||
|
6868f66f24 | ||
|
04497aec36 | ||
|
aa9d596930 | ||
|
f96e68cd11 | ||
|
013227323d |
1
AUTHORS
1
AUTHORS
@@ -230,3 +230,4 @@ that much better:
|
|||||||
* Amit Lichtenberg (https://github.com/amitlicht)
|
* Amit Lichtenberg (https://github.com/amitlicht)
|
||||||
* Lars Butler (https://github.com/larsbutler)
|
* Lars Butler (https://github.com/larsbutler)
|
||||||
* George Macon (https://github.com/gmacon)
|
* George Macon (https://github.com/gmacon)
|
||||||
|
* Ashley Whetter (https://github.com/AWhetter)
|
||||||
|
@@ -2,10 +2,15 @@
|
|||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
Changes in 0.10.3
|
||||||
|
=================
|
||||||
|
- Fix `read_preference` (it had chaining issues with PyMongo 2.x and it didn't work at all with PyMongo 3.x) #1042
|
||||||
|
|
||||||
Changes in 0.10.2
|
Changes in 0.10.2
|
||||||
=================
|
=================
|
||||||
- Allow shard key to point to a field in an embedded document. #551
|
- Allow shard key to point to a field in an embedded document. #551
|
||||||
- Allow arbirary metadata in fields. #1129
|
- Allow arbirary metadata in fields. #1129
|
||||||
|
- ReferenceFields now support abstract document types. #837
|
||||||
|
|
||||||
Changes in 0.10.1
|
Changes in 0.10.1
|
||||||
=======================
|
=======================
|
||||||
|
@@ -895,6 +895,10 @@ class ReferenceField(BaseField):
|
|||||||
or as the :class:`~pymongo.objectid.ObjectId`.id .
|
or as the :class:`~pymongo.objectid.ObjectId`.id .
|
||||||
:param reverse_delete_rule: Determines what to do when the referring
|
:param reverse_delete_rule: Determines what to do when the referring
|
||||||
object is deleted
|
object is deleted
|
||||||
|
|
||||||
|
.. note ::
|
||||||
|
A reference to an abstract document type is always stored as a
|
||||||
|
:class:`~pymongo.dbref.DBRef`, regardless of the value of `dbref`.
|
||||||
"""
|
"""
|
||||||
if not isinstance(document_type, basestring):
|
if not isinstance(document_type, basestring):
|
||||||
if not issubclass(document_type, (Document, basestring)):
|
if not issubclass(document_type, (Document, basestring)):
|
||||||
@@ -927,9 +931,14 @@ class ReferenceField(BaseField):
|
|||||||
self._auto_dereference = instance._fields[self.name]._auto_dereference
|
self._auto_dereference = instance._fields[self.name]._auto_dereference
|
||||||
# Dereference DBRefs
|
# Dereference DBRefs
|
||||||
if self._auto_dereference and isinstance(value, DBRef):
|
if self._auto_dereference and isinstance(value, DBRef):
|
||||||
value = self.document_type._get_db().dereference(value)
|
if hasattr(value, 'cls'):
|
||||||
|
# Dereference using the class type specified in the reference
|
||||||
|
cls = get_document(value.cls)
|
||||||
|
else:
|
||||||
|
cls = self.document_type
|
||||||
|
value = cls._get_db().dereference(value)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
instance._data[self.name] = self.document_type._from_son(value)
|
instance._data[self.name] = cls._from_son(value)
|
||||||
|
|
||||||
return super(ReferenceField, self).__get__(instance, owner)
|
return super(ReferenceField, self).__get__(instance, owner)
|
||||||
|
|
||||||
@@ -939,21 +948,29 @@ class ReferenceField(BaseField):
|
|||||||
return document.id
|
return document.id
|
||||||
return document
|
return document
|
||||||
|
|
||||||
id_field_name = self.document_type._meta['id_field']
|
|
||||||
id_field = self.document_type._fields[id_field_name]
|
|
||||||
|
|
||||||
if isinstance(document, Document):
|
if isinstance(document, Document):
|
||||||
# We need the id from the saved object to create the DBRef
|
# We need the id from the saved object to create the DBRef
|
||||||
id_ = document.pk
|
id_ = document.pk
|
||||||
if id_ is None:
|
if id_ is None:
|
||||||
self.error('You can only reference documents once they have'
|
self.error('You can only reference documents once they have'
|
||||||
' been saved to the database')
|
' been saved to the database')
|
||||||
|
|
||||||
|
# Use the attributes from the document instance, so that they
|
||||||
|
# override the attributes of this field's document type
|
||||||
|
cls = document
|
||||||
else:
|
else:
|
||||||
id_ = document
|
id_ = document
|
||||||
|
cls = self.document_type
|
||||||
|
|
||||||
|
id_field_name = cls._meta['id_field']
|
||||||
|
id_field = cls._fields[id_field_name]
|
||||||
|
|
||||||
id_ = id_field.to_mongo(id_)
|
id_ = id_field.to_mongo(id_)
|
||||||
if self.dbref:
|
if self.document_type._meta.get('abstract'):
|
||||||
collection = self.document_type._get_collection_name()
|
collection = cls._get_collection_name()
|
||||||
|
return DBRef(collection, id_, cls=cls._class_name)
|
||||||
|
elif self.dbref:
|
||||||
|
collection = cls._get_collection_name()
|
||||||
return DBRef(collection, id_)
|
return DBRef(collection, id_)
|
||||||
|
|
||||||
return id_
|
return id_
|
||||||
@@ -982,6 +999,14 @@ class ReferenceField(BaseField):
|
|||||||
self.error('You can only reference documents once they have been '
|
self.error('You can only reference documents once they have been '
|
||||||
'saved to the database')
|
'saved to the database')
|
||||||
|
|
||||||
|
if self.document_type._meta.get('abstract') and \
|
||||||
|
not isinstance(value, self.document_type):
|
||||||
|
self.error('%s is not an instance of abstract reference'
|
||||||
|
' type %s' % (value._class_name,
|
||||||
|
self.document_type._class_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def lookup_member(self, member_name):
|
def lookup_member(self, member_name):
|
||||||
return self.document_type._fields.get(member_name)
|
return self.document_type._fields.get(member_name)
|
||||||
|
|
||||||
|
@@ -930,6 +930,7 @@ class BaseQuerySet(object):
|
|||||||
validate_read_preference('read_preference', read_preference)
|
validate_read_preference('read_preference', read_preference)
|
||||||
queryset = self.clone()
|
queryset = self.clone()
|
||||||
queryset._read_preference = read_preference
|
queryset._read_preference = read_preference
|
||||||
|
queryset._cursor_obj = None # we need to re-create the cursor object whenever we apply read_preference
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def scalar(self, *fields):
|
def scalar(self, *fields):
|
||||||
@@ -1443,6 +1444,14 @@ class BaseQuerySet(object):
|
|||||||
def _cursor(self):
|
def _cursor(self):
|
||||||
if self._cursor_obj is None:
|
if self._cursor_obj is None:
|
||||||
|
|
||||||
|
# In PyMongo 3+, we define the read preference on a collection
|
||||||
|
# level, not a cursor level. Thus, we need to get a cloned
|
||||||
|
# collection object using `with_options` first.
|
||||||
|
if IS_PYMONGO_3 and self._read_preference is not None:
|
||||||
|
self._cursor_obj = self._collection\
|
||||||
|
.with_options(read_preference=self._read_preference)\
|
||||||
|
.find(self._query, **self._cursor_args)
|
||||||
|
else:
|
||||||
self._cursor_obj = self._collection.find(self._query,
|
self._cursor_obj = self._collection.find(self._query,
|
||||||
**self._cursor_args)
|
**self._cursor_args)
|
||||||
# Apply where clauses to cursor
|
# Apply where clauses to cursor
|
||||||
|
@@ -2281,6 +2281,81 @@ class FieldTest(unittest.TestCase):
|
|||||||
Member.drop_collection()
|
Member.drop_collection()
|
||||||
BlogPost.drop_collection()
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
|
def test_reference_class_with_abstract_parent(self):
|
||||||
|
"""Ensure that a class with an abstract parent can be referenced.
|
||||||
|
"""
|
||||||
|
class Sibling(Document):
|
||||||
|
name = StringField()
|
||||||
|
meta = {"abstract": True}
|
||||||
|
|
||||||
|
class Sister(Sibling):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Brother(Sibling):
|
||||||
|
sibling = ReferenceField(Sibling)
|
||||||
|
|
||||||
|
Sister.drop_collection()
|
||||||
|
Brother.drop_collection()
|
||||||
|
|
||||||
|
sister = Sister(name="Alice")
|
||||||
|
sister.save()
|
||||||
|
brother = Brother(name="Bob", sibling=sister)
|
||||||
|
brother.save()
|
||||||
|
|
||||||
|
self.assertEquals(Brother.objects[0].sibling.name, sister.name)
|
||||||
|
|
||||||
|
Sister.drop_collection()
|
||||||
|
Brother.drop_collection()
|
||||||
|
|
||||||
|
def test_reference_abstract_class(self):
|
||||||
|
"""Ensure that an abstract class instance cannot be used in the
|
||||||
|
reference of that abstract class.
|
||||||
|
"""
|
||||||
|
class Sibling(Document):
|
||||||
|
name = StringField()
|
||||||
|
meta = {"abstract": True}
|
||||||
|
|
||||||
|
class Sister(Sibling):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Brother(Sibling):
|
||||||
|
sibling = ReferenceField(Sibling)
|
||||||
|
|
||||||
|
Sister.drop_collection()
|
||||||
|
Brother.drop_collection()
|
||||||
|
|
||||||
|
sister = Sibling(name="Alice")
|
||||||
|
brother = Brother(name="Bob", sibling=sister)
|
||||||
|
self.assertRaises(ValidationError, brother.save)
|
||||||
|
|
||||||
|
Sister.drop_collection()
|
||||||
|
Brother.drop_collection()
|
||||||
|
|
||||||
|
def test_abstract_reference_base_type(self):
|
||||||
|
"""Ensure that an an abstract reference fails validation when given a
|
||||||
|
Document that does not inherit from the abstract type.
|
||||||
|
"""
|
||||||
|
class Sibling(Document):
|
||||||
|
name = StringField()
|
||||||
|
meta = {"abstract": True}
|
||||||
|
|
||||||
|
class Brother(Sibling):
|
||||||
|
sibling = ReferenceField(Sibling)
|
||||||
|
|
||||||
|
class Mother(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
Brother.drop_collection()
|
||||||
|
Mother.drop_collection()
|
||||||
|
|
||||||
|
mother = Mother(name="Carol")
|
||||||
|
mother.save()
|
||||||
|
brother = Brother(name="Bob", sibling=mother)
|
||||||
|
self.assertRaises(ValidationError, brother.save)
|
||||||
|
|
||||||
|
Brother.drop_collection()
|
||||||
|
Mother.drop_collection()
|
||||||
|
|
||||||
def test_generic_reference(self):
|
def test_generic_reference(self):
|
||||||
"""Ensure that a GenericReferenceField properly dereferences items.
|
"""Ensure that a GenericReferenceField properly dereferences items.
|
||||||
"""
|
"""
|
||||||
|
@@ -4165,7 +4165,11 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_read_preference(self):
|
def test_read_preference(self):
|
||||||
class Bar(Document):
|
class Bar(Document):
|
||||||
pass
|
txt = StringField()
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
'indexes': [ 'txt' ]
|
||||||
|
}
|
||||||
|
|
||||||
Bar.drop_collection()
|
Bar.drop_collection()
|
||||||
bars = list(Bar.objects(read_preference=ReadPreference.PRIMARY))
|
bars = list(Bar.objects(read_preference=ReadPreference.PRIMARY))
|
||||||
@@ -4177,9 +4181,51 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
error_class = TypeError
|
error_class = TypeError
|
||||||
self.assertRaises(error_class, Bar.objects, read_preference='Primary')
|
self.assertRaises(error_class, Bar.objects, read_preference='Primary')
|
||||||
|
|
||||||
|
# read_preference as a kwarg
|
||||||
bars = Bar.objects(read_preference=ReadPreference.SECONDARY_PREFERRED)
|
bars = Bar.objects(read_preference=ReadPreference.SECONDARY_PREFERRED)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
|
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(bars._cursor._Cursor__read_preference,
|
||||||
|
ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
|
||||||
|
# read_preference as a query set method
|
||||||
|
bars = Bar.objects.read_preference(ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(
|
||||||
|
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(bars._cursor._Cursor__read_preference,
|
||||||
|
ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
|
||||||
|
# read_preference after skip
|
||||||
|
bars = Bar.objects.skip(1) \
|
||||||
|
.read_preference(ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(
|
||||||
|
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(bars._cursor._Cursor__read_preference,
|
||||||
|
ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
|
||||||
|
# read_preference after limit
|
||||||
|
bars = Bar.objects.limit(1) \
|
||||||
|
.read_preference(ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(
|
||||||
|
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(bars._cursor._Cursor__read_preference,
|
||||||
|
ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
|
||||||
|
# read_preference after order_by
|
||||||
|
bars = Bar.objects.order_by('txt') \
|
||||||
|
.read_preference(ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(
|
||||||
|
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(bars._cursor._Cursor__read_preference,
|
||||||
|
ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
|
||||||
|
# read_preference after hint
|
||||||
|
bars = Bar.objects.hint([('txt', 1)]) \
|
||||||
|
.read_preference(ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(
|
||||||
|
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(bars._cursor._Cursor__read_preference,
|
||||||
|
ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
|
||||||
def test_json_simple(self):
|
def test_json_simple(self):
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user