Django 1.10 support. (#4158)

* Added TEMPLATES setting to tests
* Remove deprecated view-string in URL conf
* Replace 'urls = ...' in test classes with override_settings('ROOT_URLCONF=...')
* Refactor UsingURLPatterns to use override_settings(ROOT_URLCONF=...) style
* Get model managers and names in a version-compatible manner.
* Apply override_settings to a TestCase, not a mixin class
* Use '.callback' property instead of private attributes when inspecting urlpatterns
* Pass 'user' to template explicitly
* Correct sorting of import statements.
* Remove unused TEMPLATE_LOADERS setting, in favor of TEMPLATES.
* Remove code style issue
* BaseFilter test requires a concrete model
* Resolve tox.ini issues
* Resolve isort differences between local and tox environments
This commit is contained in:
Tom Christie 2016-06-01 15:31:00 +01:00
parent fe2aede18d
commit 994e1ba927
24 changed files with 114 additions and 123 deletions

View File

@ -58,6 +58,23 @@ def distinct(queryset, base):
return queryset.distinct()
def get_names_and_managers(options):
if django.VERSION >= (1, 10):
# Django 1.10 onwards provides a `.managers` property on the Options.
return [
(manager.name, manager)
for manager
in options.managers
]
# For Django 1.8 and 1.9, use the three-tuple information provided
# by .concrete_managers and .abstract_managers
return [
(manager_info[1], manager_info[2])
for manager_info
in (options.concrete_managers + options.abstract_managers)
]
# contrib.postgres only supported from 1.8 onwards.
try:
from django.contrib.postgres import fields as postgres_fields

View File

@ -635,6 +635,7 @@ class BrowsableAPIRenderer(BaseRenderer):
'view': view,
'request': request,
'response': response,
'user': request.user,
'description': self.get_description(view, response.status_code),
'name': self.get_name(view),
'version': VERSION,

View File

@ -24,7 +24,7 @@ def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required):
else:
# Regular URL pattern
regex = urlpattern.regex.pattern.rstrip('$').rstrip('/') + suffix_pattern
view = urlpattern._callback or urlpattern._callback_str
view = urlpattern.callback
kwargs = urlpattern.default_args
name = urlpattern.name
# Add in both the existing and the new urlpattern

View File

@ -10,15 +10,15 @@ from django.db import models
from django.utils.encoding import force_text
from django.utils.functional import Promise
from rest_framework.compat import unicode_repr
from rest_framework.compat import get_names_and_managers, unicode_repr
def manager_repr(value):
model = value.model
opts = model._meta
for _, name, manager in opts.concrete_managers + opts.abstract_managers:
if manager == value:
return '%s.%s.all()' % (model._meta.object_name, name)
for manager_name, manager_instance in get_names_and_managers(opts):
if manager_instance == value:
return '%s.%s.all()' % (model._meta.object_name, manager_name)
return repr(value)

View File

@ -14,7 +14,7 @@ PYTEST_ARGS = {
FLAKE8_ARGS = ['rest_framework', 'tests', '--ignore=E501']
ISORT_ARGS = ['--recursive', '--check-only', 'rest_framework', 'tests']
ISORT_ARGS = ['--recursive', '--check-only', '-p', 'tests', 'rest_framework', 'tests']
sys.path.append(os.path.dirname(__file__))

View File

@ -1,16 +1,14 @@
from __future__ import unicode_literals
from django.contrib.auth.models import User
from django.test import TestCase
from django.test import TestCase, override_settings
from rest_framework.test import APIClient
@override_settings(ROOT_URLCONF='tests.browsable_api.auth_urls')
class DropdownWithAuthTests(TestCase):
"""Tests correct dropdown behaviour with Auth views enabled."""
urls = 'tests.browsable_api.auth_urls'
def setUp(self):
self.client = APIClient(enforce_csrf_checks=True)
self.username = 'john'
@ -40,11 +38,9 @@ class DropdownWithAuthTests(TestCase):
self.assertContains(response, '>Log in<')
@override_settings(ROOT_URLCONF='tests.browsable_api.no_auth_urls')
class NoDropdownWithoutAuthTests(TestCase):
"""Tests correct dropdown behaviour with Auth views NOT enabled."""
urls = 'tests.browsable_api.no_auth_urls'
def setUp(self):
self.client = APIClient(enforce_csrf_checks=True)
self.username = 'john'

View File

@ -6,9 +6,8 @@ from rest_framework.views import APIView
class MockView(APIView):
authentication_classes = (authentication.SessionAuthentication,)
renderer_classes = (renderers.BrowsableAPIRenderer,)
renderer_classes = (renderers.BrowsableAPIRenderer, renderers.JSONRenderer)
def get(self, request):
return Response({'a': 1, 'b': 2, 'c': 3})

View File

@ -3,18 +3,24 @@ def pytest_configure():
settings.configure(
DEBUG_PROPAGATE_EXCEPTIONS=True,
DATABASES={'default': {'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:'}},
DATABASES={
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:'
}
},
SITE_ID=1,
SECRET_KEY='not very secret in tests',
USE_I18N=True,
USE_L10N=True,
STATIC_URL='/static/',
ROOT_URLCONF='tests.urls',
TEMPLATE_LOADERS=(
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
),
TEMPLATES=[
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
},
],
MIDDLEWARE_CLASSES=(
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
@ -27,7 +33,6 @@ def pytest_configure():
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'tests',

View File

@ -27,9 +27,6 @@ class BasicModel(RESTFrameworkModel):
class BaseFilterableItem(RESTFrameworkModel):
text = models.CharField(max_length=100)
class Meta:
abstract = True
class FilterableItem(BaseFilterableItem):
decimal = models.DecimalField(max_digits=4, decimal_places=2)

View File

@ -8,7 +8,7 @@ from django.conf.urls import include, url
from django.contrib.auth.models import User
from django.db import models
from django.http import HttpResponse
from django.test import TestCase
from django.test import TestCase, override_settings
from django.utils import six
from rest_framework import (
@ -19,6 +19,7 @@ from rest_framework.authentication import (
TokenAuthentication
)
from rest_framework.authtoken.models import Token
from rest_framework.authtoken.views import obtain_auth_token
from rest_framework.response import Response
from rest_framework.test import APIClient, APIRequestFactory
from rest_framework.views import APIView
@ -75,15 +76,14 @@ urlpatterns = [
authentication_classes=[CustomKeywordTokenAuthentication]
)
),
url(r'^auth-token/$', 'rest_framework.authtoken.views.obtain_auth_token'),
url(r'^auth-token/$', obtain_auth_token),
url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')),
]
@override_settings(ROOT_URLCONF='tests.test_authentication')
class BasicAuthTests(TestCase):
"""Basic authentication"""
urls = 'tests.test_authentication'
def setUp(self):
self.csrf_client = APIClient(enforce_csrf_checks=True)
self.username = 'john'
@ -151,10 +151,9 @@ class BasicAuthTests(TestCase):
self.assertEqual(response['WWW-Authenticate'], 'Basic realm="api"')
@override_settings(ROOT_URLCONF='tests.test_authentication')
class SessionAuthTests(TestCase):
"""User session authentication"""
urls = 'tests.test_authentication'
def setUp(self):
self.csrf_client = APIClient(enforce_csrf_checks=True)
self.non_csrf_client = APIClient(enforce_csrf_checks=False)
@ -223,7 +222,6 @@ class SessionAuthTests(TestCase):
class BaseTokenAuthTests(object):
"""Token authentication"""
urls = 'tests.test_authentication'
model = None
path = None
header_prefix = 'Token '
@ -311,6 +309,7 @@ class BaseTokenAuthTests(object):
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
@override_settings(ROOT_URLCONF='tests.test_authentication')
class TokenAuthTests(BaseTokenAuthTests, TestCase):
model = Token
path = '/token/'
@ -367,11 +366,13 @@ class TokenAuthTests(BaseTokenAuthTests, TestCase):
self.assertEqual(response.data['token'], self.key)
@override_settings(ROOT_URLCONF='tests.test_authentication')
class CustomTokenAuthTests(BaseTokenAuthTests, TestCase):
model = CustomToken
path = '/customtoken/'
@override_settings(ROOT_URLCONF='tests.test_authentication')
class CustomKeywordTokenAuthTests(BaseTokenAuthTests, TestCase):
model = Token
path = '/customkeywordtoken/'

View File

@ -274,12 +274,11 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK)
@override_settings(ROOT_URLCONF='tests.test_filters')
class IntegrationTestDetailFiltering(CommonFilteringTestCase):
"""
Integration tests for filtered detail views.
"""
urls = 'tests.test_filters'
def _get_url(self, item):
return reverse('detail-view', kwargs=dict(pk=item.pk))

View File

@ -5,7 +5,7 @@ from django.conf.urls import url
from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.template import Template, TemplateDoesNotExist
from django.test import TestCase
from django.test import TestCase, override_settings
from django.utils import six
from rest_framework import status
@ -43,9 +43,8 @@ urlpatterns = [
]
@override_settings(ROOT_URLCONF='tests.test_htmlrenderer')
class TemplateHTMLRendererTests(TestCase):
urls = 'tests.test_htmlrenderer'
def setUp(self):
"""
Monkeypatch get_template
@ -89,9 +88,8 @@ class TemplateHTMLRendererTests(TestCase):
self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
@override_settings(ROOT_URLCONF='tests.test_htmlrenderer')
class TemplateHTMLRendererExceptionTests(TestCase):
urls = 'tests.test_htmlrenderer'
def setUp(self):
"""
Monkeypatch get_template

View File

@ -1,6 +1,6 @@
from django.conf.urls import url
from django.contrib.auth.models import User
from django.test import override_settings
from rest_framework.authentication import TokenAuthentication
from rest_framework.authtoken.models import Token
@ -20,10 +20,8 @@ class MyMiddleware(object):
return response
@override_settings(ROOT_URLCONF='tests.test_middleware')
class TestMiddleware(APITestCase):
urls = 'tests.test_middleware'
def test_middleware_can_access_user_when_processing_response(self):
user = User.objects.create_user('john', 'john@example.com', 'password')
key = 'abcd1234'

View File

@ -1,7 +1,7 @@
from __future__ import unicode_literals
from django.conf.urls import url
from django.test import TestCase
from django.test import TestCase, override_settings
from rest_framework import serializers
from rest_framework.test import APIRequestFactory
@ -71,10 +71,8 @@ class NullableOneToOneTargetSerializer(serializers.HyperlinkedModelSerializer):
# TODO: Add test that .data cannot be accessed prior to .is_valid
@override_settings(ROOT_URLCONF='tests.test_relations_hyperlink')
class HyperlinkedManyToManyTests(TestCase):
urls = 'tests.test_relations_hyperlink'
def setUp(self):
for idx in range(1, 4):
target = ManyToManyTarget(name='target-%d' % idx)
@ -188,9 +186,8 @@ class HyperlinkedManyToManyTests(TestCase):
self.assertEqual(serializer.data, expected)
@override_settings(ROOT_URLCONF='tests.test_relations_hyperlink')
class HyperlinkedForeignKeyTests(TestCase):
urls = 'tests.test_relations_hyperlink'
def setUp(self):
target = ForeignKeyTarget(name='target-1')
target.save()
@ -318,9 +315,8 @@ class HyperlinkedForeignKeyTests(TestCase):
self.assertEqual(serializer.errors, {'target': ['This field may not be null.']})
@override_settings(ROOT_URLCONF='tests.test_relations_hyperlink')
class HyperlinkedNullableForeignKeyTests(TestCase):
urls = 'tests.test_relations_hyperlink'
def setUp(self):
target = ForeignKeyTarget(name='target-1')
target.save()
@ -425,9 +421,8 @@ class HyperlinkedNullableForeignKeyTests(TestCase):
self.assertEqual(serializer.data, expected)
@override_settings(ROOT_URLCONF='tests.test_relations_hyperlink')
class HyperlinkedNullableOneToOneTests(TestCase):
urls = 'tests.test_relations_hyperlink'
def setUp(self):
target = OneToOneTarget(name='target-1')
target.save()

View File

@ -8,7 +8,7 @@ from collections import MutableMapping, OrderedDict
from django.conf.urls import include, url
from django.core.cache import cache
from django.db import models
from django.test import TestCase
from django.test import TestCase, override_settings
from django.utils import six
from django.utils.safestring import SafeText
from django.utils.translation import ugettext_lazy as _
@ -148,13 +148,11 @@ class DocumentingRendererTests(TestCase):
self.assertContains(response, '>PATCH<')
@override_settings(ROOT_URLCONF='tests.test_renderers')
class RendererEndToEndTests(TestCase):
"""
End-to-end testing of renderers using an RendererMixin on a generic view.
"""
urls = 'tests.test_renderers'
def test_default_renderer_serializes_content(self):
"""If the Accept header is not set the default renderer should serialize the response."""
resp = self.client.get('/')
@ -397,13 +395,11 @@ class AsciiJSONRendererTests(TestCase):
# Tests for caching issue, #346
@override_settings(ROOT_URLCONF='tests.test_renderers')
class CacheRenderTest(TestCase):
"""
Tests specific to caching responses
"""
urls = 'tests.test_renderers'
def test_head_caching(self):
"""
Test caching of HEAD requests

View File

@ -7,7 +7,7 @@ from django.conf.urls import url
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import TestCase
from django.test import TestCase, override_settings
from django.utils import six
from rest_framework import status
@ -113,9 +113,8 @@ urlpatterns = [
]
@override_settings(ROOT_URLCONF='tests.test_request')
class TestContentParsingWithAuthentication(TestCase):
urls = 'tests.test_request'
def setUp(self):
self.csrf_client = APIClient(enforce_csrf_checks=True)
self.username = 'john'

View File

@ -1,7 +1,7 @@
from __future__ import unicode_literals
from django.conf.urls import include, url
from django.test import TestCase
from django.test import TestCase, override_settings
from django.utils import six
from rest_framework import generics, routers, serializers, status, viewsets
@ -131,13 +131,11 @@ urlpatterns = [
# TODO: Clean tests bellow - remove duplicates with above, better unit testing, ...
@override_settings(ROOT_URLCONF='tests.test_response')
class RendererIntegrationTests(TestCase):
"""
End-to-end testing of renderers using an ResponseMixin on a generic view.
"""
urls = 'tests.test_response'
def test_default_renderer_serializes_content(self):
"""If the Accept header is not set the default renderer should serialize the response."""
resp = self.client.get('/')
@ -201,9 +199,8 @@ class RendererIntegrationTests(TestCase):
self.assertEqual(resp.status_code, DUMMYSTATUS)
@override_settings(ROOT_URLCONF='tests.test_response')
class UnsupportedMediaTypeTests(TestCase):
urls = 'tests.test_response'
def test_should_allow_posting_json(self):
response = self.client.post('/json', data='{"test": 123}', content_type='application/json')
@ -220,12 +217,11 @@ class UnsupportedMediaTypeTests(TestCase):
self.assertEqual(response.status_code, 415)
@override_settings(ROOT_URLCONF='tests.test_response')
class Issue122Tests(TestCase):
"""
Tests that covers #122.
"""
urls = 'tests.test_response'
def test_only_html_renderer(self):
"""
Test if no infinite recursion occurs.
@ -239,13 +235,11 @@ class Issue122Tests(TestCase):
self.client.get('/html1')
@override_settings(ROOT_URLCONF='tests.test_response')
class Issue467Tests(TestCase):
"""
Tests for #467
"""
urls = 'tests.test_response'
def test_form_has_label_and_help_text(self):
resp = self.client.get('/html_new_model')
self.assertEqual(resp['Content-Type'], 'text/html; charset=utf-8')
@ -253,13 +247,11 @@ class Issue467Tests(TestCase):
# self.assertContains(resp, 'Text description.')
@override_settings(ROOT_URLCONF='tests.test_response')
class Issue807Tests(TestCase):
"""
Covers #807
"""
urls = 'tests.test_response'
def test_does_not_append_charset_by_default(self):
"""
Renderers don't include a charset unless set explicitly.

View File

@ -2,7 +2,7 @@ from __future__ import unicode_literals
from django.conf.urls import url
from django.core.urlresolvers import NoReverseMatch
from django.test import TestCase
from django.test import TestCase, override_settings
from rest_framework.reverse import reverse
from rest_framework.test import APIRequestFactory
@ -30,12 +30,11 @@ class MockVersioningScheme(object):
return 'http://scheme-reversed/view'
@override_settings(ROOT_URLCONF='tests.test_reverse')
class ReverseTests(TestCase):
"""
Tests for fully qualified URLs when using `reverse`.
"""
urls = 'tests.test_reverse'
def test_reversed_urls_are_fully_qualified(self):
request = factory.get('/view')
url = reverse('view', request=request)

View File

@ -5,7 +5,7 @@ from collections import namedtuple
from django.conf.urls import include, url
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.test import TestCase
from django.test import TestCase, override_settings
from rest_framework import permissions, serializers, viewsets
from rest_framework.decorators import detail_route, list_route
@ -113,9 +113,8 @@ class TestSimpleRouter(TestCase):
self.assertEqual(route.mapping[method], endpoint)
@override_settings(ROOT_URLCONF='tests.test_routers')
class TestRootView(TestCase):
urls = 'tests.test_routers'
def test_retrieve_namespaced_root(self):
response = self.client.get('/namespaced/')
self.assertEqual(
@ -135,12 +134,11 @@ class TestRootView(TestCase):
)
@override_settings(ROOT_URLCONF='tests.test_routers')
class TestCustomLookupFields(TestCase):
"""
Ensure that custom lookup fields are correctly routed.
"""
urls = 'tests.test_routers'
def setUp(self):
RouterTestModel.objects.create(uuid='123', text='foo bar')
@ -191,14 +189,13 @@ class TestLookupValueRegex(TestCase):
self.assertEqual(expected[idx], self.urls[idx].regex.pattern)
@override_settings(ROOT_URLCONF='tests.test_routers')
class TestLookupUrlKwargs(TestCase):
"""
Ensure the router honors lookup_url_kwarg.
Setup a deep lookup_field, but map it to a simple URL kwarg.
"""
urls = 'tests.test_routers'
def setUp(self):
RouterTestModel.objects.create(uuid='123', text='foo bar')

View File

@ -6,7 +6,7 @@ from io import BytesIO
from django.conf.urls import url
from django.contrib.auth.models import User
from django.shortcuts import redirect
from django.test import TestCase
from django.test import TestCase, override_settings
from rest_framework.decorators import api_view
from rest_framework.response import Response
@ -44,9 +44,8 @@ urlpatterns = [
]
@override_settings(ROOT_URLCONF='tests.test_testing')
class TestAPITestClient(TestCase):
urls = 'tests.test_testing'
def setUp(self):
self.client = APIClient()

View File

@ -2,7 +2,7 @@ from __future__ import unicode_literals
from django.conf.urls import url
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase
from django.test import TestCase, override_settings
from django.utils import six
import rest_framework.utils.model_meta
@ -47,12 +47,11 @@ urlpatterns = [
]
@override_settings(ROOT_URLCONF='tests.test_utils')
class BreadcrumbTests(TestCase):
"""
Tests the breadcrumb functionality used by the HTML renderer.
"""
urls = 'tests.test_utils'
def test_root_breadcrumbs(self):
url = '/'
self.assertEqual(

View File

@ -1,5 +1,6 @@
import pytest
from django.conf.urls import include, url
from django.test import override_settings
from rest_framework import serializers, status, versioning
from rest_framework.decorators import APIView
@ -9,7 +10,28 @@ from rest_framework.reverse import reverse
from rest_framework.test import APIRequestFactory, APITestCase
from rest_framework.versioning import NamespaceVersioning
from .utils import UsingURLPatterns
@override_settings(ROOT_URLCONF='tests.test_versioning')
class URLPatternsTestCase(APITestCase):
"""
Isolates URL patterns used during testing on the test class itself.
For example:
class MyTestCase(URLPatternsTestCase):
urlpatterns = [
...
]
def test_something(self):
...
"""
def setUp(self):
global urlpatterns
urlpatterns = self.urlpatterns
def tearDown(self):
global urlpatterns
urlpatterns = []
class RequestVersionView(APIView):
@ -120,7 +142,7 @@ class TestRequestVersion:
assert response.data == {'version': None}
class TestURLReversing(UsingURLPatterns, APITestCase):
class TestURLReversing(URLPatternsTestCase):
included = [
url(r'^namespaced/$', dummy_view, name='another'),
url(r'^example/(?P<pk>\d+)/$', dummy_pk_view, name='example-detail')
@ -238,7 +260,7 @@ class TestInvalidVersion:
assert response.status_code == status.HTTP_404_NOT_FOUND
class TestHyperlinkedRelatedField(UsingURLPatterns, APITestCase):
class TestHyperlinkedRelatedField(URLPatternsTestCase):
included = [
url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_view, name='namespaced'),
]
@ -270,7 +292,7 @@ class TestHyperlinkedRelatedField(UsingURLPatterns, APITestCase):
self.field.to_internal_value('/v2/namespaced/3/')
class TestNamespaceVersioningHyperlinkedRelatedFieldScheme(UsingURLPatterns, APITestCase):
class TestNamespaceVersioningHyperlinkedRelatedFieldScheme(URLPatternsTestCase):
included = [
url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_view, name='namespaced'),
]

View File

@ -2,30 +2,6 @@ from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import NoReverseMatch
class UsingURLPatterns(object):
"""
Isolates URL patterns used during testing on the test class itself.
For example:
class MyTestCase(UsingURLPatterns, TestCase):
urlpatterns = [
...
]
def test_something(self):
...
"""
urls = __name__
def setUp(self):
global urlpatterns
urlpatterns = self.urlpatterns
def tearDown(self):
global urlpatterns
urlpatterns = []
class MockObject(object):
def __init__(self, **kwargs):
self._kwargs = kwargs

10
tox.ini
View File

@ -5,8 +5,8 @@ addopts=--tb=short
envlist =
py27-{lint,docs},
{py27,py32,py33,py34,py35}-django18,
{py27,py34,py35}-django{19}
{py27,py34,py35}-django{110}
{py27,py34,py35}-django19,
{py27,py34,py35}-django110
[testenv]
commands = ./runtests.py --fast {posargs} --coverage -rw
@ -19,6 +19,12 @@ deps =
django110: Django==1.10a1
-rrequirements/requirements-testing.txt
-rrequirements/requirements-optionals.txt
basepython =
py35: python3.5
py34: python3.4
py33: python3.3
py32: python3.2
py27: python2.7
[testenv:py27-lint]
commands = ./runtests.py --lintonly