[1.5.x] Fixed #20094 - Be more careful when checking for Iterator

Python 2.6 has some different behaviour when checking
isinstance(foo, collections.Iterator).
Backport of 829dc3c5 from master.
This commit is contained in:
Marc Tamlyn 2013-03-20 10:47:56 +00:00 committed by Claude Paroz
parent 7206330c15
commit dd897e4eeb
6 changed files with 43 additions and 6 deletions

View File

@ -1,6 +1,5 @@
from __future__ import unicode_literals
import collections
import copy
import datetime
import decimal
@ -16,6 +15,7 @@ from django.core import exceptions, validators
from django.utils.datastructures import DictWrapper
from django.utils.dateparse import parse_date, parse_datetime, parse_time
from django.utils.functional import curry, total_ordering
from django.utils.itercompat import is_iterator
from django.utils.text import capfirst
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
@ -444,7 +444,7 @@ class Field(object):
return bound_field_class(self, fieldmapping, original)
def _get_choices(self):
if isinstance(self._choices, collections.Iterator):
if is_iterator(self._choices):
choices, self._choices = tee(self._choices)
return choices
else:

View File

@ -4,7 +4,6 @@ Code to manage the creation and SQL rendering of 'where' constraints.
from __future__ import absolute_import
import collections
import datetime
from itertools import repeat
@ -12,6 +11,7 @@ from django.utils import tree
from django.db.models.fields import Field
from django.db.models.sql.datastructures import EmptyResultSet
from django.db.models.sql.aggregates import Aggregate
from django.utils.itercompat import is_iterator
from django.utils.six.moves import xrange
# Connection types
@ -51,7 +51,7 @@ class WhereNode(tree.Node):
return
obj, lookup_type, value = data
if isinstance(value, collections.Iterator):
if is_iterator(value):
# Consume any generators immediately, so that we can determine
# emptiness and transform any non-empty values correctly.
value = list(value)

View File

@ -4,10 +4,12 @@ Where possible, we try to use the system-native version and only fall back to
these implementations if necessary.
"""
from django.utils.six.moves import builtins
import collections
import itertools
import sys
import warnings
def is_iterable(x):
"A implementation independent way of checking for iterables"
try:
@ -17,6 +19,17 @@ def is_iterable(x):
else:
return True
def is_iterator(x):
"""An implementation independent way of checking for iterators
Python 2.6 has a different implementation of collections.Iterator which
accepts anything with a `next` method. 2.7+ requires and `__iter__` method
as well.
"""
if sys.version_info >= (2, 7):
return isinstance(x, collections.Iterator)
return isinstance(x, collections.Iterator) and hasattr(x, '__iter__')
def product(*args, **kwds):
warnings.warn("django.utils.itercompat.product is deprecated; use the native version instead",
PendingDeprecationWarning)

View File

@ -0,0 +1,11 @@
from django.test import TestCase
from .models import Category, Thing
class TestIsIterator(TestCase):
def test_regression(self):
"""This failed on Django 1.5/Py2.6 because category has a next method."""
category = Category.objects.create(name='category')
Thing.objects.create(category=category)
Thing.objects.filter(category=category)

View File

@ -1 +1,13 @@
# Test runner needs a models.py file.
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100)
def next(self):
return self
class Thing(models.Model):
name = models.CharField(max_length=100)
category = models.ForeignKey(Category)

View File

@ -19,6 +19,7 @@ from .functional import FunctionalTestCase
from .html import TestUtilsHtml
from .http import TestUtilsHttp, ETagProcessingTests, HttpDateProcessingTests
from .ipv6 import TestUtilsIPv6
from .itercompat import TestIsIterator
from .jslex import JsToCForGettextTest, JsTokensTest
from .module_loading import CustomLoader, DefaultLoader, EggLoader
from .numberformat import TestNumberFormat