From 682326c130e396e617f3a3124cd0bcfadcc532a1 Mon Sep 17 00:00:00 2001 From: Florian Schlachter Date: Fri, 30 Apr 2010 18:04:58 +0200 Subject: [PATCH 1/7] documentation bug fixed --- docs/guide/querying.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/querying.rst b/docs/guide/querying.rst index 4447f7da..690569a9 100644 --- a/docs/guide/querying.rst +++ b/docs/guide/querying.rst @@ -172,7 +172,7 @@ custom manager methods as you like:: @queryset_manager def live_posts(doc_cls, queryset): - return queryset.filter(published=True) + return queryset(published=True).filter(published=True) BlogPost(title='test1', published=False).save() BlogPost(title='test2', published=True).save() From 9df725165beca247340fcee7f2858fdaa64107a6 Mon Sep 17 00:00:00 2001 From: Florian Schlachter Date: Fri, 14 May 2010 13:35:45 +0200 Subject: [PATCH 2/7] =?UTF-8?q?Added=20a=20possibility=20to=20define=20a?= =?UTF-8?q?=20base=20class=20for=20fields=20from=20a=20DictField=20(instea?= =?UTF-8?q?d=20of=20using=20BaseField).=20This=20is=20important=20if=20you?= =?UTF-8?q?=20want=20to=20use=20field-based=20query=20abilities=20like=20S?= =?UTF-8?q?tringField's=20startswith/endswith/contains.=20Just=20define=20?= =?UTF-8?q?`basecls=C2=B4=20when=20defining=20your=20DictField.=20Example:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit class Test(Document): name = StringField() translations = DictField(basecls=StringField) Without basecls defined: > Test.objects(translations__german__startswith='Deutsch') [] With basecls set to StringField: > Test.objects(translations__german__startswith='Deutsch') [] --- mongoengine/fields.py | 7 ++++++- mongoengine/queryset.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 55e5addb..75008d6f 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -348,6 +348,11 @@ class DictField(BaseField): .. versionadded:: 0.3 """ + def __init__(self, basecls=None, *args, **kwargs): + self.basecls = basecls or BaseField + assert issubclass(self.basecls, BaseField) + super(DictField, self).__init__(*args, **kwargs) + def validate(self, value): """Make sure that a list of valid fields is being used. """ @@ -360,7 +365,7 @@ class DictField(BaseField): 'contain "." or "$" characters') def lookup_member(self, member_name): - return BaseField(db_field=member_name) + return self.basecls(db_field=member_name) class GeoLocationField(DictField): """Supports geobased fields""" diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index 396a745c..11b5321a 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -313,7 +313,7 @@ class QuerySet(object): op = None if parts[-1] in operators + match_operators: op = parts.pop() - + if _doc_cls: # Switch field names to proper names [set in Field(name='foo')] fields = QuerySet._lookup_field(_doc_cls, parts) From 11c7a15067b1095f68fb045fd878fdcf4e84c1b7 Mon Sep 17 00:00:00 2001 From: Florian Schlachter Date: Fri, 14 May 2010 13:49:13 +0200 Subject: [PATCH 3/7] Added test for DictField's basecls. --- tests/queryset.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/queryset.py b/tests/queryset.py index aba3bc7d..c43b8f11 100644 --- a/tests/queryset.py +++ b/tests/queryset.py @@ -1063,6 +1063,29 @@ class QuerySetTest(unittest.TestCase): BlogPost.drop_collection() + def test_dict_with_custom_baseclass(self): + """Ensure DictField working with custom base clases. + """ + class Test(Document): + testdict = DictField() + + t = Test(testdict={'f': 'Value'}) + t.save() + + self.assertEqual(len(Test.objects(testdict__f__startswith='Val')), 0) + self.assertEqual(len(Test.objects(testdict__f='Value')), 1) + Test.drop_collection() + + class Test(Document): + testdict = DictField(basecls=StringField) + + t = Test(testdict={'f': 'Value'}) + t.save() + + self.assertEqual(len(Test.objects(testdict__f='Value')), 1) + self.assertEqual(len(Test.objects(testdict__f__startswith='Val')), 1) + Test.drop_collection() + def test_bulk(self): """Ensure bulk querying by object id returns a proper dict. """ From 4972bdb3834e0b5db1c4f20cf4bf0082a813ab76 Mon Sep 17 00:00:00 2001 From: Stephan Jaekel Date: Fri, 14 May 2010 14:02:39 +0200 Subject: [PATCH 4/7] ignore empty Q objects when combining Q objects. --- mongoengine/queryset.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index 11b5321a..3db23baa 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -59,8 +59,10 @@ class Q(object): def _combine(self, other, op): obj = Q() - obj.query = ['('] + copy.deepcopy(self.query) + [op] - obj.query += copy.deepcopy(other.query) + [')'] + if self.query[0]: + obj.query = ['('] + copy.deepcopy(self.query) + [op] + copy.deepcopy(other.query) + [')'] + else: + obj.query = copy.deepcopy(other.query) return obj def __or__(self, other): From 225972e151b8bead6842c6be4205f9e2771b2c3a Mon Sep 17 00:00:00 2001 From: Stephan Jaekel Date: Fri, 14 May 2010 14:03:18 +0200 Subject: [PATCH 5/7] Added some handy shortcuts for django users. --- mongoengine/django/shortcuts.py | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 mongoengine/django/shortcuts.py diff --git a/mongoengine/django/shortcuts.py b/mongoengine/django/shortcuts.py new file mode 100644 index 00000000..29bc17a8 --- /dev/null +++ b/mongoengine/django/shortcuts.py @@ -0,0 +1,45 @@ +from django.http import Http404 +from mongoengine.queryset import QuerySet +from mongoengine.base import BaseDocument + +def _get_queryset(cls): + """Inspired by django.shortcuts.*""" + if isinstance(cls, QuerySet): + return cls + else: + return cls.objects + +def get_document_or_404(cls, *args, **kwargs): + """ + Uses get() to return an document, or raises a Http404 exception if the document + does not exist. + + cls may be a Document or QuerySet object. All other passed + arguments and keyword arguments are used in the get() query. + + Note: Like with get(), an MultipleObjectsReturned will be raised if more than one + object is found. + + Inspired by django.shortcuts.* + """ + queryset = _get_queryset(cls) + try: + return queryset.get(*args, **kwargs) + except queryset._document.DoesNotExist: + raise Http404('No %s matches the given query.' % queryset._document._class_name) + +def get_list_or_404(cls, *args, **kwargs): + """ + Uses filter() to return a list of documents, or raise a Http404 exception if + the list is empty. + + cls may be a Document or QuerySet object. All other passed + arguments and keyword arguments are used in the filter() query. + + Inspired by django.shortcuts.* + """ + queryset = _get_queryset(cls) + obj_list = list(queryset.filter(*args, **kwargs)) + if not obj_list: + raise Http404('No %s matches the given query.' % queryset._document._class_name) + return obj_list From 88da9985327e7b664b844a28039e8ed37e5c1d2a Mon Sep 17 00:00:00 2001 From: Stephan Jaekel Date: Fri, 14 May 2010 14:21:58 +0200 Subject: [PATCH 6/7] added test for empty Q objects --- tests/queryset.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/queryset.py b/tests/queryset.py index aba3bc7d..f184bb0d 100644 --- a/tests/queryset.py +++ b/tests/queryset.py @@ -1136,5 +1136,18 @@ class QTest(unittest.TestCase): self.assertEqual(q._item_query_as_js(item, test_scope, 0), js) self.assertEqual(scope, test_scope) + def test_empty_q(self): + """Ensure that starting with an empty Q object won't hurt. + """ + q1 = Q() + q2 = Q(age__gte=18) + q3 = Q(name='test') + + query = ['(', {'age__gte': 18}, '||', {'name': 'test'}, ')'] + self.assertEqual((q1 | q2 | q3).query, query) + + query = ['(', {'age__gte': 18}, '&&', {'name': 'test'}, ')'] + self.assertEqual((q1 & q2 & q3).query, query) + if __name__ == '__main__': unittest.main() From f657432be3d33fda30a88d04997a4680a591cee7 Mon Sep 17 00:00:00 2001 From: Stephan Jaekel Date: Fri, 14 May 2010 14:35:27 +0200 Subject: [PATCH 7/7] always ignore empty Q objects, if the new Q is empty, the old one will be returned. --- mongoengine/queryset.py | 2 ++ tests/queryset.py | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index 3db23baa..72a474d7 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -59,6 +59,8 @@ class Q(object): def _combine(self, other, op): obj = Q() + if not other.query[0]: + return self if self.query[0]: obj.query = ['('] + copy.deepcopy(self.query) + [op] + copy.deepcopy(other.query) + [')'] else: diff --git a/tests/queryset.py b/tests/queryset.py index df2e2e74..9daa73ec 100644 --- a/tests/queryset.py +++ b/tests/queryset.py @@ -1160,17 +1160,19 @@ class QTest(unittest.TestCase): self.assertEqual(scope, test_scope) def test_empty_q(self): - """Ensure that starting with an empty Q object won't hurt. + """Ensure that empty Q objects won't hurt. """ q1 = Q() q2 = Q(age__gte=18) - q3 = Q(name='test') + q3 = Q() + q4 = Q(name='test') + q5 = Q() query = ['(', {'age__gte': 18}, '||', {'name': 'test'}, ')'] - self.assertEqual((q1 | q2 | q3).query, query) + self.assertEqual((q1 | q2 | q3 | q4 | q5).query, query) query = ['(', {'age__gte': 18}, '&&', {'name': 'test'}, ')'] - self.assertEqual((q1 & q2 & q3).query, query) + self.assertEqual((q1 & q2 & q3 & q4 & q5).query, query) if __name__ == '__main__': unittest.main()