Skip to content

Commit c22eb34

Browse files
committed
Merge branch 'master' of github.com:MongoEngine/mongoengine into release_0_19_0
2 parents d44533d + dcf3edb commit c22eb34

File tree

6 files changed

+101
-19
lines changed

6 files changed

+101
-19
lines changed

docs/guide/defining-documents.rst

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,7 @@ Document inheritance
744744

745745
To create a specialised type of a :class:`~mongoengine.Document` you have
746746
defined, you may subclass it and add any extra fields or methods you may need.
747-
As this is new class is not a direct subclass of
747+
As this new class is not a direct subclass of
748748
:class:`~mongoengine.Document`, it will not be stored in its own collection; it
749749
will use the same collection as its superclass uses. This allows for more
750750
convenient and efficient retrieval of related documents -- all you need do is
@@ -767,6 +767,27 @@ document.::
767767
Setting :attr:`allow_inheritance` to True should also be used in
768768
:class:`~mongoengine.EmbeddedDocument` class in case you need to subclass it
769769

770+
When it comes to querying using :attr:`.objects()`, querying `Page.objects()` will query
771+
both `Page` and `DatedPage` whereas querying `DatedPage` will only query the `DatedPage` documents.
772+
Behind the scenes, MongoEngine deals with inheritance by adding a :attr:`_cls` attribute that contains
773+
the class name in every documents. When a document is loaded, MongoEngine checks
774+
it's :attr:`_cls` attribute and use that class to construct the instance.::
775+
776+
Page(title='a funky title').save()
777+
DatedPage(title='another title', date=datetime.utcnow()).save()
778+
779+
print(Page.objects().count()) # 2
780+
print(DatedPage.objects().count()) # 1
781+
782+
# print documents in their native form
783+
# we remove 'id' to avoid polluting the output with unnecessary detail
784+
qs = Page.objects.exclude('id').as_pymongo()
785+
print(list(qs))
786+
# [
787+
# {'_cls': u 'Page', 'title': 'a funky title'},
788+
# {'_cls': u 'Page.DatedPage', 'title': u 'another title', 'date': datetime.datetime(2019, 12, 13, 20, 16, 59, 993000)}
789+
# ]
790+
770791
Working with existing data
771792
--------------------------
772793
As MongoEngine no longer defaults to needing :attr:`_cls`, you can quickly and

docs/guide/mongomock.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
Use mongomock for testing
33
==============================
44

5-
`mongomock <https://github.com/vmalloc/mongomock/>`_ is a package to do just
5+
`mongomock <https://github.com/vmalloc/mongomock/>`_ is a package to do just
66
what the name implies, mocking a mongo database.
77

8-
To use with mongoengine, simply specify mongomock when connecting with
8+
To use with mongoengine, simply specify mongomock when connecting with
99
mongoengine:
1010

1111
.. code-block:: python
@@ -21,7 +21,7 @@ or with an alias:
2121
conn = get_connection('testdb')
2222
2323
Example of test file:
24-
--------
24+
---------------------
2525
.. code-block:: python
2626
2727
import unittest
@@ -45,4 +45,4 @@ Example of test file:
4545
pers.save()
4646
4747
fresh_pers = Person.objects().first()
48-
self.assertEqual(fresh_pers.name, 'John')
48+
assert fresh_pers.name == 'John'

docs/guide/querying.rst

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,18 @@ keyword argument::
222222

223223
.. versionadded:: 0.4
224224

225+
Sorting/Ordering results
226+
========================
227+
It is possible to order the results by 1 or more keys using :meth:`~mongoengine.queryset.QuerySet.order_by`.
228+
The order may be specified by prepending each of the keys by "+" or "-". Ascending order is assumed if there's no prefix.::
229+
230+
# Order by ascending date
231+
blogs = BlogPost.objects().order_by('date') # equivalent to .order_by('+date')
232+
233+
# Order by ascending date first, then descending title
234+
blogs = BlogPost.objects().order_by('+date', '-title')
235+
236+
225237
Limiting and skipping results
226238
=============================
227239
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`::
585597
['database', 'mongodb']
586598

587599
From MongoDB version 2.6, push operator supports $position value which allows
588-
to push values with index.
600+
to push values with index::
601+
589602
>>> post = BlogPost(title="Test", tags=["mongo"])
590603
>>> post.save()
591604
>>> post.update(push__tags__0=["database", "code"])

mongoengine/base/datastructures.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ def __init__(self, list_items, instance, name):
120120
super(BaseList, self).__init__(list_items)
121121

122122
def __getitem__(self, key):
123+
# change index to positive value because MongoDB does not support negative one
124+
if isinstance(key, int) and key < 0:
125+
key = len(self) + key
123126
value = super(BaseList, self).__getitem__(key)
124127

125128
if isinstance(key, slice):

tests/document/test_instance.py

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
TEST_IMAGE_PATH = os.path.join(os.path.dirname(__file__), "../fields/mongoengine.png")
4242

4343

44-
class TestInstance(MongoDBTestCase):
44+
class TestDocumentInstance(MongoDBTestCase):
4545
def setUp(self):
4646
class Job(EmbeddedDocument):
4747
name = StringField()
@@ -3338,19 +3338,19 @@ class Person(Document):
33383338

33393339
# worker1.job should be equal to the job used originally to create the
33403340
# document.
3341-
self.assertEqual(worker1.job, worker.job)
3341+
assert worker1.job == worker.job
33423342

33433343
# worker1.job should be equal to a newly created Job EmbeddedDocument
33443344
# using either the Boss object or his ID.
3345-
self.assertEqual(worker1.job, Job(boss=boss, boss_dbref=boss))
3346-
self.assertEqual(worker1.job, Job(boss=boss.id, boss_dbref=boss.id))
3345+
assert worker1.job == Job(boss=boss, boss_dbref=boss)
3346+
assert worker1.job == Job(boss=boss.id, boss_dbref=boss.id)
33473347

33483348
# The above equalities should also hold after worker1.job.boss has been
33493349
# fetch()ed.
33503350
worker1.job.boss.fetch()
3351-
self.assertEqual(worker1.job, worker.job)
3352-
self.assertEqual(worker1.job, Job(boss=boss, boss_dbref=boss))
3353-
self.assertEqual(worker1.job, Job(boss=boss.id, boss_dbref=boss.id))
3351+
assert worker1.job == worker.job
3352+
assert worker1.job == Job(boss=boss, boss_dbref=boss)
3353+
assert worker1.job == Job(boss=boss.id, boss_dbref=boss.id)
33543354

33553355
def test_dbref_equality(self):
33563356
class Test2(Document):
@@ -3617,6 +3617,51 @@ class A(Document):
36173617
assert b._instance == a
36183618
assert idx == 2
36193619

3620+
def test_updating_listfield_manipulate_list(self):
3621+
class Company(Document):
3622+
name = StringField()
3623+
employees = ListField(field=DictField())
3624+
3625+
Company.drop_collection()
3626+
3627+
comp = Company(name="BigBank", employees=[{"name": "John"}])
3628+
comp.save()
3629+
comp.employees.append({"name": "Bill"})
3630+
comp.save()
3631+
3632+
stored_comp = get_as_pymongo(comp)
3633+
self.assertEqual(
3634+
stored_comp,
3635+
{
3636+
"_id": comp.id,
3637+
"employees": [{"name": "John"}, {"name": "Bill"}],
3638+
"name": "BigBank",
3639+
},
3640+
)
3641+
3642+
comp = comp.reload()
3643+
comp.employees[0]["color"] = "red"
3644+
comp.employees[-1]["color"] = "blue"
3645+
comp.employees[-1].update({"size": "xl"})
3646+
comp.save()
3647+
3648+
assert len(comp.employees) == 2
3649+
assert comp.employees[0] == {"name": "John", "color": "red"}
3650+
assert comp.employees[1] == {"name": "Bill", "size": "xl", "color": "blue"}
3651+
3652+
stored_comp = get_as_pymongo(comp)
3653+
self.assertEqual(
3654+
stored_comp,
3655+
{
3656+
"_id": comp.id,
3657+
"employees": [
3658+
{"name": "John", "color": "red"},
3659+
{"size": "xl", "color": "blue", "name": "Bill"},
3660+
],
3661+
"name": "BigBank",
3662+
},
3663+
)
3664+
36203665
def test_falsey_pk(self):
36213666
"""Ensure that we can create and update a document with Falsey PK."""
36223667

@@ -3693,13 +3738,13 @@ class Jedi(Document):
36933738
value = u"I_should_be_a_dict"
36943739
coll.insert_one({"light_saber": value})
36953740

3696-
with self.assertRaises(InvalidDocumentError) as cm:
3741+
with pytest.raises(InvalidDocumentError) as exc_info:
36973742
list(Jedi.objects)
36983743

3699-
self.assertEqual(
3700-
str(cm.exception),
3701-
"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"
3702-
% type(value),
3744+
assert str(
3745+
exc_info.value
3746+
) == "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(
3747+
value
37033748
)
37043749

37053750

tests/fields/test_file_field.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ class StreamFile(Document):
151151
result = StreamFile.objects.first()
152152
assert streamfile == result
153153
assert result.the_file.read() == text + more_text
154-
# self.assertEqual(result.the_file.content_type, content_type)
154+
# assert result.the_file.content_type == content_type
155155
result.the_file.seek(0)
156156
assert result.the_file.tell() == 0
157157
assert result.the_file.read(len(text)) == text

0 commit comments

Comments
 (0)