Merge branch 'master' of github.com:MongoEngine/mongoengine into release_0_19_0

This commit is contained in:
Bastien Gérard 2019-12-20 23:49:41 +01:00
commit c22eb34017
6 changed files with 101 additions and 19 deletions

View File

@ -744,7 +744,7 @@ Document inheritance
To create a specialised type of a :class:`~mongoengine.Document` you have To create a specialised type of a :class:`~mongoengine.Document` you have
defined, you may subclass it and add any extra fields or methods you may need. defined, you may subclass it and add any extra fields or methods you may need.
As this is new class is not a direct subclass of As this new class is not a direct subclass of
:class:`~mongoengine.Document`, it will not be stored in its own collection; it :class:`~mongoengine.Document`, it will not be stored in its own collection; it
will use the same collection as its superclass uses. This allows for more will use the same collection as its superclass uses. This allows for more
convenient and efficient retrieval of related documents -- all you need do is convenient and efficient retrieval of related documents -- all you need do is
@ -767,6 +767,27 @@ document.::
Setting :attr:`allow_inheritance` to True should also be used in Setting :attr:`allow_inheritance` to True should also be used in
:class:`~mongoengine.EmbeddedDocument` class in case you need to subclass it :class:`~mongoengine.EmbeddedDocument` class in case you need to subclass it
When it comes to querying using :attr:`.objects()`, querying `Page.objects()` will query
both `Page` and `DatedPage` whereas querying `DatedPage` will only query the `DatedPage` documents.
Behind the scenes, MongoEngine deals with inheritance by adding a :attr:`_cls` attribute that contains
the class name in every documents. When a document is loaded, MongoEngine checks
it's :attr:`_cls` attribute and use that class to construct the instance.::
Page(title='a funky title').save()
DatedPage(title='another title', date=datetime.utcnow()).save()
print(Page.objects().count()) # 2
print(DatedPage.objects().count()) # 1
# print documents in their native form
# we remove 'id' to avoid polluting the output with unnecessary detail
qs = Page.objects.exclude('id').as_pymongo()
print(list(qs))
# [
# {'_cls': u 'Page', 'title': 'a funky title'},
# {'_cls': u 'Page.DatedPage', 'title': u 'another title', 'date': datetime.datetime(2019, 12, 13, 20, 16, 59, 993000)}
# ]
Working with existing data Working with existing data
-------------------------- --------------------------
As MongoEngine no longer defaults to needing :attr:`_cls`, you can quickly and As MongoEngine no longer defaults to needing :attr:`_cls`, you can quickly and

View File

@ -2,10 +2,10 @@
Use mongomock for testing Use mongomock for testing
============================== ==============================
`mongomock <https://github.com/vmalloc/mongomock/>`_ is a package to do just `mongomock <https://github.com/vmalloc/mongomock/>`_ is a package to do just
what the name implies, mocking a mongo database. what the name implies, mocking a mongo database.
To use with mongoengine, simply specify mongomock when connecting with To use with mongoengine, simply specify mongomock when connecting with
mongoengine: mongoengine:
.. code-block:: python .. code-block:: python
@ -21,7 +21,7 @@ or with an alias:
conn = get_connection('testdb') conn = get_connection('testdb')
Example of test file: Example of test file:
-------- ---------------------
.. code-block:: python .. code-block:: python
import unittest import unittest
@ -45,4 +45,4 @@ Example of test file:
pers.save() pers.save()
fresh_pers = Person.objects().first() fresh_pers = Person.objects().first()
self.assertEqual(fresh_pers.name, 'John') assert fresh_pers.name == 'John'

View File

@ -222,6 +222,18 @@ keyword argument::
.. versionadded:: 0.4 .. versionadded:: 0.4
Sorting/Ordering results
========================
It is possible to order the results by 1 or more keys using :meth:`~mongoengine.queryset.QuerySet.order_by`.
The order may be specified by prepending each of the keys by "+" or "-". Ascending order is assumed if there's no prefix.::
# Order by ascending date
blogs = BlogPost.objects().order_by('date') # equivalent to .order_by('+date')
# Order by ascending date first, then descending title
blogs = BlogPost.objects().order_by('+date', '-title')
Limiting and skipping results Limiting and skipping results
============================= =============================
Just as with traditional ORMs, you may limit the number of results returned or Just as with traditional ORMs, you may limit the number of results returned or
@ -585,7 +597,8 @@ cannot use the `$` syntax in keyword arguments it has been mapped to `S`::
['database', 'mongodb'] ['database', 'mongodb']
From MongoDB version 2.6, push operator supports $position value which allows From MongoDB version 2.6, push operator supports $position value which allows
to push values with index. to push values with index::
>>> post = BlogPost(title="Test", tags=["mongo"]) >>> post = BlogPost(title="Test", tags=["mongo"])
>>> post.save() >>> post.save()
>>> post.update(push__tags__0=["database", "code"]) >>> post.update(push__tags__0=["database", "code"])

View File

@ -120,6 +120,9 @@ class BaseList(list):
super(BaseList, self).__init__(list_items) super(BaseList, self).__init__(list_items)
def __getitem__(self, key): def __getitem__(self, key):
# change index to positive value because MongoDB does not support negative one
if isinstance(key, int) and key < 0:
key = len(self) + key
value = super(BaseList, self).__getitem__(key) value = super(BaseList, self).__getitem__(key)
if isinstance(key, slice): if isinstance(key, slice):

View File

@ -41,7 +41,7 @@ from tests.utils import MongoDBTestCase, get_as_pymongo
TEST_IMAGE_PATH = os.path.join(os.path.dirname(__file__), "../fields/mongoengine.png") TEST_IMAGE_PATH = os.path.join(os.path.dirname(__file__), "../fields/mongoengine.png")
class TestInstance(MongoDBTestCase): class TestDocumentInstance(MongoDBTestCase):
def setUp(self): def setUp(self):
class Job(EmbeddedDocument): class Job(EmbeddedDocument):
name = StringField() name = StringField()
@ -3338,19 +3338,19 @@ class TestInstance(MongoDBTestCase):
# worker1.job should be equal to the job used originally to create the # worker1.job should be equal to the job used originally to create the
# document. # document.
self.assertEqual(worker1.job, worker.job) assert worker1.job == worker.job
# worker1.job should be equal to a newly created Job EmbeddedDocument # worker1.job should be equal to a newly created Job EmbeddedDocument
# using either the Boss object or his ID. # using either the Boss object or his ID.
self.assertEqual(worker1.job, Job(boss=boss, boss_dbref=boss)) assert worker1.job == Job(boss=boss, boss_dbref=boss)
self.assertEqual(worker1.job, Job(boss=boss.id, boss_dbref=boss.id)) assert worker1.job == Job(boss=boss.id, boss_dbref=boss.id)
# The above equalities should also hold after worker1.job.boss has been # The above equalities should also hold after worker1.job.boss has been
# fetch()ed. # fetch()ed.
worker1.job.boss.fetch() worker1.job.boss.fetch()
self.assertEqual(worker1.job, worker.job) assert worker1.job == worker.job
self.assertEqual(worker1.job, Job(boss=boss, boss_dbref=boss)) assert worker1.job == Job(boss=boss, boss_dbref=boss)
self.assertEqual(worker1.job, Job(boss=boss.id, boss_dbref=boss.id)) assert worker1.job == Job(boss=boss.id, boss_dbref=boss.id)
def test_dbref_equality(self): def test_dbref_equality(self):
class Test2(Document): class Test2(Document):
@ -3617,6 +3617,51 @@ class TestInstance(MongoDBTestCase):
assert b._instance == a assert b._instance == a
assert idx == 2 assert idx == 2
def test_updating_listfield_manipulate_list(self):
class Company(Document):
name = StringField()
employees = ListField(field=DictField())
Company.drop_collection()
comp = Company(name="BigBank", employees=[{"name": "John"}])
comp.save()
comp.employees.append({"name": "Bill"})
comp.save()
stored_comp = get_as_pymongo(comp)
self.assertEqual(
stored_comp,
{
"_id": comp.id,
"employees": [{"name": "John"}, {"name": "Bill"}],
"name": "BigBank",
},
)
comp = comp.reload()
comp.employees[0]["color"] = "red"
comp.employees[-1]["color"] = "blue"
comp.employees[-1].update({"size": "xl"})
comp.save()
assert len(comp.employees) == 2
assert comp.employees[0] == {"name": "John", "color": "red"}
assert comp.employees[1] == {"name": "Bill", "size": "xl", "color": "blue"}
stored_comp = get_as_pymongo(comp)
self.assertEqual(
stored_comp,
{
"_id": comp.id,
"employees": [
{"name": "John", "color": "red"},
{"size": "xl", "color": "blue", "name": "Bill"},
],
"name": "BigBank",
},
)
def test_falsey_pk(self): def test_falsey_pk(self):
"""Ensure that we can create and update a document with Falsey PK.""" """Ensure that we can create and update a document with Falsey PK."""
@ -3693,13 +3738,13 @@ class TestInstance(MongoDBTestCase):
value = u"I_should_be_a_dict" value = u"I_should_be_a_dict"
coll.insert_one({"light_saber": value}) coll.insert_one({"light_saber": value})
with self.assertRaises(InvalidDocumentError) as cm: with pytest.raises(InvalidDocumentError) as exc_info:
list(Jedi.objects) list(Jedi.objects)
self.assertEqual( assert str(
str(cm.exception), exc_info.value
"Invalid data to create a `Jedi` instance.\nField 'light_saber' - The source SON object needs to be of type 'dict' but a '%s' was found" ) == "Invalid data to create a `Jedi` instance.\nField 'light_saber' - The source SON object needs to be of type 'dict' but a '%s' was found" % type(
% type(value), value
) )

View File

@ -151,7 +151,7 @@ class TestFileField(MongoDBTestCase):
result = StreamFile.objects.first() result = StreamFile.objects.first()
assert streamfile == result assert streamfile == result
assert result.the_file.read() == text + more_text assert result.the_file.read() == text + more_text
# self.assertEqual(result.the_file.content_type, content_type) # assert result.the_file.content_type == content_type
result.the_file.seek(0) result.the_file.seek(0)
assert result.the_file.tell() == 0 assert result.the_file.tell() == 0
assert result.the_file.read(len(text)) == text assert result.the_file.read(len(text)) == text