aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorXavier Ordoquy2014-05-01 08:01:38 +0200
committerXavier Ordoquy2014-05-01 08:01:38 +0200
commit7b4463f73983e36f228e6af0ff8c921d4698a9b3 (patch)
treec26fd01425a3754e65a0b2c48694ca734278745f /tests
parent2aca69a94601858a462060bc55154c812f70fb91 (diff)
parentc9e6f31166ebccc5c3bf2f27e12a6d6c87f5cf22 (diff)
downloaddjango-rest-framework-7b4463f73983e36f228e6af0ff8c921d4698a9b3.tar.bz2
Merge remote-tracking branch 'reference/2.4.0' into feature/pytest
Conflicts: rest_framework/runtests/urls.py tests/test_response.py tox.ini
Diffstat (limited to 'tests')
-rw-r--r--tests/settings.py5
-rw-r--r--tests/test_authentication.py3
-rw-r--r--tests/test_breadcrumbs.py2
-rw-r--r--tests/test_filters.py4
-rw-r--r--tests/test_genericrelations.py2
-rw-r--r--tests/test_htmlrenderer.py2
-rw-r--r--tests/test_hyperlinkedserializers.py4
-rw-r--r--tests/test_nullable_fields.py2
-rw-r--r--tests/test_relations_hyperlink.py2
-rw-r--r--tests/test_renderers.py3
-rw-r--r--tests/test_request.py2
-rw-r--r--tests/test_response.py2
-rw-r--r--tests/test_reverse.py2
-rw-r--r--tests/test_routers.py89
-rw-r--r--tests/test_serializer.py28
-rw-r--r--tests/test_testing.py2
-rw-r--r--tests/test_throttling.py66
-rw-r--r--tests/test_urlpatterns.py2
18 files changed, 188 insertions, 34 deletions
diff --git a/tests/settings.py b/tests/settings.py
index 75f7c54b..de41dc66 100644
--- a/tests/settings.py
+++ b/tests/settings.py
@@ -93,10 +93,7 @@ INSTALLED_APPS = (
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
- # Uncomment the next line to enable the admin:
- # 'django.contrib.admin',
- # Uncomment the next line to enable admin documentation:
- # 'django.contrib.admindocs',
+ 'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'tests',
diff --git a/tests/test_authentication.py b/tests/test_authentication.py
index d0290eac..5b97d60b 100644
--- a/tests/test_authentication.py
+++ b/tests/test_authentication.py
@@ -1,4 +1,5 @@
from __future__ import unicode_literals
+from django.conf.urls import patterns, url, include
from django.contrib.auth.models import User
from django.http import HttpResponse
from django.test import TestCase
@@ -19,7 +20,7 @@ from rest_framework.authentication import (
OAuth2Authentication
)
from rest_framework.authtoken.models import Token
-from rest_framework.compat import patterns, url, include, six
+from rest_framework.compat import six
from rest_framework.compat import oauth2_provider, oauth2_provider_scope
from rest_framework.compat import oauth, oauth_provider
from rest_framework.test import APIRequestFactory, APIClient
diff --git a/tests/test_breadcrumbs.py b/tests/test_breadcrumbs.py
index 78edc603..f26c3eaf 100644
--- a/tests/test_breadcrumbs.py
+++ b/tests/test_breadcrumbs.py
@@ -1,6 +1,6 @@
from __future__ import unicode_literals
+from django.conf.urls import patterns, url
from django.test import TestCase
-from rest_framework.compat import patterns, url
from rest_framework.utils.breadcrumbs import get_breadcrumbs
from rest_framework.views import APIView
diff --git a/tests/test_filters.py b/tests/test_filters.py
index 3c6e8857..85840e01 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -5,9 +5,9 @@ from django.db import models
from django.core.urlresolvers import reverse
from django.test import TestCase
from django.utils import unittest
+from django.conf.urls import patterns, url
from rest_framework import generics, serializers, status, filters
-from rest_framework.compat import django_filters, patterns, url
-from rest_framework.settings import api_settings
+from rest_framework.compat import django_filters
from rest_framework.test import APIRequestFactory
from .models import FilterableItem, BasicModel
from .utils import temporary_setting
diff --git a/tests/test_genericrelations.py b/tests/test_genericrelations.py
index 46a2d863..3a8f3c7f 100644
--- a/tests/test_genericrelations.py
+++ b/tests/test_genericrelations.py
@@ -84,7 +84,7 @@ class TestGenericRelations(TestCase):
exclude = ('content_type', 'object_id')
class BookmarkSerializer(serializers.ModelSerializer):
- tags = TagSerializer()
+ tags = TagSerializer(many=True)
class Meta:
model = Bookmark
diff --git a/tests/test_htmlrenderer.py b/tests/test_htmlrenderer.py
index 8af5bb50..88d11c46 100644
--- a/tests/test_htmlrenderer.py
+++ b/tests/test_htmlrenderer.py
@@ -1,11 +1,11 @@
from __future__ import unicode_literals
from django.core.exceptions import PermissionDenied
+from django.conf.urls import patterns, url
from django.http import Http404
from django.test import TestCase
from django.template import TemplateDoesNotExist, Template
import django.template.loader
from rest_framework import status
-from rest_framework.compat import patterns, url
from rest_framework.decorators import api_view, renderer_classes
from rest_framework.renderers import TemplateHTMLRenderer
from rest_framework.response import Response
diff --git a/tests/test_hyperlinkedserializers.py b/tests/test_hyperlinkedserializers.py
index eee179ca..d478ea73 100644
--- a/tests/test_hyperlinkedserializers.py
+++ b/tests/test_hyperlinkedserializers.py
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
import json
from django.test import TestCase
from rest_framework import generics, status, serializers
-from rest_framework.compat import patterns, url
+from django.conf.urls import patterns, url
from rest_framework.settings import api_settings
from rest_framework.test import APIRequestFactory
from tests.models import (
@@ -25,7 +25,7 @@ class BlogPostCommentSerializer(serializers.ModelSerializer):
class PhotoSerializer(serializers.Serializer):
description = serializers.CharField()
- album_url = serializers.HyperlinkedRelatedField(source='album', view_name='album-detail', queryset=Album.objects.all(), lookup_field='title', slug_url_kwarg='title')
+ album_url = serializers.HyperlinkedRelatedField(source='album', view_name='album-detail', queryset=Album.objects.all(), lookup_field='title')
def restore_object(self, attrs, instance=None):
return Photo(**attrs)
diff --git a/tests/test_nullable_fields.py b/tests/test_nullable_fields.py
index 33a9685f..0c133fc2 100644
--- a/tests/test_nullable_fields.py
+++ b/tests/test_nullable_fields.py
@@ -1,6 +1,6 @@
from django.core.urlresolvers import reverse
-from rest_framework.compat import patterns, url
+from django.conf.urls import patterns, url
from rest_framework.test import APITestCase
from tests.models import NullableForeignKeySource
from tests.serializers import NullableFKSourceSerializer
diff --git a/tests/test_relations_hyperlink.py b/tests/test_relations_hyperlink.py
index 98f68d29..ab1c6664 100644
--- a/tests/test_relations_hyperlink.py
+++ b/tests/test_relations_hyperlink.py
@@ -1,7 +1,7 @@
from __future__ import unicode_literals
+from django.conf.urls import patterns, url
from django.test import TestCase
from rest_framework import serializers
-from rest_framework.compat import patterns, url
from rest_framework.test import APIRequestFactory
from tests.models import (
BlogPost,
diff --git a/tests/test_renderers.py b/tests/test_renderers.py
index f733d6b6..1d8adfa7 100644
--- a/tests/test_renderers.py
+++ b/tests/test_renderers.py
@@ -2,13 +2,14 @@
from __future__ import unicode_literals
from decimal import Decimal
+from django.conf.urls import patterns, url, include
from django.core.cache import cache
from django.db import models
from django.test import TestCase
from django.utils import unittest
from django.utils.translation import ugettext_lazy as _
from rest_framework import status, permissions
-from rest_framework.compat import yaml, etree, patterns, url, include, six, StringIO
+from rest_framework.compat import yaml, etree, six, StringIO
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
diff --git a/tests/test_request.py b/tests/test_request.py
index 0a9355f0..0cde0fb4 100644
--- a/tests/test_request.py
+++ b/tests/test_request.py
@@ -2,6 +2,7 @@
Tests for content parsing, and form-overloaded content parsing.
"""
from __future__ import unicode_literals
+from django.conf.urls import patterns
from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login, logout
from django.contrib.sessions.middleware import SessionMiddleware
@@ -9,7 +10,6 @@ from django.core.handlers.wsgi import WSGIRequest
from django.test import TestCase
from rest_framework import status
from rest_framework.authentication import SessionAuthentication
-from rest_framework.compat import patterns
from rest_framework.parsers import (
BaseParser,
FormParser,
diff --git a/tests/test_response.py b/tests/test_response.py
index 41c0f49d..0551f4a8 100644
--- a/tests/test_response.py
+++ b/tests/test_response.py
@@ -1,7 +1,7 @@
from __future__ import unicode_literals
+from django.conf.urls import patterns, url, include
from django.test import TestCase
from tests.models import BasicModel, BasicModelSerializer
-from rest_framework.compat import patterns, url, include
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import generics
diff --git a/tests/test_reverse.py b/tests/test_reverse.py
index 3d14a28f..0d3fddf0 100644
--- a/tests/test_reverse.py
+++ b/tests/test_reverse.py
@@ -1,6 +1,6 @@
from __future__ import unicode_literals
+from django.conf.urls import patterns, url
from django.test import TestCase
-from rest_framework.compat import patterns, url
from rest_framework.reverse import reverse
from rest_framework.test import APIRequestFactory
diff --git a/tests/test_routers.py b/tests/test_routers.py
index 084c0e27..381569bd 100644
--- a/tests/test_routers.py
+++ b/tests/test_routers.py
@@ -1,10 +1,10 @@
from __future__ import unicode_literals
+from django.conf.urls import patterns, url, include
from django.db import models
from django.test import TestCase
from django.core.exceptions import ImproperlyConfigured
from rest_framework import serializers, viewsets, permissions
-from rest_framework.compat import include, patterns, url
-from rest_framework.decorators import link, action
+from rest_framework.decorators import detail_route, list_route
from rest_framework.response import Response
from rest_framework.routers import SimpleRouter, DefaultRouter
from rest_framework.test import APIRequestFactory
@@ -18,23 +18,23 @@ class BasicViewSet(viewsets.ViewSet):
def list(self, request, *args, **kwargs):
return Response({'method': 'list'})
- @action()
+ @detail_route(methods=['post'])
def action1(self, request, *args, **kwargs):
return Response({'method': 'action1'})
- @action()
+ @detail_route(methods=['post'])
def action2(self, request, *args, **kwargs):
return Response({'method': 'action2'})
- @action(methods=['post', 'delete'])
+ @detail_route(methods=['post', 'delete'])
def action3(self, request, *args, **kwargs):
return Response({'method': 'action2'})
- @link()
+ @detail_route()
def link1(self, request, *args, **kwargs):
return Response({'method': 'link1'})
- @link()
+ @detail_route()
def link2(self, request, *args, **kwargs):
return Response({'method': 'link2'})
@@ -121,6 +121,27 @@ class TestCustomLookupFields(TestCase):
)
+class TestLookupValueRegex(TestCase):
+ """
+ Ensure the router honors lookup_value_regex when applied
+ to the viewset.
+ """
+ def setUp(self):
+ class NoteViewSet(viewsets.ModelViewSet):
+ queryset = RouterTestModel.objects.all()
+ lookup_field = 'uuid'
+ lookup_value_regex = '[0-9a-f]{32}'
+
+ self.router = SimpleRouter()
+ self.router.register(r'notes', NoteViewSet)
+ self.urls = self.router.urls
+
+ def test_urls_limited_by_lookup_value_regex(self):
+ expected = ['^notes/$', '^notes/(?P<uuid>[0-9a-f]{32})/$']
+ for idx in range(len(expected)):
+ self.assertEqual(expected[idx], self.urls[idx].regex.pattern)
+
+
class TestTrailingSlashIncluded(TestCase):
def setUp(self):
class NoteViewSet(viewsets.ModelViewSet):
@@ -131,7 +152,7 @@ class TestTrailingSlashIncluded(TestCase):
self.urls = self.router.urls
def test_urls_have_trailing_slash_by_default(self):
- expected = ['^notes/$', '^notes/(?P<pk>[^/]+)/$']
+ expected = ['^notes/$', '^notes/(?P<pk>[^/.]+)/$']
for idx in range(len(expected)):
self.assertEqual(expected[idx], self.urls[idx].regex.pattern)
@@ -175,7 +196,7 @@ class TestActionKeywordArgs(TestCase):
class TestViewSet(viewsets.ModelViewSet):
permission_classes = []
- @action(permission_classes=[permissions.AllowAny])
+ @detail_route(methods=['post'], permission_classes=[permissions.AllowAny])
def custom(self, request, *args, **kwargs):
return Response({
'permission_classes': self.permission_classes
@@ -196,14 +217,14 @@ class TestActionKeywordArgs(TestCase):
class TestActionAppliedToExistingRoute(TestCase):
"""
- Ensure `@action` decorator raises an except when applied
+ Ensure `@detail_route` decorator raises an except when applied
to an existing route
"""
def test_exception_raised_when_action_applied_to_existing_route(self):
class TestViewSet(viewsets.ModelViewSet):
- @action()
+ @detail_route(methods=['post'])
def retrieve(self, request, *args, **kwargs):
return Response({
'hello': 'world'
@@ -214,3 +235,49 @@ class TestActionAppliedToExistingRoute(TestCase):
with self.assertRaises(ImproperlyConfigured):
self.router.urls
+
+
+class DynamicListAndDetailViewSet(viewsets.ViewSet):
+ def list(self, request, *args, **kwargs):
+ return Response({'method': 'list'})
+
+ @list_route(methods=['post'])
+ def list_route_post(self, request, *args, **kwargs):
+ return Response({'method': 'action1'})
+
+ @detail_route(methods=['post'])
+ def detail_route_post(self, request, *args, **kwargs):
+ return Response({'method': 'action2'})
+
+ @list_route()
+ def list_route_get(self, request, *args, **kwargs):
+ return Response({'method': 'link1'})
+
+ @detail_route()
+ def detail_route_get(self, request, *args, **kwargs):
+ return Response({'method': 'link2'})
+
+
+class TestDynamicListAndDetailRouter(TestCase):
+ def setUp(self):
+ self.router = SimpleRouter()
+
+ def test_list_and_detail_route_decorators(self):
+ routes = self.router.get_routes(DynamicListAndDetailViewSet)
+ decorator_routes = [r for r in routes if not (r.name.endswith('-list') or r.name.endswith('-detail'))]
+ # Make sure all these endpoints exist and none have been clobbered
+ for i, endpoint in enumerate(['list_route_get', 'list_route_post', 'detail_route_get', 'detail_route_post']):
+ route = decorator_routes[i]
+ # check url listing
+ if endpoint.startswith('list_'):
+ self.assertEqual(route.url,
+ '^{{prefix}}/{0}{{trailing_slash}}$'.format(endpoint))
+ else:
+ self.assertEqual(route.url,
+ '^{{prefix}}/{{lookup}}/{0}{{trailing_slash}}$'.format(endpoint))
+ # check method to function mapping
+ if endpoint.endswith('_post'):
+ method_map = 'post'
+ else:
+ method_map = 'get'
+ self.assertEqual(route.mapping[method_map], endpoint)
diff --git a/tests/test_serializer.py b/tests/test_serializer.py
index 73eb5c79..98936ae8 100644
--- a/tests/test_serializer.py
+++ b/tests/test_serializer.py
@@ -30,6 +30,7 @@ if PIL is not None:
image_field = models.ImageField(upload_to='test', max_length=1024, blank=True)
slug_field = models.SlugField(max_length=1024, blank=True)
url_field = models.URLField(max_length=1024, blank=True)
+ nullable_char_field = models.CharField(max_length=1024, blank=True, null=True)
class DVOAFModel(RESTFrameworkModel):
positive_integer_field = models.PositiveIntegerField(blank=True)
@@ -660,7 +661,7 @@ class ModelValidationTests(TestCase):
second_serializer = AlbumsSerializer(data={'title': 'a'})
self.assertFalse(second_serializer.is_valid())
self.assertEqual(second_serializer.errors, {'title': ['Album with this Title already exists.'],})
- third_serializer = AlbumsSerializer(data=[{'title': 'b', 'ref': '1'}, {'title': 'c'}])
+ third_serializer = AlbumsSerializer(data=[{'title': 'b', 'ref': '1'}, {'title': 'c'}], many=True)
self.assertFalse(third_serializer.is_valid())
self.assertEqual(third_serializer.errors, [{'ref': ['Album with this Ref already exists.']}, {}])
@@ -1257,6 +1258,20 @@ class BlankFieldTests(TestCase):
serializer = self.model_serializer_class(data={})
self.assertEqual(serializer.is_valid(), True)
+ def test_create_model_null_field_save(self):
+ """
+ Regression test for #1330.
+
+ https://github.com/tomchristie/django-rest-framework/pull/1330
+ """
+ serializer = self.model_serializer_class(data={'title': None})
+ self.assertEqual(serializer.is_valid(), True)
+
+ try:
+ serializer.save()
+ except Exception:
+ self.fail('Exception raised on save() after validation passes')
+
#test for issue #460
class SerializerPickleTests(TestCase):
@@ -1491,7 +1506,7 @@ class NestedSerializerContextTests(TestCase):
model = Album
fields = ("photo_set", "callable")
- photo_set = PhotoSerializer(source="photo_set")
+ photo_set = PhotoSerializer(source="photo_set", many=True)
callable = serializers.SerializerMethodField("_callable")
def _callable(self, instance):
@@ -1503,7 +1518,7 @@ class NestedSerializerContextTests(TestCase):
albums = None
class AlbumCollectionSerializer(serializers.Serializer):
- albums = AlbumSerializer(source="albums")
+ albums = AlbumSerializer(source="albums", many=True)
album1 = Album.objects.create(title="album 1")
album2 = Album.objects.create(title="album 2")
@@ -1660,6 +1675,10 @@ class AttributeMappingOnAutogeneratedFieldsTests(TestCase):
'url_field': [
('max_length', 1024),
],
+ 'nullable_char_field': [
+ ('max_length', 1024),
+ ('allow_none', True),
+ ],
}
def field_test(self, field):
@@ -1696,6 +1715,9 @@ class AttributeMappingOnAutogeneratedFieldsTests(TestCase):
def test_url_field(self):
self.field_test('url_field')
+ def test_nullable_char_field(self):
+ self.field_test('nullable_char_field')
+
@unittest.skipUnless(PIL is not None, 'PIL is not installed')
class DefaultValuesOnAutogeneratedFieldsTests(TestCase):
diff --git a/tests/test_testing.py b/tests/test_testing.py
index bd3e1329..e2e4e217 100644
--- a/tests/test_testing.py
+++ b/tests/test_testing.py
@@ -1,11 +1,11 @@
# -- coding: utf-8 --
from __future__ import unicode_literals
+from django.conf.urls import patterns, url
from io import BytesIO
from django.contrib.auth.models import User
from django.test import TestCase
-from rest_framework.compat import patterns, url
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.test import APIClient, APIRequestFactory, force_authenticate
diff --git a/tests/test_throttling.py b/tests/test_throttling.py
index 41bff692..8c5eefe9 100644
--- a/tests/test_throttling.py
+++ b/tests/test_throttling.py
@@ -5,6 +5,7 @@ from __future__ import unicode_literals
from django.test import TestCase
from django.contrib.auth.models import User
from django.core.cache import cache
+from rest_framework.settings import api_settings
from rest_framework.test import APIRequestFactory
from rest_framework.views import APIView
from rest_framework.throttling import BaseThrottle, UserRateThrottle, ScopedRateThrottle
@@ -275,3 +276,68 @@ class ScopedRateThrottleTests(TestCase):
self.increment_timer()
response = self.unscoped_view(request)
self.assertEqual(200, response.status_code)
+
+
+class XffTestingBase(TestCase):
+ def setUp(self):
+
+ class Throttle(ScopedRateThrottle):
+ THROTTLE_RATES = {'test_limit': '1/day'}
+ TIMER_SECONDS = 0
+ timer = lambda self: self.TIMER_SECONDS
+
+ class View(APIView):
+ throttle_classes = (Throttle,)
+ throttle_scope = 'test_limit'
+
+ def get(self, request):
+ return Response('test_limit')
+
+ cache.clear()
+ self.throttle = Throttle()
+ self.view = View.as_view()
+ self.request = APIRequestFactory().get('/some_uri')
+ self.request.META['REMOTE_ADDR'] = '3.3.3.3'
+ self.request.META['HTTP_X_FORWARDED_FOR'] = '0.0.0.0, 1.1.1.1, 2.2.2.2'
+
+ def config_proxy(self, num_proxies):
+ setattr(api_settings, 'NUM_PROXIES', num_proxies)
+
+
+class IdWithXffBasicTests(XffTestingBase):
+ def test_accepts_request_under_limit(self):
+ self.config_proxy(0)
+ self.assertEqual(200, self.view(self.request).status_code)
+
+ def test_denies_request_over_limit(self):
+ self.config_proxy(0)
+ self.view(self.request)
+ self.assertEqual(429, self.view(self.request).status_code)
+
+
+class XffSpoofingTests(XffTestingBase):
+ def test_xff_spoofing_doesnt_change_machine_id_with_one_app_proxy(self):
+ self.config_proxy(1)
+ self.view(self.request)
+ self.request.META['HTTP_X_FORWARDED_FOR'] = '4.4.4.4, 5.5.5.5, 2.2.2.2'
+ self.assertEqual(429, self.view(self.request).status_code)
+
+ def test_xff_spoofing_doesnt_change_machine_id_with_two_app_proxies(self):
+ self.config_proxy(2)
+ self.view(self.request)
+ self.request.META['HTTP_X_FORWARDED_FOR'] = '4.4.4.4, 1.1.1.1, 2.2.2.2'
+ self.assertEqual(429, self.view(self.request).status_code)
+
+
+class XffUniqueMachinesTest(XffTestingBase):
+ def test_unique_clients_are_counted_independently_with_one_proxy(self):
+ self.config_proxy(1)
+ self.view(self.request)
+ self.request.META['HTTP_X_FORWARDED_FOR'] = '0.0.0.0, 1.1.1.1, 7.7.7.7'
+ self.assertEqual(200, self.view(self.request).status_code)
+
+ def test_unique_clients_are_counted_independently_with_two_proxies(self):
+ self.config_proxy(2)
+ self.view(self.request)
+ self.request.META['HTTP_X_FORWARDED_FOR'] = '0.0.0.0, 7.7.7.7, 2.2.2.2'
+ self.assertEqual(200, self.view(self.request).status_code)
diff --git a/tests/test_urlpatterns.py b/tests/test_urlpatterns.py
index 8132ec4c..e0060e69 100644
--- a/tests/test_urlpatterns.py
+++ b/tests/test_urlpatterns.py
@@ -1,9 +1,9 @@
from __future__ import unicode_literals
from collections import namedtuple
+from django.conf.urls import patterns, url, include
from django.core import urlresolvers
from django.test import TestCase
from rest_framework.test import APIRequestFactory
-from rest_framework.compat import patterns, url, include
from rest_framework.urlpatterns import format_suffix_patterns