Fix naming collisions in Schema Generation (#5464)

* Add failing tests for #4704

* Add generic view based test case.

* Adjust insert_into to raise ValueError
This commit is contained in:
Carlton Gibson 2017-10-05 11:06:09 +02:00 committed by GitHub
parent 2befa6c316
commit d138f30a86
2 changed files with 107 additions and 4 deletions

View File

@ -51,6 +51,19 @@ def is_api_view(callback):
return (cls is not None) and issubclass(cls, APIView) return (cls is not None) and issubclass(cls, APIView)
INSERT_INTO_COLLISION_FMT = """
Schema Naming Collision.
coreapi.Link for URL path {value_url} cannot be inserted into schema.
Position conflicts with coreapi.Link for URL path {target_url}.
Attemped to insert link with keys: {keys}.
Adjust URLs to avoid naming collision or override `SchemaGenerator.get_keys()`
to customise schema structure.
"""
def insert_into(target, keys, value): def insert_into(target, keys, value):
""" """
Nested dictionary insertion. Nested dictionary insertion.
@ -64,7 +77,15 @@ def insert_into(target, keys, value):
if key not in target: if key not in target:
target[key] = {} target[key] = {}
target = target[key] target = target[key]
try:
target[keys[-1]] = value target[keys[-1]] = value
except TypeError:
msg = INSERT_INTO_COLLISION_FMT.format(
value_url=value.url,
target_url=target.url,
keys=keys
)
raise ValueError(msg)
def is_custom_action(action): def is_custom_action(action):

View File

@ -6,13 +6,15 @@ from django.core.exceptions import PermissionDenied
from django.http import Http404 from django.http import Http404
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from rest_framework import filters, pagination, permissions, serializers from rest_framework import (
filters, generics, pagination, permissions, serializers
)
from rest_framework.compat import coreapi, coreschema from rest_framework.compat import coreapi, coreschema
from rest_framework.decorators import ( from rest_framework.decorators import (
api_view, detail_route, list_route, schema api_view, detail_route, list_route, schema
) )
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter, SimpleRouter
from rest_framework.schemas import ( from rest_framework.schemas import (
AutoSchema, ManualSchema, SchemaGenerator, get_schema_view AutoSchema, ManualSchema, SchemaGenerator, get_schema_view
) )
@ -20,7 +22,9 @@ from rest_framework.schemas.generators import EndpointEnumerator
from rest_framework.test import APIClient, APIRequestFactory from rest_framework.test import APIClient, APIRequestFactory
from rest_framework.utils import formatting from rest_framework.utils import formatting
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet
from .models import BasicModel
factory = APIRequestFactory() factory = APIRequestFactory()
@ -726,3 +730,81 @@ class SchemaGenerationExclusionTests(TestCase):
"The `OldFashionedExcludedView.exclude_from_schema` attribute is " "The `OldFashionedExcludedView.exclude_from_schema` attribute is "
"pending deprecation. Set `schema = None` instead." "pending deprecation. Set `schema = None` instead."
) )
@api_view(["GET"])
def simple_fbv(request):
pass
class BasicModelSerializer(serializers.ModelSerializer):
class Meta:
model = BasicModel
fields = "__all__"
class NamingCollisionView(generics.RetrieveUpdateDestroyAPIView):
queryset = BasicModel.objects.all()
serializer_class = BasicModelSerializer
class NamingCollisionViewSet(GenericViewSet):
"""
Example via: https://stackoverflow.com/questions/43778668/django-rest-framwork-occured-typeerror-link-object-does-not-support-item-ass/
"""
permision_class = ()
@list_route()
def detail(self, request):
return {}
@list_route(url_path='detail/export')
def detail_export(self, request):
return {}
naming_collisions_router = SimpleRouter()
naming_collisions_router.register(r'collision', NamingCollisionViewSet, base_name="collision")
class TestURLNamingCollisions(TestCase):
"""
Ref: https://github.com/encode/django-rest-framework/issues/4704
"""
def test_manually_routing_nested_routes(self):
patterns = [
url(r'^test', simple_fbv),
url(r'^test/list/', simple_fbv),
]
generator = SchemaGenerator(title='Naming Colisions', patterns=patterns)
with pytest.raises(ValueError):
generator.get_schema()
def test_manually_routing_generic_view(self):
patterns = [
url(r'^test', NamingCollisionView.as_view()),
url(r'^test/retrieve/', NamingCollisionView.as_view()),
url(r'^test/update/', NamingCollisionView.as_view()),
# Fails with method names:
url(r'^test/get/', NamingCollisionView.as_view()),
url(r'^test/put/', NamingCollisionView.as_view()),
url(r'^test/delete/', NamingCollisionView.as_view()),
]
generator = SchemaGenerator(title='Naming Colisions', patterns=patterns)
with pytest.raises(ValueError):
generator.get_schema()
def test_from_router(self):
patterns = [
url(r'from-router', include(naming_collisions_router.urls)),
]
generator = SchemaGenerator(title='Naming Colisions', patterns=patterns)
with pytest.raises(ValueError):
generator.get_schema()