Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Django 1.10 support. #4158

Merged
merged 14 commits into from
Jun 1, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions rest_framework/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions rest_framework/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,7 @@ def get_context(self, data, accepted_media_type, renderer_context):
'view': view,
'request': request,
'response': response,
'user': request.user,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed by Django 1.10 ?

Copy link
Member Author

@tomchristie tomchristie Jun 1, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For one reason or another, yes.

I've not dug into why yet, but presumably we were previously having it automatically included by the RequestContext. I assume that we could also change our settings in order to resolve this (eg perhaps context processor configuration has moved to being part of the TEMPLATES dictionary?)

In any case it's probably best that we pass it explicitly, rather than relying on the correct context processors being set.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified. Previously we were relying on the default value of TEMPLATE_CONTEXT_PROCESSORS, which includes django.contrib.auth.context_processors.auth.

We could add this in to the TEMPLATES.OPTIONS.context_processors in conftest.py, which would resolve our test cases, but it's better if we pass it explicitly, and not rely on the user settings.

'description': self.get_description(view, response.status_code),
'name': self.get_name(view),
'version': VERSION,
Expand Down
2 changes: 1 addition & 1 deletion rest_framework/urlpatterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions rest_framework/utils/representation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@
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)


Expand Down
2 changes: 1 addition & 1 deletion runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

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__))

Expand Down
10 changes: 3 additions & 7 deletions tests/browsable_api/test_browsable_api.py
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -40,11 +38,9 @@ def test_login_shown_when_logged_out(self):
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'
Expand Down
3 changes: 1 addition & 2 deletions tests/browsable_api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@


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})
19 changes: 12 additions & 7 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -27,7 +33,6 @@ def pytest_configure():
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.staticfiles',

'rest_framework',
'rest_framework.authtoken',
'tests',
Expand Down
3 changes: 0 additions & 3 deletions tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
15 changes: 8 additions & 7 deletions tests/test_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
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 (
Expand All @@ -19,6 +19,7 @@
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
Expand Down Expand Up @@ -75,15 +76,14 @@ def put(self, request):
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'
Expand Down Expand Up @@ -151,10 +151,9 @@ def test_post_json_failing_basic_auth(self):
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)
Expand Down Expand Up @@ -223,7 +222,6 @@ def test_post_form_session_auth_failing(self):

class BaseTokenAuthTests(object):
"""Token authentication"""
urls = 'tests.test_authentication'
model = None
path = None
header_prefix = 'Token '
Expand Down Expand Up @@ -311,6 +309,7 @@ def test_post_json_failing_token_auth(self):
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)


@override_settings(ROOT_URLCONF='tests.test_authentication')
class TokenAuthTests(BaseTokenAuthTests, TestCase):
model = Token
path = '/token/'
Expand Down Expand Up @@ -367,11 +366,13 @@ def test_token_login_form(self):
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/'
Expand Down
3 changes: 1 addition & 2 deletions tests/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,12 +274,11 @@ def test_unknown_filter(self):
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))

Expand Down
8 changes: 3 additions & 5 deletions tests/test_htmlrenderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
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
Expand Down Expand Up @@ -43,9 +43,8 @@ def not_found(request):
]


@override_settings(ROOT_URLCONF='tests.test_htmlrenderer')
class TemplateHTMLRendererTests(TestCase):
urls = 'tests.test_htmlrenderer'

def setUp(self):
"""
Monkeypatch get_template
Expand Down Expand Up @@ -89,9 +88,8 @@ def test_permission_denied_html_view(self):
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
Expand Down
6 changes: 2 additions & 4 deletions tests/test_middleware.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -20,10 +20,8 @@ def process_response(self, request, response):
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', '[email protected]', 'password')
key = 'abcd1234'
Expand Down
15 changes: 5 additions & 10 deletions tests/test_relations_hyperlink.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -71,10 +71,8 @@ class Meta:


# 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)
Expand Down Expand Up @@ -188,9 +186,8 @@ def test_reverse_many_to_many_create(self):
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()
Expand Down Expand Up @@ -318,9 +315,8 @@ def test_foreign_key_update_with_invalid_null(self):
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()
Expand Down Expand Up @@ -425,9 +421,8 @@ def test_foreign_key_update_with_valid_emptystring(self):
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()
Expand Down
10 changes: 3 additions & 7 deletions tests/test_renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
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 _
Expand Down Expand Up @@ -148,13 +148,11 @@ def test_only_permitted_forms_are_displayed(self):
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('/')
Expand Down Expand Up @@ -397,13 +395,11 @@ class AsciiJSONRenderer(JSONRenderer):


# 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
Expand Down
Loading