2016-07-04 18:38:17 +03:00
import unittest
from django . conf . urls import include , url
2016-11-02 12:04:01 +03:00
from django . core . exceptions import PermissionDenied
from django . http import Http404
2016-07-04 18:38:17 +03:00
from django . test import TestCase , override_settings
from rest_framework import filters , pagination , permissions , serializers
2017-03-03 18:24:37 +03:00
from rest_framework . compat import coreapi , coreschema
2016-08-11 16:07:40 +03:00
from rest_framework . decorators import detail_route , list_route
2016-11-02 12:04:01 +03:00
from rest_framework . request import Request
2016-07-04 18:38:17 +03:00
from rest_framework . routers import DefaultRouter
2016-10-10 15:03:46 +03:00
from rest_framework . schemas import SchemaGenerator , get_schema_view
2016-11-02 12:04:01 +03:00
from rest_framework . test import APIClient , APIRequestFactory
2016-07-27 17:36:36 +03:00
from rest_framework . views import APIView
2016-07-04 18:38:17 +03:00
from rest_framework . viewsets import ModelViewSet
2016-11-02 12:04:01 +03:00
factory = APIRequestFactory ( )
2016-07-04 18:38:17 +03:00
class MockUser ( object ) :
def is_authenticated ( self ) :
return True
class ExamplePagination ( pagination . PageNumberPagination ) :
page_size = 100
2016-10-12 18:09:45 +03:00
page_size_query_param = ' page_size '
2016-07-04 18:38:17 +03:00
2016-10-10 15:03:46 +03:00
class EmptySerializer ( serializers . Serializer ) :
pass
2016-07-04 18:38:17 +03:00
class ExampleSerializer ( serializers . Serializer ) :
2016-08-11 18:18:33 +03:00
a = serializers . CharField ( required = True , help_text = ' A field description ' )
2016-07-04 18:38:17 +03:00
b = serializers . CharField ( required = False )
2016-08-22 18:53:53 +03:00
read_only = serializers . CharField ( read_only = True )
hidden = serializers . HiddenField ( default = ' hello ' )
2016-07-04 18:38:17 +03:00
2017-08-20 19:12:56 +03:00
class AnotherSerializerWithListFields ( serializers . Serializer ) :
a = serializers . ListField ( child = serializers . IntegerField ( ) )
b = serializers . ListSerializer ( child = serializers . CharField ( ) )
2016-08-05 13:19:39 +03:00
class AnotherSerializer ( serializers . Serializer ) :
c = serializers . CharField ( required = True )
d = serializers . CharField ( required = False )
2016-07-04 18:38:17 +03:00
class ExampleViewSet ( ModelViewSet ) :
pagination_class = ExamplePagination
permission_classes = [ permissions . IsAuthenticatedOrReadOnly ]
filter_backends = [ filters . OrderingFilter ]
serializer_class = ExampleSerializer
2016-08-05 13:19:39 +03:00
@detail_route ( methods = [ ' post ' ] , serializer_class = AnotherSerializer )
def custom_action ( self , request , pk ) :
2016-10-10 15:03:46 +03:00
"""
A description of custom action .
"""
2016-08-05 13:19:39 +03:00
return super ( ExampleSerializer , self ) . retrieve ( self , request )
2017-08-20 19:12:56 +03:00
@detail_route ( methods = [ ' post ' ] , serializer_class = AnotherSerializerWithListFields )
def custom_action_with_list_fields ( self , request , pk ) :
"""
A custom action using both list field and list serializer in the serializer .
"""
return super ( ExampleSerializer , self ) . retrieve ( self , request )
2016-08-11 16:07:40 +03:00
@list_route ( )
def custom_list_action ( self , request ) :
return super ( ExampleViewSet , self ) . list ( self , request )
2016-10-10 15:03:46 +03:00
@list_route ( methods = [ ' post ' , ' get ' ] , serializer_class = EmptySerializer )
def custom_list_action_multiple_methods ( self , request ) :
return super ( ExampleViewSet , self ) . list ( self , request )
2016-08-11 13:27:28 +03:00
def get_serializer ( self , * args , * * kwargs ) :
assert self . request
2016-08-15 19:10:55 +03:00
assert self . action
2016-08-11 13:27:28 +03:00
return super ( ExampleViewSet , self ) . get_serializer ( * args , * * kwargs )
2017-05-29 22:15:07 +03:00
2016-10-10 15:03:46 +03:00
if coreapi :
schema_view = get_schema_view ( title = ' Example API ' )
else :
def schema_view ( request ) :
pass
2016-07-27 17:36:36 +03:00
2016-10-10 15:03:46 +03:00
router = DefaultRouter ( )
2016-07-04 18:38:17 +03:00
router . register ( ' example ' , ExampleViewSet , base_name = ' example ' )
urlpatterns = [
2016-10-10 15:03:46 +03:00
url ( r ' ^$ ' , schema_view ) ,
2016-07-04 18:38:17 +03:00
url ( r ' ^ ' , include ( router . urls ) )
]
@unittest.skipUnless ( coreapi , ' coreapi is not installed ' )
@override_settings ( ROOT_URLCONF = ' tests.test_schemas ' )
class TestRouterGeneratedSchema ( TestCase ) :
def test_anonymous_request ( self ) :
client = APIClient ( )
2016-10-10 15:03:46 +03:00
response = client . get ( ' / ' , HTTP_ACCEPT = ' application/coreapi+json ' )
2017-01-12 19:33:24 +03:00
assert response . status_code == 200
2016-07-04 18:38:17 +03:00
expected = coreapi . Document (
2017-03-03 18:24:37 +03:00
url = ' http://testserver/ ' ,
2016-07-04 18:38:17 +03:00
title = ' Example API ' ,
content = {
' example ' : {
' list ' : coreapi . Link (
url = ' /example/ ' ,
action = ' get ' ,
fields = [
2017-03-03 18:24:37 +03:00
coreapi . Field ( ' page ' , required = False , location = ' query ' , schema = coreschema . Integer ( title = ' Page ' , description = ' A page number within the paginated result set. ' ) ) ,
coreapi . Field ( ' page_size ' , required = False , location = ' query ' , schema = coreschema . Integer ( title = ' Page size ' , description = ' Number of results to return per page. ' ) ) ,
coreapi . Field ( ' ordering ' , required = False , location = ' query ' , schema = coreschema . String ( title = ' Ordering ' , description = ' Which field to use when ordering the results. ' ) )
2016-07-04 18:38:17 +03:00
]
) ,
2016-08-11 16:07:40 +03:00
' custom_list_action ' : coreapi . Link (
url = ' /example/custom_list_action/ ' ,
action = ' get '
) ,
2016-10-10 15:03:46 +03:00
' custom_list_action_multiple_methods ' : {
' read ' : coreapi . Link (
url = ' /example/custom_list_action_multiple_methods/ ' ,
action = ' get '
)
} ,
' read ' : coreapi . Link (
url = ' /example/ {id} / ' ,
2016-07-04 18:38:17 +03:00
action = ' get ' ,
fields = [
2017-03-03 18:24:37 +03:00
coreapi . Field ( ' id ' , required = True , location = ' path ' , schema = coreschema . String ( ) )
2016-07-04 18:38:17 +03:00
]
)
}
}
)
2017-01-12 19:33:24 +03:00
assert response . data == expected
2016-07-04 18:38:17 +03:00
def test_authenticated_request ( self ) :
client = APIClient ( )
client . force_authenticate ( MockUser ( ) )
2016-10-10 15:03:46 +03:00
response = client . get ( ' / ' , HTTP_ACCEPT = ' application/coreapi+json ' )
2017-01-12 19:33:24 +03:00
assert response . status_code == 200
2016-07-04 18:38:17 +03:00
expected = coreapi . Document (
2017-03-03 18:24:37 +03:00
url = ' http://testserver/ ' ,
2016-07-04 18:38:17 +03:00
title = ' Example API ' ,
content = {
' example ' : {
' list ' : coreapi . Link (
url = ' /example/ ' ,
action = ' get ' ,
fields = [
2017-03-03 18:24:37 +03:00
coreapi . Field ( ' page ' , required = False , location = ' query ' , schema = coreschema . Integer ( title = ' Page ' , description = ' A page number within the paginated result set. ' ) ) ,
coreapi . Field ( ' page_size ' , required = False , location = ' query ' , schema = coreschema . Integer ( title = ' Page size ' , description = ' Number of results to return per page. ' ) ) ,
coreapi . Field ( ' ordering ' , required = False , location = ' query ' , schema = coreschema . String ( title = ' Ordering ' , description = ' Which field to use when ordering the results. ' ) )
2016-07-04 18:38:17 +03:00
]
) ,
' create ' : coreapi . Link (
url = ' /example/ ' ,
action = ' post ' ,
encoding = ' application/json ' ,
fields = [
2017-03-03 18:24:37 +03:00
coreapi . Field ( ' a ' , required = True , location = ' form ' , schema = coreschema . String ( title = ' A ' , description = ' A field description ' ) ) ,
coreapi . Field ( ' b ' , required = False , location = ' form ' , schema = coreschema . String ( title = ' B ' ) )
2016-07-04 18:38:17 +03:00
]
) ,
2016-10-10 15:03:46 +03:00
' read ' : coreapi . Link (
url = ' /example/ {id} / ' ,
2016-07-04 18:38:17 +03:00
action = ' get ' ,
fields = [
2017-03-03 18:24:37 +03:00
coreapi . Field ( ' id ' , required = True , location = ' path ' , schema = coreschema . String ( ) )
2016-07-04 18:38:17 +03:00
]
) ,
2016-08-05 13:19:39 +03:00
' custom_action ' : coreapi . Link (
2016-10-10 15:03:46 +03:00
url = ' /example/ {id} /custom_action/ ' ,
2016-08-05 13:19:39 +03:00
action = ' post ' ,
encoding = ' application/json ' ,
2016-10-10 15:03:46 +03:00
description = ' A description of custom action. ' ,
2016-08-05 13:19:39 +03:00
fields = [
2017-03-03 18:24:37 +03:00
coreapi . Field ( ' id ' , required = True , location = ' path ' , schema = coreschema . String ( ) ) ,
coreapi . Field ( ' c ' , required = True , location = ' form ' , schema = coreschema . String ( title = ' C ' ) ) ,
coreapi . Field ( ' d ' , required = False , location = ' form ' , schema = coreschema . String ( title = ' D ' ) ) ,
2016-08-05 13:19:39 +03:00
]
) ,
2017-08-20 19:12:56 +03:00
' custom_action_with_list_fields ' : coreapi . Link (
url = ' /example/ {id} /custom_action_with_list_fields/ ' ,
action = ' post ' ,
encoding = ' application/json ' ,
description = ' A custom action using both list field and list serializer in the serializer. ' ,
fields = [
coreapi . Field ( ' id ' , required = True , location = ' path ' , schema = coreschema . String ( ) ) ,
coreapi . Field ( ' a ' , required = True , location = ' form ' , schema = coreschema . Array ( title = ' A ' , items = coreschema . Integer ( ) ) ) ,
coreapi . Field ( ' b ' , required = True , location = ' form ' , schema = coreschema . Array ( title = ' B ' , items = coreschema . String ( ) ) ) ,
]
) ,
2016-08-11 16:07:40 +03:00
' custom_list_action ' : coreapi . Link (
url = ' /example/custom_list_action/ ' ,
action = ' get '
) ,
2016-10-10 15:03:46 +03:00
' custom_list_action_multiple_methods ' : {
' read ' : coreapi . Link (
url = ' /example/custom_list_action_multiple_methods/ ' ,
action = ' get '
) ,
' create ' : coreapi . Link (
url = ' /example/custom_list_action_multiple_methods/ ' ,
action = ' post '
)
} ,
2016-07-04 18:38:17 +03:00
' update ' : coreapi . Link (
2016-10-10 15:03:46 +03:00
url = ' /example/ {id} / ' ,
2016-07-04 18:38:17 +03:00
action = ' put ' ,
encoding = ' application/json ' ,
fields = [
2017-03-03 18:24:37 +03:00
coreapi . Field ( ' id ' , required = True , location = ' path ' , schema = coreschema . String ( ) ) ,
coreapi . Field ( ' a ' , required = True , location = ' form ' , schema = coreschema . String ( title = ' A ' , description = ( ' A field description ' ) ) ) ,
coreapi . Field ( ' b ' , required = False , location = ' form ' , schema = coreschema . String ( title = ' B ' ) )
2016-07-04 18:38:17 +03:00
]
) ,
' partial_update ' : coreapi . Link (
2016-10-10 15:03:46 +03:00
url = ' /example/ {id} / ' ,
2016-07-04 18:38:17 +03:00
action = ' patch ' ,
encoding = ' application/json ' ,
fields = [
2017-03-03 18:24:37 +03:00
coreapi . Field ( ' id ' , required = True , location = ' path ' , schema = coreschema . String ( ) ) ,
coreapi . Field ( ' a ' , required = False , location = ' form ' , schema = coreschema . String ( title = ' A ' , description = ' A field description ' ) ) ,
coreapi . Field ( ' b ' , required = False , location = ' form ' , schema = coreschema . String ( title = ' B ' ) )
2016-07-04 18:38:17 +03:00
]
) ,
2016-10-10 15:03:46 +03:00
' delete ' : coreapi . Link (
url = ' /example/ {id} / ' ,
2016-07-04 18:38:17 +03:00
action = ' delete ' ,
fields = [
2017-03-03 18:24:37 +03:00
coreapi . Field ( ' id ' , required = True , location = ' path ' , schema = coreschema . String ( ) )
2016-07-04 18:38:17 +03:00
]
)
}
}
)
2017-01-12 19:33:24 +03:00
assert response . data == expected
2016-07-27 17:36:36 +03:00
2016-11-02 12:04:01 +03:00
class DenyAllUsingHttp404 ( permissions . BasePermission ) :
def has_permission ( self , request , view ) :
raise Http404 ( )
def has_object_permission ( self , request , view , obj ) :
raise Http404 ( )
class DenyAllUsingPermissionDenied ( permissions . BasePermission ) :
def has_permission ( self , request , view ) :
raise PermissionDenied ( )
def has_object_permission ( self , request , view , obj ) :
raise PermissionDenied ( )
class Http404ExampleViewSet ( ExampleViewSet ) :
permission_classes = [ DenyAllUsingHttp404 ]
class PermissionDeniedExampleViewSet ( ExampleViewSet ) :
permission_classes = [ DenyAllUsingPermissionDenied ]
2017-04-19 06:44:18 +03:00
class MethodLimitedViewSet ( ExampleViewSet ) :
permission_classes = [ ]
http_method_names = [ ' get ' , ' head ' , ' options ' ]
2016-10-10 15:03:46 +03:00
class ExampleListView ( APIView ) :
permission_classes = [ permissions . IsAuthenticatedOrReadOnly ]
def get ( self , * args , * * kwargs ) :
pass
def post ( self , request , * args , * * kwargs ) :
pass
class ExampleDetailView ( APIView ) :
permission_classes = [ permissions . IsAuthenticatedOrReadOnly ]
def get ( self , * args , * * kwargs ) :
pass
2016-07-27 17:36:36 +03:00
@unittest.skipUnless ( coreapi , ' coreapi is not installed ' )
class TestSchemaGenerator ( TestCase ) :
2016-10-10 15:03:46 +03:00
def setUp ( self ) :
self . patterns = [
url ( ' ^example/?$ ' , ExampleListView . as_view ( ) ) ,
url ( ' ^example/(?P<pk> \ d+)/?$ ' , ExampleDetailView . as_view ( ) ) ,
url ( ' ^example/(?P<pk> \ d+)/sub/?$ ' , ExampleDetailView . as_view ( ) ) ,
]
def test_schema_for_regular_views ( self ) :
"""
Ensure that schema generation works for APIView classes .
"""
generator = SchemaGenerator ( title = ' Example API ' , patterns = self . patterns )
schema = generator . get_schema ( )
2016-07-27 17:36:36 +03:00
expected = coreapi . Document (
url = ' ' ,
2016-10-10 15:03:46 +03:00
title = ' Example API ' ,
2016-07-27 17:36:36 +03:00
content = {
2016-10-10 15:03:46 +03:00
' example ' : {
2016-07-27 17:36:36 +03:00
' create ' : coreapi . Link (
2016-10-10 15:03:46 +03:00
url = ' /example/ ' ,
2016-07-27 17:36:36 +03:00
action = ' post ' ,
fields = [ ]
) ,
2016-10-10 15:03:46 +03:00
' list ' : coreapi . Link (
url = ' /example/ ' ,
action = ' get ' ,
fields = [ ]
) ,
2016-07-27 17:36:36 +03:00
' read ' : coreapi . Link (
2016-10-10 15:03:46 +03:00
url = ' /example/ {id} / ' ,
2016-07-27 17:36:36 +03:00
action = ' get ' ,
2016-10-10 15:03:46 +03:00
fields = [
2017-03-03 18:24:37 +03:00
coreapi . Field ( ' id ' , required = True , location = ' path ' , schema = coreschema . String ( ) )
2016-10-10 15:03:46 +03:00
]
) ,
' sub ' : {
' list ' : coreapi . Link (
url = ' /example/ {id} /sub/ ' ,
action = ' get ' ,
fields = [
2017-03-03 18:24:37 +03:00
coreapi . Field ( ' id ' , required = True , location = ' path ' , schema = coreschema . String ( ) )
2016-10-10 15:03:46 +03:00
]
)
}
}
}
)
2017-01-12 19:33:24 +03:00
assert schema == expected
2016-10-10 15:03:46 +03:00
@unittest.skipUnless ( coreapi , ' coreapi is not installed ' )
class TestSchemaGeneratorNotAtRoot ( TestCase ) :
def setUp ( self ) :
self . patterns = [
url ( ' ^api/v1/example/?$ ' , ExampleListView . as_view ( ) ) ,
url ( ' ^api/v1/example/(?P<pk> \ d+)/?$ ' , ExampleDetailView . as_view ( ) ) ,
url ( ' ^api/v1/example/(?P<pk> \ d+)/sub/?$ ' , ExampleDetailView . as_view ( ) ) ,
]
def test_schema_for_regular_views ( self ) :
"""
Ensure that schema generation with an API that is not at the URL
root continues to use correct structure for link keys .
"""
generator = SchemaGenerator ( title = ' Example API ' , patterns = self . patterns )
schema = generator . get_schema ( )
expected = coreapi . Document (
url = ' ' ,
title = ' Example API ' ,
content = {
' example ' : {
' create ' : coreapi . Link (
url = ' /api/v1/example/ ' ,
action = ' post ' ,
2016-07-27 17:36:36 +03:00
fields = [ ]
2016-10-10 15:03:46 +03:00
) ,
' list ' : coreapi . Link (
url = ' /api/v1/example/ ' ,
action = ' get ' ,
fields = [ ]
) ,
' read ' : coreapi . Link (
url = ' /api/v1/example/ {id} / ' ,
action = ' get ' ,
fields = [
2017-03-03 18:24:37 +03:00
coreapi . Field ( ' id ' , required = True , location = ' path ' , schema = coreschema . String ( ) )
2016-10-10 15:03:46 +03:00
]
) ,
' sub ' : {
' list ' : coreapi . Link (
url = ' /api/v1/example/ {id} /sub/ ' ,
action = ' get ' ,
fields = [
2017-03-03 18:24:37 +03:00
coreapi . Field ( ' id ' , required = True , location = ' path ' , schema = coreschema . String ( ) )
2016-10-10 15:03:46 +03:00
]
)
}
2016-07-27 17:36:36 +03:00
}
}
)
2017-01-12 19:33:24 +03:00
assert schema == expected
2016-10-21 18:59:34 +03:00
2017-04-19 06:44:18 +03:00
@unittest.skipUnless ( coreapi , ' coreapi is not installed ' )
class TestSchemaGeneratorWithMethodLimitedViewSets ( TestCase ) :
def setUp ( self ) :
router = DefaultRouter ( )
router . register ( ' example1 ' , MethodLimitedViewSet , base_name = ' example1 ' )
self . patterns = [
url ( r ' ^ ' , include ( router . urls ) )
]
def test_schema_for_regular_views ( self ) :
"""
Ensure that schema generation works for ViewSet classes
with method limitation by Django CBV ' s http_method_names attribute
"""
generator = SchemaGenerator ( title = ' Example API ' , patterns = self . patterns )
request = factory . get ( ' /example1/ ' )
schema = generator . get_schema ( Request ( request ) )
expected = coreapi . Document (
url = ' http://testserver/example1/ ' ,
title = ' Example API ' ,
content = {
' example1 ' : {
' list ' : coreapi . Link (
url = ' /example1/ ' ,
action = ' get ' ,
fields = [
coreapi . Field ( ' page ' , required = False , location = ' query ' , schema = coreschema . Integer ( title = ' Page ' , description = ' A page number within the paginated result set. ' ) ) ,
coreapi . Field ( ' page_size ' , required = False , location = ' query ' , schema = coreschema . Integer ( title = ' Page size ' , description = ' Number of results to return per page. ' ) ) ,
coreapi . Field ( ' ordering ' , required = False , location = ' query ' , schema = coreschema . String ( title = ' Ordering ' , description = ' Which field to use when ordering the results. ' ) )
]
) ,
' custom_list_action ' : coreapi . Link (
url = ' /example1/custom_list_action/ ' ,
action = ' get '
) ,
' custom_list_action_multiple_methods ' : {
' read ' : coreapi . Link (
url = ' /example1/custom_list_action_multiple_methods/ ' ,
action = ' get '
)
} ,
' read ' : coreapi . Link (
url = ' /example1/ {id} / ' ,
action = ' get ' ,
fields = [
coreapi . Field ( ' id ' , required = True , location = ' path ' , schema = coreschema . String ( ) )
]
)
}
}
)
assert schema == expected
2016-11-02 12:04:01 +03:00
@unittest.skipUnless ( coreapi , ' coreapi is not installed ' )
class TestSchemaGeneratorWithRestrictedViewSets ( TestCase ) :
def setUp ( self ) :
router = DefaultRouter ( )
router . register ( ' example1 ' , Http404ExampleViewSet , base_name = ' example1 ' )
router . register ( ' example2 ' , PermissionDeniedExampleViewSet , base_name = ' example2 ' )
self . patterns = [
url ( ' ^example/?$ ' , ExampleListView . as_view ( ) ) ,
url ( r ' ^ ' , include ( router . urls ) )
]
def test_schema_for_regular_views ( self ) :
"""
Ensure that schema generation works for ViewSet classes
with permission classes raising exceptions .
"""
generator = SchemaGenerator ( title = ' Example API ' , patterns = self . patterns )
request = factory . get ( ' / ' )
schema = generator . get_schema ( Request ( request ) )
expected = coreapi . Document (
2017-03-03 18:24:37 +03:00
url = ' http://testserver/ ' ,
2016-11-02 12:04:01 +03:00
title = ' Example API ' ,
content = {
' example ' : {
' list ' : coreapi . Link (
url = ' /example/ ' ,
action = ' get ' ,
fields = [ ]
) ,
} ,
}
)
2017-01-12 19:33:24 +03:00
assert schema == expected
2016-11-02 12:04:01 +03:00
2016-10-21 18:59:34 +03:00
@unittest.skipUnless ( coreapi , ' coreapi is not installed ' )
class Test4605Regression ( TestCase ) :
def test_4605_regression ( self ) :
generator = SchemaGenerator ( )
prefix = generator . determine_path_prefix ( [
' /api/v1/items/ ' ,
' /auth/convert-token/ '
] )
assert prefix == ' / '