Merge branch 'dev' of git://github.com/hmarr/mongoengine into dev

This commit is contained in:
Wilson Júnior 2011-08-17 09:24:17 -03:00
commit 4e462ffdb5
7 changed files with 222 additions and 27 deletions

61
AUTHORS
View File

@ -1,3 +1,5 @@
The PRIMARY AUTHORS are (and/or have been):
Harry Marr <harry@hmarr.com> Harry Marr <harry@hmarr.com>
Matt Dennewitz <mattdennewitz@gmail.com> Matt Dennewitz <mattdennewitz@gmail.com>
Deepak Thukral <iapain@yahoo.com> Deepak Thukral <iapain@yahoo.com>
@ -5,3 +7,62 @@ Florian Schlachter <flori@n-schlachter.de>
Steve Challis <steve@stevechallis.com> Steve Challis <steve@stevechallis.com>
Ross Lawley <ross.lawley@gmail.com> Ross Lawley <ross.lawley@gmail.com>
Wilson Júnior <wilsonpjunior@gmail.com> Wilson Júnior <wilsonpjunior@gmail.com>
Dan Crosta https://github.com/dcrosta
CONTRIBUTORS
Dervived from the git logs, inevitably incomplete but all of whom and others
have submitted patches, reported bugs and generally helped make MongoEngine
that much better:
* Harry Marr
* Ross Lawley
* blackbrrr
* Florian Schlachter
* Vincent Driessen
* Steve Challis
* flosch
* Deepak Thukral
* Colin Howe
* Wilson Júnior
* Alistair Roche
* Dan Crosta
* Viktor Kerkez
* Stephan Jaekel
* Rached Ben Mustapha
* Greg Turner
* Daniel Hasselrot
* Mircea Pasoi
* Matt Chisholm
* James Punteney
* TimothéePeignier
* Stuart Rackham
* Serge Matveenko
* Matt Dennewitz
* Don Spaulding
* Ales Zoulek
* sshwsfc
* sib
* Samuel Clay
* Nick Vlku
* martin
* Flavio Amieiro
* Анхбаяр Лхагвадорж
* Zak Johnson
* Victor Farazdagi
* vandersonmota
* Theo Julienne
* sp
* Slavi Pantaleev
* Richard Henry
* Nicolas Perriault
* Nick Vlku Jr
* Michael Henson
* Leo Honkanen
* kuno
* Josh Ourisman
* Jaime
* Igor Ivanov
* Gregg Lind
* Gareth Lloyd
* Albert Choi

View File

@ -5,6 +5,8 @@ Changelog
Changes in dev Changes in dev
============== ==============
- Updated sum / average to use map_reduce as db.eval doesn't work in sharded environments
- Added where() - filter to allowing users to specify query expressions as Javascript
- Added SequenceField - for creating sequential counters - Added SequenceField - for creating sequential counters
- Added update() convenience method to a document - Added update() convenience method to a document
- Added cascading saves - so changes to Referenced documents are saved on .save() - Added cascading saves - so changes to Referenced documents are saved on .save()
@ -28,7 +30,7 @@ Changes in dev
- Added insert method for bulk inserts - Added insert method for bulk inserts
- Added blinker signal support - Added blinker signal support
- Added query_counter context manager for tests - Added query_counter context manager for tests
- Added optional map_reduce method item_frequencies - Added map_reduce method item_frequencies and set as default (as db.eval doesn't work in sharded environments)
- Added inline_map_reduce option to map_reduce - Added inline_map_reduce option to map_reduce
- Updated connection exception so it provides more info on the cause. - Updated connection exception so it provides more info on the cause.
- Added searching multiple levels deep in ``DictField`` - Added searching multiple levels deep in ``DictField``

View File

@ -812,7 +812,8 @@ class BaseDocument(object):
field_cls = field.document_type field_cls = field.document_type
if field_cls in inspected_classes: if field_cls in inspected_classes:
continue continue
geo_indices += field_cls._geo_indices(inspected_classes) if hasattr(field_cls, '_geo_indices'):
geo_indices += field_cls._geo_indices(inspected_classes)
elif field._geo_index: elif field._geo_index:
geo_indices.append(field) geo_indices.append(field)
return geo_indices return geo_indices

View File

@ -620,7 +620,7 @@ class GenericReferenceField(BaseField):
"""A reference to *any* :class:`~mongoengine.document.Document` subclass """A reference to *any* :class:`~mongoengine.document.Document` subclass
that will be automatically dereferenced on access (lazily). that will be automatically dereferenced on access (lazily).
note: Any documents used as a generic reference must be registered in the ..note :: Any documents used as a generic reference must be registered in the
document registry. Importing the model will automatically register it. document registry. Importing the model will automatically register it.
.. versionadded:: 0.3 .. versionadded:: 0.3
@ -925,7 +925,7 @@ class SequenceField(IntField):
return value return value
def __set__(self, instance, value): def __set__(self, instance, value):
if value is None and instance._initialised: if value is None and instance._initialised:
value = self.generate_new_value() value = self.generate_new_value()

View File

@ -1397,22 +1397,45 @@ class QuerySet(object):
db = _get_db() db = _get_db()
return db.eval(code, *fields) return db.eval(code, *fields)
def where(self, where_clause):
"""Filter ``QuerySet`` results with a ``$where`` clause (a Javascript
expression). Performs automatic field name substitution like
:meth:`mongoengine.queryset.Queryset.exec_js`.
.. note:: When using this mode of query, the database will call your
function, or evaluate your predicate clause, for each object
in the collection.
"""
where_clause = self._sub_js_fields(where_clause)
self._where_clause = where_clause
return self
def sum(self, field): def sum(self, field):
"""Sum over the values of the specified field. """Sum over the values of the specified field.
:param field: the field to sum over; use dot-notation to refer to :param field: the field to sum over; use dot-notation to refer to
embedded document fields embedded document fields
""" """
sum_func = """ map_func = pymongo.code.Code("""
function(sumField) { function() {
var total = 0.0; emit(1, this[field] || 0);
db[collection].find(query).forEach(function(doc) {
total += (doc[sumField] || 0.0);
});
return total;
} }
""" """, scope={'field': field})
return self.exec_js(sum_func, field)
reduce_func = pymongo.code.Code("""
function(key, values) {
var sum = 0;
for (var i in values) {
sum += values[i];
}
return sum;
}
""")
for result in self.map_reduce(map_func, reduce_func, output='inline'):
return result.value
else:
return 0
def average(self, field): def average(self, field):
"""Average over the values of the specified field. """Average over the values of the specified field.
@ -1420,22 +1443,38 @@ class QuerySet(object):
:param field: the field to average over; use dot-notation to refer to :param field: the field to average over; use dot-notation to refer to
embedded document fields embedded document fields
""" """
average_func = """ map_func = pymongo.code.Code("""
function(averageField) { function() {
var total = 0.0; if (this.hasOwnProperty(field))
var num = 0; emit(1, {t: this[field] || 0, c: 1});
db[collection].find(query).forEach(function(doc) {
if (doc[averageField] !== undefined) {
total += doc[averageField];
num += 1;
}
});
return total / num;
} }
""" """, scope={'field': field})
return self.exec_js(average_func, field)
def item_frequencies(self, field, normalize=False, map_reduce=False): reduce_func = pymongo.code.Code("""
function(key, values) {
var out = {t: 0, c: 0};
for (var i in values) {
var value = values[i];
out.t += value.t;
out.c += value.c;
}
return out;
}
""")
finalize_func = pymongo.code.Code("""
function(key, value) {
return value.t / value.c;
}
""")
for result in self.map_reduce(map_func, reduce_func, finalize_f=finalize_func, output='inline'):
return result.value
else:
return 0
def item_frequencies(self, field, normalize=False, map_reduce=True):
"""Returns a dictionary of all items present in a field across """Returns a dictionary of all items present in a field across
the whole queried set of documents, and their corresponding frequency. the whole queried set of documents, and their corresponding frequency.
This is useful for generating tag clouds, or searching documents. This is useful for generating tag clouds, or searching documents.

View File

@ -690,6 +690,57 @@ class DocumentTest(unittest.TestCase):
BlogPost.drop_collection() BlogPost.drop_collection()
def test_embedded_document_index(self):
"""Tests settings an index on an embedded document
"""
class Date(EmbeddedDocument):
year = IntField(db_field='yr')
class BlogPost(Document):
title = StringField()
date = EmbeddedDocumentField(Date)
meta = {
'indexes': [
'-date.year'
],
}
BlogPost.drop_collection()
info = BlogPost.objects._collection.index_information()
self.assertEqual(info.keys(), ['_types_1_date.yr_-1', '_id_'])
BlogPost.drop_collection()
def test_list_embedded_document_index(self):
"""Ensure list embedded documents can be indexed
"""
class Tag(EmbeddedDocument):
name = StringField(db_field='tag')
class BlogPost(Document):
title = StringField()
tags = ListField(EmbeddedDocumentField(Tag))
meta = {
'indexes': [
'tags.name'
],
}
BlogPost.drop_collection()
info = BlogPost.objects._collection.index_information()
# we don't use _types in with list fields by default
self.assertEqual(info.keys(), ['_id_', '_types_1', 'tags.tag_1'])
post1 = BlogPost(title="Embedded Indexes tests in place",
tags=[Tag(name="about"), Tag(name="time")]
)
post1.save()
BlogPost.drop_collection()
def test_geo_indexes_recursion(self): def test_geo_indexes_recursion(self):
class User(Document): class User(Document):

View File

@ -2502,6 +2502,47 @@ class QuerySetTest(unittest.TestCase):
for key, value in info.iteritems()] for key, value in info.iteritems()]
self.assertTrue(([('_types', 1), ('message', 1)], False, False) in info) self.assertTrue(([('_types', 1), ('message', 1)], False, False) in info)
def test_where(self):
"""Ensure that where clauses work.
"""
class IntPair(Document):
fielda = IntField()
fieldb = IntField()
IntPair.objects._collection.remove()
a = IntPair(fielda=1, fieldb=1)
b = IntPair(fielda=1, fieldb=2)
c = IntPair(fielda=2, fieldb=1)
a.save()
b.save()
c.save()
query = IntPair.objects.where('this[~fielda] >= this[~fieldb]')
self.assertEqual('this["fielda"] >= this["fieldb"]', query._where_clause)
results = list(query)
self.assertEqual(2, len(results))
self.assertTrue(a in results)
self.assertTrue(c in results)
query = IntPair.objects.where('this[~fielda] == this[~fieldb]')
results = list(query)
self.assertEqual(1, len(results))
self.assertTrue(a in results)
query = IntPair.objects.where('function() { return this[~fielda] >= this[~fieldb] }')
self.assertEqual('function() { return this["fielda"] >= this["fieldb"] }', query._where_clause)
results = list(query)
self.assertEqual(2, len(results))
self.assertTrue(a in results)
self.assertTrue(c in results)
def invalid_where():
list(IntPair.objects.where(fielda__gte=3))
self.assertRaises(TypeError, invalid_where)
class QTest(unittest.TestCase): class QTest(unittest.TestCase):