diff --git a/.travis.yml b/.travis.yml index 4a7fdd42f..acd711917 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,11 +13,6 @@ env: - TOX_ENV=py33-django17 - TOX_ENV=py32-django17 - TOX_ENV=py27-django17 - - TOX_ENV=py34-django16 - - TOX_ENV=py33-django16 - - TOX_ENV=py32-django16 - - TOX_ENV=py27-django16 - - TOX_ENV=py26-django16 - TOX_ENV=py27-djangomaster - TOX_ENV=py34-djangomaster - TOX_ENV=py35-djangomaster diff --git a/README.md b/README.md index c2a686f4c..c0b7eab1e 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,8 @@ There is a live example API for testing purposes, [available here][sandbox]. # Requirements -* Python (2.6.5+, 2.7, 3.2, 3.3, 3.4) -* Django (1.6.3+, 1.7, 1.8) +* Python (2.7, 3.2, 3.3, 3.4) +* Django (1.7, 1.8) # Installation diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index be2586f02..13f95b80b 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -45,6 +45,7 @@ You can determine your currently installed version using `pip freeze`: **Date**: NOT YET RELEASED * Removed support for Django Version 1.5 ([#3421][gh3421]) +* Removed support for Django Version 1.6 and Python 2.6 ([#3429][gh3429]) ## 3.2.x series @@ -524,4 +525,4 @@ For older release notes, [please see the version 2.x documentation][old-release- [gh3421]: https://github.com/tomchristie/django-rest-framework/pulls/3421 - +[gh3429]: https://github.com/tomchristie/django-rest-framework/pull/3429 diff --git a/rest_framework/compat.py b/rest_framework/compat.py index fcca2dcbf..baed9d40a 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -57,24 +57,6 @@ def distinct(queryset, base): return queryset.distinct() -# OrderedDict only available in Python 2.7. -# This will always be the case in Django 1.7 and above, as these versions -# no longer support Python 2.6. -# For Django <= 1.6 and Python 2.6 fall back to SortedDict. -try: - from collections import OrderedDict -except ImportError: - from django.utils.datastructures import SortedDict as OrderedDict - - -# unittest.SkipUnless only available in Python 2.7. -try: - import unittest - unittest.skipUnless -except (ImportError, AttributeError): - from django.utils import unittest - - # contrib.postgres only supported from 1.8 onwards. try: from django.contrib.postgres import fields as postgres_fields @@ -82,24 +64,6 @@ except ImportError: postgres_fields = None -# Apps only exists from 1.7 onwards. -try: - from django.apps import apps - get_model = apps.get_model -except ImportError: - from django.db.models import get_model - - -# Import path changes from 1.7 onwards. -try: - from django.contrib.contenttypes.fields import ( - GenericForeignKey, GenericRelation - ) -except ImportError: - from django.contrib.contenttypes.generic import ( - GenericForeignKey, GenericRelation - ) - # django-filter is optional try: import django_filters diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 1e23b35f0..f216c7612 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -7,6 +7,7 @@ import decimal import inspect import re import uuid +from collections import OrderedDict from django.conf import settings from django.core.exceptions import ValidationError as DjangoValidationError @@ -26,8 +27,8 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import ISO_8601 from rest_framework.compat import ( MaxLengthValidator, MaxValueValidator, MinLengthValidator, - MinValueValidator, OrderedDict, duration_string, parse_duration, - unicode_repr, unicode_to_repr + MinValueValidator, duration_string, parse_duration, unicode_repr, + unicode_to_repr ) from rest_framework.exceptions import ValidationError from rest_framework.settings import api_settings diff --git a/rest_framework/metadata.py b/rest_framework/metadata.py index 5058422f2..aba1a2013 100644 --- a/rest_framework/metadata.py +++ b/rest_framework/metadata.py @@ -8,12 +8,13 @@ to return this information in a more standardized way. """ from __future__ import unicode_literals +from collections import OrderedDict + from django.core.exceptions import PermissionDenied from django.http import Http404 from django.utils.encoding import force_text from rest_framework import exceptions, serializers -from rest_framework.compat import OrderedDict from rest_framework.request import clone_request from rest_framework.utils.field_mapping import ClassLookupDict diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index bf72ef4fc..d82a755c8 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals import warnings from base64 import b64decode, b64encode -from collections import namedtuple +from collections import OrderedDict, namedtuple from django.core.paginator import Paginator as DjangoPaginator from django.core.paginator import InvalidPage @@ -16,7 +16,6 @@ from django.utils import six from django.utils.six.moves.urllib import parse as urlparse from django.utils.translation import ugettext_lazy as _ -from rest_framework.compat import OrderedDict from rest_framework.exceptions import NotFound from rest_framework.response import Response from rest_framework.settings import api_settings diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 56af657d9..24800a758 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -1,6 +1,8 @@ # coding: utf-8 from __future__ import unicode_literals +from collections import OrderedDict + from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django.core.urlresolvers import ( NoReverseMatch, Resolver404, get_script_prefix, resolve @@ -12,7 +14,6 @@ from django.utils.encoding import smart_text from django.utils.six.moves.urllib import parse as urlparse from django.utils.translation import ugettext_lazy as _ -from rest_framework.compat import OrderedDict from rest_framework.fields import ( Field, empty, get_attribute, is_simple_callable, iter_options ) diff --git a/rest_framework/routers.py b/rest_framework/routers.py index b96100ec2..d4e9d95ed 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -16,14 +16,13 @@ For example, you might have a `urls.py` that looks something like this: from __future__ import unicode_literals import itertools -from collections import namedtuple +from collections import OrderedDict, namedtuple from django.conf.urls import url from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import NoReverseMatch from rest_framework import views -from rest_framework.compat import OrderedDict from rest_framework.response import Response from rest_framework.reverse import reverse from rest_framework.urlpatterns import format_suffix_patterns diff --git a/rest_framework/utils/model_meta.py b/rest_framework/utils/model_meta.py index 971864769..d16630ed6 100644 --- a/rest_framework/utils/model_meta.py +++ b/rest_framework/utils/model_meta.py @@ -6,14 +6,13 @@ relationships and their associated metadata. Usage: `get_field_info(model)` returns a `FieldInfo` instance. """ import inspect -from collections import namedtuple +from collections import OrderedDict, namedtuple +from django.apps import apps from django.core.exceptions import ImproperlyConfigured from django.db import models from django.utils import six -from rest_framework.compat import OrderedDict, get_model - FieldInfo = namedtuple('FieldResult', [ 'pk', # Model field instance 'fields', # Dict of field name -> model field instance @@ -45,7 +44,7 @@ def _resolve_model(obj): """ if isinstance(obj, six.string_types) and len(obj.split('.')) == 2: app_name, model_name = obj.split('.') - resolved_model = get_model(app_name, model_name) + resolved_model = apps.get_model(app_name, model_name) if resolved_model is None: msg = "Django did not return a model for {0}.{1}" raise ImproperlyConfigured(msg.format(app_name, model_name)) diff --git a/rest_framework/utils/serializer_helpers.py b/rest_framework/utils/serializer_helpers.py index 0aede90f7..ddf160868 100644 --- a/rest_framework/utils/serializer_helpers.py +++ b/rest_framework/utils/serializer_helpers.py @@ -1,10 +1,11 @@ from __future__ import unicode_literals import collections +from collections import OrderedDict from django.utils.encoding import force_text -from rest_framework.compat import OrderedDict, unicode_to_repr +from rest_framework.compat import unicode_to_repr class ReturnDict(OrderedDict): diff --git a/tests/test_atomic_requests.py b/tests/test_atomic_requests.py index 2d973cb8a..316c8832a 100644 --- a/tests/test_atomic_requests.py +++ b/tests/test_atomic_requests.py @@ -1,5 +1,7 @@ from __future__ import unicode_literals +import unittest + from django.conf.urls import url from django.db import connection, connections, transaction from django.http import Http404 @@ -7,7 +9,6 @@ from django.test import TestCase, TransactionTestCase from django.utils.decorators import method_decorator from rest_framework import status -from rest_framework.compat import unittest from rest_framework.exceptions import APIException from rest_framework.response import Response from rest_framework.test import APIRequestFactory diff --git a/tests/test_filters.py b/tests/test_filters.py index bce6e08fa..729a7b75b 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import datetime +import unittest from decimal import Decimal from django.conf.urls import url @@ -12,7 +13,7 @@ from django.utils.dateparse import parse_date from django.utils.six.moves import reload_module from rest_framework import filters, generics, serializers, status -from rest_framework.compat import django_filters, unittest +from rest_framework.compat import django_filters from rest_framework.test import APIRequestFactory from .models import BaseFilterableItem, BasicModel, FilterableItem diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 89557fa1d..aa62ec4ae 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -8,6 +8,7 @@ an appropriate set of serializer fields for each case. from __future__ import unicode_literals import decimal +from collections import OrderedDict import django import pytest @@ -21,7 +22,7 @@ from django.utils import six from rest_framework import serializers from rest_framework.compat import DurationField as ModelDurationField -from rest_framework.compat import OrderedDict, unicode_repr +from rest_framework.compat import unicode_repr def dedent(blocktext): diff --git a/tests/test_permissions.py b/tests/test_permissions.py index f0d77e957..e04c72ec9 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import base64 +import unittest from django.contrib.auth.models import Group, Permission, User from django.core.urlresolvers import ResolverMatch @@ -11,7 +12,7 @@ from rest_framework import ( HTTP_HEADER_ENCODING, authentication, generics, permissions, serializers, status ) -from rest_framework.compat import guardian, unittest +from rest_framework.compat import guardian from rest_framework.filters import DjangoObjectPermissionsFilter from rest_framework.routers import DefaultRouter from rest_framework.test import APIRequestFactory diff --git a/tests/test_relations_generic.py b/tests/test_relations_generic.py index 5cb2dfc05..340d4d1d1 100644 --- a/tests/test_relations_generic.py +++ b/tests/test_relations_generic.py @@ -1,12 +1,14 @@ from __future__ import unicode_literals +from django.contrib.contenttypes.fields import ( + GenericForeignKey, GenericRelation +) from django.contrib.contenttypes.models import ContentType from django.db import models from django.test import TestCase from django.utils.encoding import python_2_unicode_compatible from rest_framework import serializers -from rest_framework.compat import GenericForeignKey, GenericRelation @python_2_unicode_compatible diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 03b95243f..060d3503a 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import json import re -from collections import MutableMapping +from collections import MutableMapping, OrderedDict from django.conf.urls import include, url from django.core.cache import cache @@ -13,7 +13,6 @@ from django.utils import six from django.utils.translation import ugettext_lazy as _ from rest_framework import permissions, serializers, status -from rest_framework.compat import OrderedDict from rest_framework.renderers import ( BaseRenderer, BrowsableAPIRenderer, HTMLFormRenderer, JSONRenderer ) diff --git a/tests/test_utils.py b/tests/test_utils.py index 4c9fd03c8..fdc61a4aa 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -150,16 +150,16 @@ class ResolveModelWithPatchedDjangoTests(TestCase): def setUp(self): """Monkeypatch get_model.""" - self.get_model = rest_framework.utils.model_meta.get_model + self.get_model = rest_framework.utils.model_meta.apps.get_model def get_model(app_label, model_name): return None - rest_framework.utils.model_meta.get_model = get_model + rest_framework.utils.model_meta.apps.get_model = get_model def tearDown(self): """Revert monkeypatching.""" - rest_framework.utils.model_meta.models.get_model = self.get_model + rest_framework.utils.model_meta.apps.get_model = self.get_model def test_blows_up_if_model_does_not_resolve(self): with self.assertRaises(ImproperlyConfigured): diff --git a/tox.ini b/tox.ini index 204a68457..0d3836ff2 100644 --- a/tox.ini +++ b/tox.ini @@ -4,13 +4,11 @@ addopts=--tb=short [tox] envlist = py27-{lint,docs}, - {py26,py27,py32,py33,py34}-django16, {py27,py32,py33,py34}-django{17,18}, {py27,py34,py35}-django{master} [testenv] basepython = - py26: python2.6 py27: python2.7 py32: python3.2 py33: python3.3 @@ -21,7 +19,6 @@ commands = ./runtests.py --fast {posargs} --coverage setenv = PYTHONDONTWRITEBYTECODE=1 deps = - django16: Django==1.6.3 # Should track minimum supported django17: Django==1.7.10 # Should track maximum supported django18: Django==1.8.4 # Should track maximum supported djangomaster: https://github.com/django/django/archive/master.tar.gz