From 220be31791693f056591e2b9593b80d17c31830c Mon Sep 17 00:00:00 2001 From: Artem Muterko Date: Fri, 27 Jan 2017 17:44:00 +0200 Subject: [PATCH 1/8] Git add remaining tests for BaseSerializer (#4857) --- tests/test_serializer.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test_serializer.py b/tests/test_serializer.py index 47258fdd1..04fa39c0d 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -159,6 +159,32 @@ class TestBaseSerializer: self.Serializer = ExampleSerializer + def test_abstract_methods_raise_proper_errors(self): + serializer = serializers.BaseSerializer() + with pytest.raises(NotImplementedError): + serializer.to_internal_value(None) + with pytest.raises(NotImplementedError): + serializer.to_representation(None) + with pytest.raises(NotImplementedError): + serializer.update(None, None) + with pytest.raises(NotImplementedError): + serializer.create(None) + + def test_access_to_data_attribute_before_validation_raises_error(self): + serializer = serializers.BaseSerializer(data={'foo': 'bar'}) + with pytest.raises(AssertionError): + serializer.data + + def test_access_to_errors_attribute_before_validation_raises_error(self): + serializer = serializers.BaseSerializer(data={'foo': 'bar'}) + with pytest.raises(AssertionError): + serializer.errors + + def test_access_to_validated_data_attribute_before_validation_raises_error(self): + serializer = serializers.BaseSerializer(data={'foo': 'bar'}) + with pytest.raises(AssertionError): + serializer.validated_data + def test_serialize_instance(self): instance = {'id': 1, 'name': 'tom', 'domain': 'example.com'} serializer = self.Serializer(instance) From a8dbc2202811c85b84b3112dc2f317154906da57 Mon Sep 17 00:00:00 2001 From: Anna Ossowski Date: Sat, 28 Jan 2017 23:41:21 +0100 Subject: [PATCH 2/8] Updated Support section and added funding email (#4860) --- docs/index.md | 2 +- docs/topics/funding.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 29a11791e..f3897696f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -260,7 +260,7 @@ Framework. For support please see the [REST framework discussion group][group], try the `#restframework` channel on `irc.freenode.net`, search [the IRC archives][botbot], or raise a question on [Stack Overflow][stack-overflow], making sure to include the ['django-rest-framework'][django-rest-framework-tag] tag. -[Paid support is available][paid-support] from [DabApps][dabapps], and can include work on REST framework core, or support with building your REST framework API. Please [contact DabApps][contact-dabapps] if you'd like to discuss commercial support options. +For priority support please sign up for a [professional or premium sponsorship plan](https://fund.django-rest-framework.org/topics/funding/). For updates on REST framework development, you may also want to follow [the author][twitter] on Twitter. diff --git a/docs/topics/funding.md b/docs/topics/funding.md index 814de0a3c..91099cb3f 100644 --- a/docs/topics/funding.md +++ b/docs/topics/funding.md @@ -308,7 +308,7 @@ Our professional and premium plans also include **priority support**. At any tim Once you've signed up I'll contact you via email and arrange your ad placements on the site. -For further enquires please contact tom@tomchristie.com. +For further enquires please contact funding@django-rest-framework.org. --- From 1c437a793c3f3cedb1aa53f0cccec23a96cc3f34 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Sun, 29 Jan 2017 20:38:39 +0100 Subject: [PATCH 3/8] Removed unnecessary importlib wrapper. --- rest_framework/compat.py | 5 ----- rest_framework/settings.py | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index b0e076203..872441abc 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -17,11 +17,6 @@ from django.template import Context, RequestContext, Template from django.utils import six from django.views.generic import View -try: - import importlib # Available in Python 3.1+ -except ImportError: - from django.utils import importlib # Will be removed in Django 1.9 - try: from django.urls import ( diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 6d9ed2355..b699d7caf 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -18,13 +18,13 @@ REST framework settings, checking for user settings first, then falling back to the defaults. """ from __future__ import unicode_literals +from importlib import import_module from django.conf import settings from django.test.signals import setting_changed from django.utils import six from rest_framework import ISO_8601 -from rest_framework.compat import importlib DEFAULTS = { # Base API policies @@ -174,7 +174,7 @@ def import_from_string(val, setting_name): # Nod to tastypie's use of importlib. parts = val.split('.') module_path, class_name = '.'.join(parts[:-1]), parts[-1] - module = importlib.import_module(module_path) + module = import_module(module_path) return getattr(module, class_name) except (ImportError, AttributeError) as e: msg = "Could not import '%s' for API setting '%s'. %s: %s." % (val, setting_name, e.__class__.__name__, e) From 31e9f7dfbba7ad967d7f912e2014d9ad291fca3e Mon Sep 17 00:00:00 2001 From: Artem Muterko Date: Mon, 30 Jan 2017 13:08:03 +0200 Subject: [PATCH 4/8] Add remaining tests for generics (#4865) --- tests/test_generics.py | 91 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/tests/test_generics.py b/tests/test_generics.py index c24cda006..59278572e 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -547,3 +547,94 @@ class TestGuardedQueryset(TestCase): request = factory.get('/') with pytest.raises(RuntimeError): view(request).render() + + +class ApiViewsTests(TestCase): + + def test_create_api_view_post(self): + class MockCreateApiView(generics.CreateAPIView): + def create(self, request, *args, **kwargs): + self.called = True + self.call_args = (request, args, kwargs) + view = MockCreateApiView() + data = ('test request', ('test arg',), {'test_kwarg': 'test'}) + view.post('test request', 'test arg', test_kwarg='test') + assert view.called is True + assert view.call_args == data + + def test_destroy_api_view_delete(self): + class MockDestroyApiView(generics.DestroyAPIView): + def destroy(self, request, *args, **kwargs): + self.called = True + self.call_args = (request, args, kwargs) + view = MockDestroyApiView() + data = ('test request', ('test arg',), {'test_kwarg': 'test'}) + view.delete('test request', 'test arg', test_kwarg='test') + assert view.called is True + assert view.call_args == data + + def test_update_api_view_partial_update(self): + class MockUpdateApiView(generics.UpdateAPIView): + def partial_update(self, request, *args, **kwargs): + self.called = True + self.call_args = (request, args, kwargs) + view = MockUpdateApiView() + data = ('test request', ('test arg',), {'test_kwarg': 'test'}) + view.patch('test request', 'test arg', test_kwarg='test') + assert view.called is True + assert view.call_args == data + + def test_retrieve_update_api_view_get(self): + class MockRetrieveUpdateApiView(generics.RetrieveUpdateAPIView): + def retrieve(self, request, *args, **kwargs): + self.called = True + self.call_args = (request, args, kwargs) + view = MockRetrieveUpdateApiView() + data = ('test request', ('test arg',), {'test_kwarg': 'test'}) + view.get('test request', 'test arg', test_kwarg='test') + assert view.called is True + assert view.call_args == data + + def test_retrieve_update_api_view_put(self): + class MockRetrieveUpdateApiView(generics.RetrieveUpdateAPIView): + def update(self, request, *args, **kwargs): + self.called = True + self.call_args = (request, args, kwargs) + view = MockRetrieveUpdateApiView() + data = ('test request', ('test arg',), {'test_kwarg': 'test'}) + view.put('test request', 'test arg', test_kwarg='test') + assert view.called is True + assert view.call_args == data + + def test_retrieve_update_api_view_patch(self): + class MockRetrieveUpdateApiView(generics.RetrieveUpdateAPIView): + def partial_update(self, request, *args, **kwargs): + self.called = True + self.call_args = (request, args, kwargs) + view = MockRetrieveUpdateApiView() + data = ('test request', ('test arg',), {'test_kwarg': 'test'}) + view.patch('test request', 'test arg', test_kwarg='test') + assert view.called is True + assert view.call_args == data + + def test_retrieve_destroy_api_view_get(self): + class MockRetrieveDestroyUApiView(generics.RetrieveDestroyAPIView): + def retrieve(self, request, *args, **kwargs): + self.called = True + self.call_args = (request, args, kwargs) + view = MockRetrieveDestroyUApiView() + data = ('test request', ('test arg',), {'test_kwarg': 'test'}) + view.get('test request', 'test arg', test_kwarg='test') + assert view.called is True + assert view.call_args == data + + def test_retrieve_destroy_api_view_delete(self): + class MockRetrieveDestroyUApiView(generics.RetrieveDestroyAPIView): + def destroy(self, request, *args, **kwargs): + self.called = True + self.call_args = (request, args, kwargs) + view = MockRetrieveDestroyUApiView() + data = ('test request', ('test arg',), {'test_kwarg': 'test'}) + view.delete('test request', 'test arg', test_kwarg='test') + assert view.called is True + assert view.call_args == data From 3001b56e069a8aee118c2cd0977d04aa16733af4 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 30 Jan 2017 17:11:19 +0100 Subject: [PATCH 5/8] Fixed Django 2.0 compatibility due to `django.conf.urls.include` parameters change. (#4866) --- rest_framework/compat.py | 7 +++++++ rest_framework/urlpatterns.py | 4 ++-- tests/test_routers.py | 5 +++-- tests/test_versioning.py | 11 ++++++----- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 872441abc..16bc41f3a 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -307,3 +307,10 @@ def set_many(instance, field, value): else: field = getattr(instance, field) field.set(value) + +def include(module, namespace=None, app_name=None): + from django.conf.urls import include + if django.VERSION < (1,9): + return include(module, namespace, app_name) + else: + return include((module, app_name), namespace) diff --git a/rest_framework/urlpatterns.py b/rest_framework/urlpatterns.py index 4ea55300e..2ce4ba52d 100644 --- a/rest_framework/urlpatterns.py +++ b/rest_framework/urlpatterns.py @@ -1,8 +1,8 @@ from __future__ import unicode_literals -from django.conf.urls import include, url +from django.conf.urls import url -from rest_framework.compat import RegexURLResolver +from rest_framework.compat import RegexURLResolver, include from rest_framework.settings import api_settings diff --git a/tests/test_routers.py b/tests/test_routers.py index 99e4391c0..dc3df2e7b 100644 --- a/tests/test_routers.py +++ b/tests/test_routers.py @@ -4,12 +4,13 @@ import json from collections import namedtuple import pytest -from django.conf.urls import include, url +from django.conf.urls import url from django.core.exceptions import ImproperlyConfigured from django.db import models from django.test import TestCase, override_settings from rest_framework import permissions, serializers, viewsets +from rest_framework.compat import include from rest_framework.decorators import detail_route, list_route from rest_framework.response import Response from rest_framework.routers import DefaultRouter, SimpleRouter @@ -81,7 +82,7 @@ empty_prefix_urls = [ urlpatterns = [ url(r'^non-namespaced/', include(namespaced_router.urls)), - url(r'^namespaced/', include(namespaced_router.urls, namespace='example')), + url(r'^namespaced/', include(namespaced_router.urls, namespace='example', app_name='example')), url(r'^example/', include(notes_router.urls)), url(r'^example2/', include(kwarged_notes_router.urls)), diff --git a/tests/test_versioning.py b/tests/test_versioning.py index 195f3fec1..098b09b65 100644 --- a/tests/test_versioning.py +++ b/tests/test_versioning.py @@ -1,8 +1,9 @@ import pytest -from django.conf.urls import include, url +from django.conf.urls import url from django.test import override_settings from rest_framework import serializers, status, versioning +from rest_framework.compat import include from rest_framework.decorators import APIView from rest_framework.relations import PKOnlyObject from rest_framework.response import Response @@ -170,7 +171,7 @@ class TestURLReversing(URLPatternsTestCase): ] urlpatterns = [ - url(r'^v1/', include(included, namespace='v1')), + url(r'^v1/', include(included, namespace='v1', app_name='v1')), url(r'^another/$', dummy_view, name='another'), url(r'^(?P[v1|v2]+)/another/$', dummy_view, name='another'), ] @@ -335,8 +336,8 @@ class TestHyperlinkedRelatedField(URLPatternsTestCase): ] urlpatterns = [ - url(r'^v1/', include(included, namespace='v1')), - url(r'^v2/', include(included, namespace='v2')) + url(r'^v1/', include(included, namespace='v1', app_name='v1')), + url(r'^v2/', include(included, namespace='v2', app_name='v2')) ] def setUp(self): @@ -367,7 +368,7 @@ class TestNamespaceVersioningHyperlinkedRelatedFieldScheme(URLPatternsTestCase): ] included = [ url(r'^namespaced/(?P\d+)/$', dummy_pk_view, name='namespaced'), - url(r'^nested/', include(nested, namespace='nested-namespace')) + url(r'^nested/', include(nested, namespace='nested-namespace', app_name='nested-namespace')) ] urlpatterns = [ From d610d150f1f2bceb7769c15c0214567870013a41 Mon Sep 17 00:00:00 2001 From: Artem Muterko Date: Tue, 31 Jan 2017 16:51:09 +0200 Subject: [PATCH 6/8] Add test for pagination when limit not set --- tests/test_pagination.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_pagination.py b/tests/test_pagination.py index 9f2e1c57c..dd7f70330 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -370,6 +370,13 @@ class TestLimitOffset: assert self.pagination.display_page_controls assert isinstance(self.pagination.to_html(), type('')) + def test_pagination_not_applied_if_limit_or_default_limit_not_set(self): + class MockPagination(pagination.LimitOffsetPagination): + default_limit = None + request = Request(factory.get('/')) + queryset = MockPagination().paginate_queryset(self.queryset, request) + assert queryset is None + def test_single_offset(self): """ When the offset is not a multiple of the limit we get some edge cases: From b99272c4251b718a1ea3715205abf1fd3be89039 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 31 Jan 2017 20:57:52 +0100 Subject: [PATCH 7/8] Fixed `dedent` for tab indent. --- rest_framework/utils/formatting.py | 25 ++++++++++--------------- tests/test_description.py | 6 +++++- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py index ca5b33c5e..78cb37e56 100644 --- a/rest_framework/utils/formatting.py +++ b/rest_framework/utils/formatting.py @@ -32,23 +32,18 @@ def dedent(content): unindented text on the initial line. """ content = force_text(content) - whitespace_counts = [ - len(line) - len(line.lstrip(' ')) - for line in content.splitlines()[1:] if line.lstrip() - ] - tab_counts = [ - len(line) - len(line.lstrip('\t')) - for line in content.splitlines()[1:] if line.lstrip() - ] + lines = [line for line in content.splitlines()[1:] if line.lstrip()] # unindent the content if needed - if whitespace_counts: - whitespace_pattern = '^' + (' ' * min(whitespace_counts)) - content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content) - elif tab_counts: - whitespace_pattern = '^' + ('\t' * min(whitespace_counts)) - content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content) - + if lines: + whitespace_counts = min([len(line) - len(line.lstrip(' ')) for line in lines]) + tab_counts = min([len(line) - len(line.lstrip('\t')) for line in lines]) + if whitespace_counts: + whitespace_pattern = '^' + (' ' * whitespace_counts) + content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content) + elif tab_counts: + whitespace_pattern = '^' + ('\t' * tab_counts) + content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content) return content.strip() diff --git a/tests/test_description.py b/tests/test_description.py index 08d8bddec..001a3ea21 100644 --- a/tests/test_description.py +++ b/tests/test_description.py @@ -124,4 +124,8 @@ class TestViewNamesAndDescriptions(TestCase): def test_dedent_tabs(): - assert dedent("\tfirst string\n\n\tsecond string") == 'first string\n\n\tsecond string' + result = 'first string\n\nsecond string' + assert dedent(" first string\n\n second string") == result + assert dedent("first string\n\n second string") == result + assert dedent("\tfirst string\n\n\tsecond string") == result + assert dedent("first string\n\n\tsecond string") == result From 2ec3db81777234c7038c0d6a4edde5162fc1edc6 Mon Sep 17 00:00:00 2001 From: Mohammad Ashraful Islam Date: Wed, 1 Feb 2017 18:20:06 +0600 Subject: [PATCH 8/8] fixed url checker ':' to 'http' (#4678) --- rest_framework/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/test.py b/rest_framework/test.py index 241f94c91..87255bca0 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -114,7 +114,7 @@ if requests is not None: self.mount('https://', adapter) def request(self, method, url, *args, **kwargs): - if ':' not in url: + if not url.startswith('http'): raise ValueError('Missing "http:" or "https:". Use a fully qualified URL, eg "http://testserver%s"' % url) return super(RequestsClient, self).request(method, url, *args, **kwargs)