diff --git a/django/core/mail/message.py b/django/core/mail/message.py index 98ab3c90753..db9023a0bb2 100644 --- a/django/core/mail/message.py +++ b/django/core/mail/message.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals import mimetypes import os import random -import sys import time from email import charset as Charset, encoders as Encoders from email.generator import Generator @@ -139,9 +138,6 @@ class SafeMIMEText(MIMEText): """ fp = six.StringIO() g = Generator(fp, mangle_from_ = False) - if sys.version_info < (2, 6, 6) and isinstance(self._payload, six.text_type): - # Workaround for http://bugs.python.org/issue1368247 - self._payload = self._payload.encode(self._charset.output_charset) g.flatten(self, unixfrom=unixfrom) return fp.getvalue() diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index a7e0b52e876..ddc76fccf75 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import collections import copy import datetime import decimal @@ -17,7 +18,6 @@ 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 _ @@ -585,7 +585,7 @@ class Field(object): return bound_field_class(self, fieldmapping, original) def _get_choices(self): - if is_iterator(self._choices): + if isinstance(self._choices, collections.Iterator): choices, self._choices = tee(self._choices) return choices else: diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py index 024b995c99b..2e83ecdce48 100644 --- a/django/db/models/sql/where.py +++ b/django/db/models/sql/where.py @@ -4,6 +4,7 @@ Code to manage the creation and SQL rendering of 'where' constraints. from __future__ import absolute_import +import collections import datetime from itertools import repeat @@ -11,7 +12,6 @@ from django.conf import settings from django.db.models.fields import DateTimeField, Field from django.db.models.sql.datastructures import EmptyResultSet, Empty from django.db.models.sql.aggregates import Aggregate -from django.utils.itercompat import is_iterator from django.utils.six.moves import xrange from django.utils import timezone from django.utils import tree @@ -61,7 +61,7 @@ class WhereNode(tree.Node): if not isinstance(data, (list, tuple)): return data obj, lookup_type, value = data - if is_iterator(value): + if isinstance(value, collections.Iterator): # Consume any generators immediately, so that we can determine # emptiness and transform any non-empty values correctly. value = list(value) diff --git a/django/test/testcases.py b/django/test/testcases.py index 04d044de270..d0442360ac7 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -8,7 +8,6 @@ import json import os import re import sys -import select import socket import threading import unittest @@ -924,104 +923,6 @@ class QuietWSGIRequestHandler(WSGIRequestHandler): pass -if sys.version_info >= (3, 3, 0): - _ImprovedEvent = threading.Event -elif sys.version_info >= (2, 7, 0): - _ImprovedEvent = threading._Event -else: - class _ImprovedEvent(threading._Event): - """ - Does the same as `threading.Event` except it overrides the wait() method - with some code borrowed from Python 2.7 to return the set state of the - event (see: http://hg.python.org/cpython/rev/b5aa8aa78c0f/). This allows - to know whether the wait() method exited normally or because of the - timeout. This class can be removed when Django supports only Python >= 2.7. - """ - - def wait(self, timeout=None): - self._Event__cond.acquire() - try: - if not self._Event__flag: - self._Event__cond.wait(timeout) - return self._Event__flag - finally: - self._Event__cond.release() - - -class StoppableWSGIServer(WSGIServer): - """ - The code in this class is borrowed from the `SocketServer.BaseServer` class - in Python 2.6. The important functionality here is that the server is non- - blocking and that it can be shut down at any moment. This is made possible - by the server regularly polling the socket and checking if it has been - asked to stop. - Note for the future: Once Django stops supporting Python 2.6, this class - can be removed as `WSGIServer` will have this ability to shutdown on - demand and will not require the use of the _ImprovedEvent class whose code - is borrowed from Python 2.7. - """ - - def __init__(self, *args, **kwargs): - super(StoppableWSGIServer, self).__init__(*args, **kwargs) - self.__is_shut_down = _ImprovedEvent() - self.__serving = False - - def serve_forever(self, poll_interval=0.5): - """ - Handle one request at a time until shutdown. - - Polls for shutdown every poll_interval seconds. - """ - self.__serving = True - self.__is_shut_down.clear() - while self.__serving: - r, w, e = select.select([self], [], [], poll_interval) - if r: - self._handle_request_noblock() - self.__is_shut_down.set() - - def shutdown(self): - """ - Stops the serve_forever loop. - - Blocks until the loop has finished. This must be called while - serve_forever() is running in another thread, or it will - deadlock. - """ - self.__serving = False - if not self.__is_shut_down.wait(2): - raise RuntimeError( - "Failed to shutdown the live test server in 2 seconds. The " - "server might be stuck or generating a slow response.") - - def handle_request(self): - """Handle one request, possibly blocking. - """ - fd_sets = select.select([self], [], [], None) - if not fd_sets[0]: - return - self._handle_request_noblock() - - def _handle_request_noblock(self): - """ - Handle one request, without blocking. - - I assume that select.select has returned that the socket is - readable before this function was called, so there should be - no risk of blocking in get_request(). - """ - try: - request, client_address = self.get_request() - except socket.error: - return - if self.verify_request(request, client_address): - try: - self.process_request(request, client_address) - except Exception: - self.handle_error(request, client_address) - self.close_request(request) - - class _MediaFilesHandler(StaticFilesHandler): """ Handler for serving the media files. This is a private class that is @@ -1071,7 +972,7 @@ class LiveServerThread(threading.Thread): # one that is free to use for the WSGI server. for index, port in enumerate(self.possible_ports): try: - self.httpd = StoppableWSGIServer( + self.httpd = WSGIServer( (self.host, port), QuietWSGIRequestHandler) except WSGIServerException as e: if (index + 1 < len(self.possible_ports) and diff --git a/django/utils/functional.py b/django/utils/functional.py index 93befcf59e7..d5d52205d72 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -398,9 +398,8 @@ def partition(predicate, values): if sys.version_info >= (2, 7, 2): from functools import total_ordering else: - # For Python < 2.7.2. Python 2.6 does not have total_ordering, and - # total_ordering in 2.7 versions prior to 2.7.2 is buggy. See - # http://bugs.python.org/issue10042 for details. For these versions use + # For Python < 2.7.2. total_ordering in versions prior to 2.7.2 is buggy. + # See http://bugs.python.org/issue10042 for details. For these versions use # code borrowed from Python 2.7.3. def total_ordering(cls): """Class decorator that fills in missing ordering methods""" diff --git a/django/utils/itercompat.py b/django/utils/itercompat.py index 1df1e57cb2c..096f8086714 100644 --- a/django/utils/itercompat.py +++ b/django/utils/itercompat.py @@ -4,9 +4,6 @@ Where possible, we try to use the system-native version and only fall back to these implementations if necessary. """ -import collections -import sys - def is_iterable(x): "A implementation independent way of checking for iterables" @@ -16,14 +13,3 @@ def is_iterable(x): return False 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__') diff --git a/docs/howto/deployment/checklist.txt b/docs/howto/deployment/checklist.txt index 1a235673e86..7d382401a06 100644 --- a/docs/howto/deployment/checklist.txt +++ b/docs/howto/deployment/checklist.txt @@ -217,9 +217,9 @@ This setting is required if you're using the :ttag:`ssi` template tag. Python Options ============== -If you're using Python 2.6.8+, it's strongly recommended that you invoke the -Python process running your Django application using the `-R`_ option or with -the :envvar:`PYTHONHASHSEED` environment variable set to ``random``. +It's strongly recommended that you invoke the Python process running your +Django application using the `-R`_ option or with the +:envvar:`PYTHONHASHSEED` environment variable set to ``random``. These options help protect your site from denial-of-service (DoS) attacks triggered by carefully crafted inputs. Such an attack can diff --git a/docs/howto/jython.txt b/docs/howto/jython.txt index 461a5d3804a..96a3d642512 100644 --- a/docs/howto/jython.txt +++ b/docs/howto/jython.txt @@ -4,13 +4,6 @@ Running Django on Jython .. index:: Jython, Java, JVM -.. admonition:: Python 2.6 support - - Django 1.5 has dropped support for Python 2.5. Therefore, you have to use - a Jython 2.7 alpha release if you want to use Django 1.5 with Jython. - Please use Django 1.4 if you want to keep using Django on a stable Jython - version. - Jython_ is an implementation of Python that runs on the Java platform (JVM). Django runs cleanly on Jython version 2.5 or later, which means you can deploy Django on any Java platform. @@ -22,7 +15,7 @@ This document will get you up and running with Django on top of Jython. Installing Jython ================= -Django works with Jython versions 2.5b3 and higher. Download Jython at +Django works with Jython versions 2.5b3 and higher. Download Jython at http://www.jython.org/. Creating a servlet container diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt index 879cda913a9..7fa1ffc8d9e 100644 --- a/docs/intro/reusable-apps.txt +++ b/docs/intro/reusable-apps.txt @@ -255,9 +255,7 @@ working. We'll now fix this by installing our new ``django-polls`` package. installs have a lot of advantages over installing the package system-wide, such as being usable on systems where you don't have administrator access as well as preventing the package from affecting system services and other - users of the machine. Python 2.6 added support for user libraries, so if - you are using an older version this won't work, but Django 1.5 requires - Python 2.6 or newer anyway. + users of the machine. Note that per-user installations can still affect the behavior of system tools that run as that user, so ``virtualenv`` is a more robust solution diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index f3defb37a3b..296150334b8 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -14,6 +14,19 @@ deprecation process for some features`_. .. _`backwards incompatible changes`: `Backwards incompatible changes in 1.7`_ .. _`begun the deprecation process for some features`: `Features deprecated in 1.7`_ +Python compatibility +==================== + +Django 1.7 requires Python 2.7 or above, though we **highly recommend** +the latest minor release. Support for Python 2.6 has been dropped. + +This change should affect only a small number of Django users, as most +operating-system vendors today are shipping Python 2.7 or newer as their default +version. If you're still using Python 2.6, however, you'll need to stick to +Django 1.6 until you can upgrade your Python version. Per :doc:`our support +policy `, Django 1.6 will continue to receive +security support until the release of Django 1.8. + What's new in Django 1.7 ======================== diff --git a/tests/logging_tests/tests.py b/tests/logging_tests/tests.py index cae2860306f..8ba4702aba7 100644 --- a/tests/logging_tests/tests.py +++ b/tests/logging_tests/tests.py @@ -19,7 +19,6 @@ from admin_scripts.tests import AdminScriptTestCase from .logconfig import MyEmailBackend -PYVERS = sys.version_info[:2] # logging config prior to using filter with mail_admins OLD_LOGGING = { @@ -87,7 +86,6 @@ class DefaultLoggingTest(TestCase): self.logger.error("Hey, this is an error.") self.assertEqual(output.getvalue(), 'Hey, this is an error.\n') -@skipUnless(PYVERS > (2,6), "warnings captured only in Python >= 2.7") class WarningLoggerTests(TestCase): """ Tests that warnings output for DeprecationWarnings is enabled diff --git a/tests/queries/tests.py b/tests/queries/tests.py index 6520eb01766..87d54b637e2 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -2148,13 +2148,6 @@ class ConditionalTests(BaseQuerysetTest): t4 = Tag.objects.create(name='t4', parent=t3) t5 = Tag.objects.create(name='t5', parent=t3) - - # In Python 2.6 beta releases, exceptions raised in __len__ are swallowed - # (Python issue 1242657), so these cases return an empty list, rather than - # raising an exception. Not a lot we can do about that, unfortunately, due to - # the way Python handles list() calls internally. Thus, we skip the tests for - # Python 2.6. - @unittest.skipIf(sys.version_info[:2] == (2, 6), "Python version is 2.6") def test_infinite_loop(self): # If you're not careful, it's possible to introduce infinite loops via # default ordering on foreign keys in a cycle. We detect that. diff --git a/tests/select_for_update/tests.py b/tests/select_for_update/tests.py index b8a8ad19bf7..3204d74224d 100644 --- a/tests/select_for_update/tests.py +++ b/tests/select_for_update/tests.py @@ -99,15 +99,8 @@ class SelectForUpdateTests(TransactionTestCase): list(Person.objects.all().select_for_update(nowait=True)) self.assertTrue(self.has_for_update_sql(connection, nowait=True)) - # In Python 2.6 beta and some final releases, exceptions raised in __len__ - # are swallowed (Python issue 1242657), so these cases return an empty - # list, rather than raising an exception. Not a lot we can do about that, - # unfortunately, due to the way Python handles list() calls internally. - # Python 2.6.1 is the "in the wild" version affected by this, so we skip - # the test for that version. @requires_threading @skipUnlessDBFeature('has_select_for_update_nowait') - @unittest.skipIf(sys.version_info[:3] == (2, 6, 1), "Python version is 2.6.1") def test_nowait_raises_error_on_block(self): """ If nowait is specified, we expect an error to be raised rather @@ -128,15 +121,8 @@ class SelectForUpdateTests(TransactionTestCase): self.end_blocking_transaction() self.assertIsInstance(status[-1], DatabaseError) - # In Python 2.6 beta and some final releases, exceptions raised in __len__ - # are swallowed (Python issue 1242657), so these cases return an empty - # list, rather than raising an exception. Not a lot we can do about that, - # unfortunately, due to the way Python handles list() calls internally. - # Python 2.6.1 is the "in the wild" version affected by this, so we skip - # the test for that version. @skipIfDBFeature('has_select_for_update_nowait') @skipUnlessDBFeature('has_select_for_update') - @unittest.skipIf(sys.version_info[:3] == (2, 6, 1), "Python version is 2.6.1") def test_unsupported_nowait_raises_error(self): """ If a SELECT...FOR UPDATE NOWAIT is run on a database backend diff --git a/tests/test_runner/tests.py b/tests/test_runner/tests.py index 7765f973ab4..72200e40d36 100644 --- a/tests/test_runner/tests.py +++ b/tests/test_runner/tests.py @@ -367,8 +367,6 @@ class DeprecationDisplayTest(AdminScriptTestCase): self.assertIn("DeprecationWarning: warning from test", err) self.assertIn("DeprecationWarning: module-level warning from deprecation_app", err) - @unittest.skipIf(sys.version_info[:2] == (2, 6), - "On Python 2.6, DeprecationWarnings are visible anyway") def test_runner_deprecation_verbosity_zero(self): args = ['test', '--settings=settings', '--verbosity=0'] out, err = self.run_django_admin(args)