mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-10-31 16:07:38 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			400 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			400 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import pytest
 | |
| from django.test import override_settings
 | |
| from django.urls import ResolverMatch, include, path, re_path
 | |
| 
 | |
| from rest_framework import serializers, status, versioning
 | |
| from rest_framework.decorators import APIView
 | |
| from rest_framework.relations import PKOnlyObject
 | |
| from rest_framework.response import Response
 | |
| from rest_framework.reverse import reverse
 | |
| from rest_framework.test import (
 | |
|     APIRequestFactory, APITestCase, URLPatternsTestCase
 | |
| )
 | |
| from rest_framework.versioning import NamespaceVersioning
 | |
| 
 | |
| 
 | |
| class RequestVersionView(APIView):
 | |
|     def get(self, request, *args, **kwargs):
 | |
|         return Response({'version': request.version})
 | |
| 
 | |
| 
 | |
| class ReverseView(APIView):
 | |
|     def get(self, request, *args, **kwargs):
 | |
|         return Response({'url': reverse('another', request=request)})
 | |
| 
 | |
| 
 | |
| class AllowedVersionsView(RequestVersionView):
 | |
|     def determine_version(self, request, *args, **kwargs):
 | |
|         scheme = self.versioning_class()
 | |
|         scheme.allowed_versions = ('v1', 'v2')
 | |
|         return (scheme.determine_version(request, *args, **kwargs), scheme)
 | |
| 
 | |
| 
 | |
| class AllowedAndDefaultVersionsView(RequestVersionView):
 | |
|     def determine_version(self, request, *args, **kwargs):
 | |
|         scheme = self.versioning_class()
 | |
|         scheme.allowed_versions = ('v1', 'v2')
 | |
|         scheme.default_version = 'v2'
 | |
|         return (scheme.determine_version(request, *args, **kwargs), scheme)
 | |
| 
 | |
| 
 | |
| class AllowedWithNoneVersionsView(RequestVersionView):
 | |
|     def determine_version(self, request, *args, **kwargs):
 | |
|         scheme = self.versioning_class()
 | |
|         scheme.allowed_versions = ('v1', 'v2', None)
 | |
|         return (scheme.determine_version(request, *args, **kwargs), scheme)
 | |
| 
 | |
| 
 | |
| class AllowedWithNoneAndDefaultVersionsView(RequestVersionView):
 | |
|     def determine_version(self, request, *args, **kwargs):
 | |
|         scheme = self.versioning_class()
 | |
|         scheme.allowed_versions = ('v1', 'v2', None)
 | |
|         scheme.default_version = 'v2'
 | |
|         return (scheme.determine_version(request, *args, **kwargs), scheme)
 | |
| 
 | |
| 
 | |
| factory = APIRequestFactory()
 | |
| 
 | |
| 
 | |
| def dummy_view(request):
 | |
|     pass
 | |
| 
 | |
| 
 | |
| def dummy_pk_view(request, pk):
 | |
|     pass
 | |
| 
 | |
| 
 | |
| class TestRequestVersion:
 | |
|     def test_unversioned(self):
 | |
|         view = RequestVersionView.as_view()
 | |
| 
 | |
|         request = factory.get('/endpoint/')
 | |
|         response = view(request)
 | |
|         assert response.data == {'version': None}
 | |
| 
 | |
|     def test_query_param_versioning(self):
 | |
|         scheme = versioning.QueryParameterVersioning
 | |
|         view = RequestVersionView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/endpoint/?version=1.2.3')
 | |
|         response = view(request)
 | |
|         assert response.data == {'version': '1.2.3'}
 | |
| 
 | |
|         request = factory.get('/endpoint/')
 | |
|         response = view(request)
 | |
|         assert response.data == {'version': None}
 | |
| 
 | |
|     @override_settings(ALLOWED_HOSTS=['*'])
 | |
|     def test_host_name_versioning(self):
 | |
|         scheme = versioning.HostNameVersioning
 | |
|         view = RequestVersionView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/endpoint/', HTTP_HOST='v1.example.org')
 | |
|         response = view(request)
 | |
|         assert response.data == {'version': 'v1'}
 | |
| 
 | |
|         request = factory.get('/endpoint/')
 | |
|         response = view(request)
 | |
|         assert response.data == {'version': None}
 | |
| 
 | |
|     def test_accept_header_versioning(self):
 | |
|         scheme = versioning.AcceptHeaderVersioning
 | |
|         view = RequestVersionView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/endpoint/', HTTP_ACCEPT='application/json; version=1.2.3')
 | |
|         response = view(request)
 | |
|         assert response.data == {'version': '1.2.3'}
 | |
| 
 | |
|         request = factory.get('/endpoint/', HTTP_ACCEPT='*/*; version=1.2.3')
 | |
|         response = view(request)
 | |
|         assert response.data == {'version': '1.2.3'}
 | |
| 
 | |
|         request = factory.get('/endpoint/', HTTP_ACCEPT='application/json')
 | |
|         response = view(request)
 | |
|         assert response.data == {'version': None}
 | |
| 
 | |
|     def test_url_path_versioning(self):
 | |
|         scheme = versioning.URLPathVersioning
 | |
|         view = RequestVersionView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/1.2.3/endpoint/')
 | |
|         response = view(request, version='1.2.3')
 | |
|         assert response.data == {'version': '1.2.3'}
 | |
| 
 | |
|         request = factory.get('/endpoint/')
 | |
|         response = view(request)
 | |
|         assert response.data == {'version': None}
 | |
| 
 | |
|     def test_namespace_versioning(self):
 | |
|         class FakeResolverMatch(ResolverMatch):
 | |
|             namespace = 'v1'
 | |
| 
 | |
|         scheme = versioning.NamespaceVersioning
 | |
|         view = RequestVersionView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/v1/endpoint/')
 | |
|         request.resolver_match = FakeResolverMatch
 | |
|         response = view(request, version='v1')
 | |
|         assert response.data == {'version': 'v1'}
 | |
| 
 | |
|         request = factory.get('/endpoint/')
 | |
|         response = view(request)
 | |
|         assert response.data == {'version': None}
 | |
| 
 | |
| 
 | |
| class TestURLReversing(URLPatternsTestCase, APITestCase):
 | |
|     included = [
 | |
|         path('namespaced/', dummy_view, name='another'),
 | |
|         path('example/<int:pk>/', dummy_pk_view, name='example-detail')
 | |
|     ]
 | |
| 
 | |
|     urlpatterns = [
 | |
|         path('v1/', include((included, 'v1'), namespace='v1')),
 | |
|         path('another/', dummy_view, name='another'),
 | |
|         re_path(r'^(?P<version>[v1|v2]+)/another/$', dummy_view, name='another'),
 | |
|         re_path(r'^(?P<foo>.+)/unversioned/$', dummy_view, name='unversioned'),
 | |
| 
 | |
|     ]
 | |
| 
 | |
|     def test_reverse_unversioned(self):
 | |
|         view = ReverseView.as_view()
 | |
| 
 | |
|         request = factory.get('/endpoint/')
 | |
|         response = view(request)
 | |
|         assert response.data == {'url': 'http://testserver/another/'}
 | |
| 
 | |
|     def test_reverse_query_param_versioning(self):
 | |
|         scheme = versioning.QueryParameterVersioning
 | |
|         view = ReverseView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/endpoint/?version=v1')
 | |
|         response = view(request)
 | |
|         assert response.data == {'url': 'http://testserver/another/?version=v1'}
 | |
| 
 | |
|         request = factory.get('/endpoint/')
 | |
|         response = view(request)
 | |
|         assert response.data == {'url': 'http://testserver/another/'}
 | |
| 
 | |
|     @override_settings(ALLOWED_HOSTS=['*'])
 | |
|     def test_reverse_host_name_versioning(self):
 | |
|         scheme = versioning.HostNameVersioning
 | |
|         view = ReverseView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/endpoint/', HTTP_HOST='v1.example.org')
 | |
|         response = view(request)
 | |
|         assert response.data == {'url': 'http://v1.example.org/another/'}
 | |
| 
 | |
|         request = factory.get('/endpoint/')
 | |
|         response = view(request)
 | |
|         assert response.data == {'url': 'http://testserver/another/'}
 | |
| 
 | |
|     def test_reverse_url_path_versioning(self):
 | |
|         scheme = versioning.URLPathVersioning
 | |
|         view = ReverseView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/v1/endpoint/')
 | |
|         response = view(request, version='v1')
 | |
|         assert response.data == {'url': 'http://testserver/v1/another/'}
 | |
| 
 | |
|         request = factory.get('/endpoint/')
 | |
|         response = view(request)
 | |
|         assert response.data == {'url': 'http://testserver/another/'}
 | |
| 
 | |
|         # Test fallback when kwargs is not None
 | |
|         request = factory.get('/v1/endpoint/')
 | |
|         request.versioning_scheme = scheme()
 | |
|         request.version = 'v1'
 | |
| 
 | |
|         reversed_url = reverse('unversioned', request=request, kwargs={'foo': 'bar'})
 | |
|         assert reversed_url == 'http://testserver/bar/unversioned/'
 | |
| 
 | |
|     def test_reverse_namespace_versioning(self):
 | |
|         class FakeResolverMatch(ResolverMatch):
 | |
|             namespace = 'v1'
 | |
| 
 | |
|         scheme = versioning.NamespaceVersioning
 | |
|         view = ReverseView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/v1/endpoint/')
 | |
|         request.resolver_match = FakeResolverMatch
 | |
|         response = view(request, version='v1')
 | |
|         assert response.data == {'url': 'http://testserver/v1/namespaced/'}
 | |
| 
 | |
|         request = factory.get('/endpoint/')
 | |
|         response = view(request)
 | |
|         assert response.data == {'url': 'http://testserver/another/'}
 | |
| 
 | |
| 
 | |
| class TestInvalidVersion:
 | |
|     def test_invalid_query_param_versioning(self):
 | |
|         scheme = versioning.QueryParameterVersioning
 | |
|         view = AllowedVersionsView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/endpoint/?version=v3')
 | |
|         response = view(request)
 | |
|         assert response.status_code == status.HTTP_404_NOT_FOUND
 | |
| 
 | |
|     @override_settings(ALLOWED_HOSTS=['*'])
 | |
|     def test_invalid_host_name_versioning(self):
 | |
|         scheme = versioning.HostNameVersioning
 | |
|         view = AllowedVersionsView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/endpoint/', HTTP_HOST='v3.example.org')
 | |
|         response = view(request)
 | |
|         assert response.status_code == status.HTTP_404_NOT_FOUND
 | |
| 
 | |
|     def test_invalid_accept_header_versioning(self):
 | |
|         scheme = versioning.AcceptHeaderVersioning
 | |
|         view = AllowedVersionsView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/endpoint/', HTTP_ACCEPT='application/json; version=v3')
 | |
|         response = view(request)
 | |
|         assert response.status_code == status.HTTP_406_NOT_ACCEPTABLE
 | |
| 
 | |
|     def test_invalid_url_path_versioning(self):
 | |
|         scheme = versioning.URLPathVersioning
 | |
|         view = AllowedVersionsView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/v3/endpoint/')
 | |
|         response = view(request, version='v3')
 | |
|         assert response.status_code == status.HTTP_404_NOT_FOUND
 | |
| 
 | |
|     def test_invalid_namespace_versioning(self):
 | |
|         class FakeResolverMatch(ResolverMatch):
 | |
|             namespace = 'v3'
 | |
| 
 | |
|         scheme = versioning.NamespaceVersioning
 | |
|         view = AllowedVersionsView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/v3/endpoint/')
 | |
|         request.resolver_match = FakeResolverMatch
 | |
|         response = view(request, version='v3')
 | |
|         assert response.status_code == status.HTTP_404_NOT_FOUND
 | |
| 
 | |
| 
 | |
| class TestAllowedAndDefaultVersion:
 | |
|     def test_missing_without_default(self):
 | |
|         scheme = versioning.AcceptHeaderVersioning
 | |
|         view = AllowedVersionsView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/endpoint/', HTTP_ACCEPT='application/json')
 | |
|         response = view(request)
 | |
|         assert response.status_code == status.HTTP_406_NOT_ACCEPTABLE
 | |
| 
 | |
|     def test_missing_with_default(self):
 | |
|         scheme = versioning.AcceptHeaderVersioning
 | |
|         view = AllowedAndDefaultVersionsView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/endpoint/', HTTP_ACCEPT='application/json')
 | |
|         response = view(request)
 | |
|         assert response.status_code == status.HTTP_200_OK
 | |
|         assert response.data == {'version': 'v2'}
 | |
| 
 | |
|     def test_with_default(self):
 | |
|         scheme = versioning.AcceptHeaderVersioning
 | |
|         view = AllowedAndDefaultVersionsView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/endpoint/',
 | |
|                               HTTP_ACCEPT='application/json; version=v2')
 | |
|         response = view(request)
 | |
|         assert response.status_code == status.HTTP_200_OK
 | |
| 
 | |
|     def test_missing_without_default_but_none_allowed(self):
 | |
|         scheme = versioning.AcceptHeaderVersioning
 | |
|         view = AllowedWithNoneVersionsView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/endpoint/', HTTP_ACCEPT='application/json')
 | |
|         response = view(request)
 | |
|         assert response.status_code == status.HTTP_200_OK
 | |
|         assert response.data == {'version': None}
 | |
| 
 | |
|     def test_missing_with_default_and_none_allowed(self):
 | |
|         scheme = versioning.AcceptHeaderVersioning
 | |
|         view = AllowedWithNoneAndDefaultVersionsView.as_view(versioning_class=scheme)
 | |
| 
 | |
|         request = factory.get('/endpoint/', HTTP_ACCEPT='application/json')
 | |
|         response = view(request)
 | |
|         assert response.status_code == status.HTTP_200_OK
 | |
|         assert response.data == {'version': 'v2'}
 | |
| 
 | |
| 
 | |
| class TestHyperlinkedRelatedField(URLPatternsTestCase, APITestCase):
 | |
|     included = [
 | |
|         path('namespaced/<int:pk>/', dummy_pk_view, name='namespaced'),
 | |
|     ]
 | |
| 
 | |
|     urlpatterns = [
 | |
|         path('v1/', include((included, 'v1'), namespace='v1')),
 | |
|         path('v2/', include((included, 'v2'), namespace='v2'))
 | |
|     ]
 | |
| 
 | |
|     def setUp(self):
 | |
|         super().setUp()
 | |
| 
 | |
|         class MockQueryset:
 | |
|             def get(self, pk):
 | |
|                 return 'object %s' % pk
 | |
| 
 | |
|         self.field = serializers.HyperlinkedRelatedField(
 | |
|             view_name='namespaced',
 | |
|             queryset=MockQueryset()
 | |
|         )
 | |
|         request = factory.get('/')
 | |
|         request.versioning_scheme = NamespaceVersioning()
 | |
|         request.version = 'v1'
 | |
|         self.field._context = {'request': request}
 | |
| 
 | |
|     def test_bug_2489(self):
 | |
|         assert self.field.to_internal_value('/v1/namespaced/3/') == 'object 3'
 | |
|         with pytest.raises(serializers.ValidationError):
 | |
|             self.field.to_internal_value('/v2/namespaced/3/')
 | |
| 
 | |
| 
 | |
| class TestNamespaceVersioningHyperlinkedRelatedFieldScheme(URLPatternsTestCase, APITestCase):
 | |
|     nested = [
 | |
|         path('namespaced/<int:pk>/', dummy_pk_view, name='nested'),
 | |
|     ]
 | |
|     included = [
 | |
|         path('namespaced/<int:pk>/', dummy_pk_view, name='namespaced'),
 | |
|         path('nested/', include((nested, 'nested-namespace'), namespace='nested-namespace'))
 | |
|     ]
 | |
| 
 | |
|     urlpatterns = [
 | |
|         path('v1/', include((included, 'restframeworkv1'), namespace='v1')),
 | |
|         path('v2/', include((included, 'restframeworkv2'), namespace='v2')),
 | |
|         path('non-api/<int:pk>/', dummy_pk_view, name='non-api-view')
 | |
|     ]
 | |
| 
 | |
|     def _create_field(self, view_name, version):
 | |
|         request = factory.get("/")
 | |
|         request.versioning_scheme = NamespaceVersioning()
 | |
|         request.version = version
 | |
| 
 | |
|         field = serializers.HyperlinkedRelatedField(
 | |
|             view_name=view_name,
 | |
|             read_only=True)
 | |
|         field._context = {'request': request}
 | |
|         return field
 | |
| 
 | |
|     def test_api_url_is_properly_reversed_with_v1(self):
 | |
|         field = self._create_field('namespaced', 'v1')
 | |
|         assert field.to_representation(PKOnlyObject(3)) == 'http://testserver/v1/namespaced/3/'
 | |
| 
 | |
|     def test_api_url_is_properly_reversed_with_v2(self):
 | |
|         field = self._create_field('namespaced', 'v2')
 | |
|         assert field.to_representation(PKOnlyObject(5)) == 'http://testserver/v2/namespaced/5/'
 | |
| 
 | |
|     def test_api_url_is_properly_reversed_with_nested(self):
 | |
|         field = self._create_field('nested', 'v1:nested-namespace')
 | |
|         assert field.to_representation(PKOnlyObject(3)) == 'http://testserver/v1/nested/namespaced/3/'
 | |
| 
 | |
|     def test_non_api_url_is_properly_reversed_regardless_of_the_version(self):
 | |
|         """
 | |
|         Regression test for #2711
 | |
|         """
 | |
|         field = self._create_field('non-api-view', 'v1')
 | |
|         assert field.to_representation(PKOnlyObject(10)) == 'http://testserver/non-api/10/'
 | |
| 
 | |
|         field = self._create_field('non-api-view', 'v2')
 | |
|         assert field.to_representation(PKOnlyObject(10)) == 'http://testserver/non-api/10/'
 |