diff --git a/AUTHORS b/AUTHORS index 7efd3ad2..903542ad 100644 --- a/AUTHORS +++ b/AUTHORS @@ -143,3 +143,4 @@ that much better: * Martin Alderete (https://github.com/malderete) * Nick Joyce * Jared Forsyth + * Kenneth Falck diff --git a/docs/changelog.rst b/docs/changelog.rst index 213e6fba..e9783cd0 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -47,6 +47,7 @@ Changes in 0.8.X - Added the "get_decoded" method to the MongoSession class (#216) - Fixed invalid choices error bubbling (#214) - Updated Save so it calls $set and $unset in a single operation (#211) +- Fixed inner queryset looping (#204) Changes in 0.7.9 ================ diff --git a/mongoengine/queryset/queryset.py b/mongoengine/queryset/queryset.py index d313740b..ba6134fc 100644 --- a/mongoengine/queryset/queryset.py +++ b/mongoengine/queryset/queryset.py @@ -112,8 +112,11 @@ class QuerySet(object): def __iter__(self): """Support iterator protocol""" - self.rewind() - return self + queryset = self + if queryset._iter: + queryset = self.clone() + queryset.rewind() + return queryset def __len__(self): return self.count() @@ -159,7 +162,6 @@ class QuerySet(object): .. versionchanged:: 0.6.13 Now doesnt modify the cursor """ - if self._iter: return '.. queryset mid-iteration ..' @@ -537,7 +539,7 @@ class QuerySet(object): c._cursor_obj = self._cursor_obj.clone() if self._slice: - c._cursor_obj[self._slice] + c._cursor[self._slice] return c diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index 3d5c659c..d5e80be5 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -3125,5 +3125,46 @@ class QuerySetTest(unittest.TestCase): Organization)) self.assertTrue(isinstance(qs.first().organization, Organization)) + def test_nested_queryset_iterator(self): + # Try iterating the same queryset twice, nested. + names = ['Alice', 'Bob', 'Chuck', 'David', 'Eric', 'Francis', 'George'] + + class User(Document): + name = StringField() + + def __unicode__(self): + return self.name + + User.drop_collection() + + for name in names: + User(name=name).save() + + users = User.objects.all().order_by('name') + + outer_count = 0 + inner_count = 0 + inner_total_count = 0 + + self.assertEqual(len(users), 7) + + for i, outer_user in enumerate(users): + self.assertEqual(outer_user.name, names[i]) + outer_count += 1 + inner_count = 0 + + # Calling len might disrupt the inner loop if there are bugs + self.assertEqual(len(users), 7) + + for j, inner_user in enumerate(users): + self.assertEqual(inner_user.name, names[j]) + inner_count += 1 + inner_total_count += 1 + + self.assertEqual(inner_count, 7) # inner loop should always be executed seven times + + self.assertEqual(outer_count, 7) # outer loop should be executed seven times total + self.assertEqual(inner_total_count, 7 * 7) # inner loop should be executed fourtynine times total + if __name__ == '__main__': unittest.main() diff --git a/tests/test_django.py b/tests/test_django.py index 563f4074..dceeba27 100644 --- a/tests/test_django.py +++ b/tests/test_django.py @@ -136,7 +136,25 @@ class QuerySetTest(unittest.TestCase): start = end - 1 self.assertEqual(t.render(Context(d)), u'%d:%d:' % (start, end)) + def test_nested_queryset_template_iterator(self): + # Try iterating the same queryset twice, nested, in a Django template. + names = ['A', 'B', 'C', 'D'] + class User(Document): + name = StringField() + + def __unicode__(self): + return self.name + + User.drop_collection() + + for name in names: + User(name=name).save() + + users = User.objects.all().order_by('name') + template = Template("{% for user in users %}{{ user.name }}{% ifequal forloop.counter 2 %} {% for inner_user in users %}{{ inner_user.name }}{% endfor %} {% endifequal %}{% endfor %}") + rendered = template.render(Context({'users': users})) + self.assertEqual(rendered, 'AB ABCD CD') class MongoDBSessionTest(SessionTestsMixin, unittest.TestCase): backend = SessionStore