-
-
Notifications
You must be signed in to change notification settings - Fork 7k
Description
Checklist
- I have verified that that issue exists against the
master
branch of Django REST framework. - I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
- This is not a usage question. (Those should be directed to the discussion group instead.)
- This cannot be dealt with as a third party library. (We prefer new functionality to be in the form of third party libraries where possible.)
- I have reduced the issue to the simplest possible case.
- I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)
Steps to reproduce
Just start a single app django project including 2 or more urls having same prefix, set a url for get_schema_view, then request the url.
Following below, I sure you will reproduce this.
the project tree:
test_proj/
├── db.sqlite3
├── manage.py
├── test_app
│ ├── __init__.py
│ ├── __pycache__
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── test_proj
│ ├── __init__.py
│ ├── __pycache__
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── venv
├── bin
├── include
├── lib
└── pip-selfcheck.json
test_app/views.py:
from rest_framework import viewsets, serializers
from rest_framework.response import Response
from rest_framework.decorators import action
class TestSerializer(serializers.Serializer):
a = serializers.IntegerField()
class TestViewSet(viewsets.GenericViewSet):
serializer_class = TestSerializer
@action(['GET'], detail=False)
def test1(self):
return Response({})
@action(['GET'], detail=False)
def test2(self):
return Response({})
test_app/urls.py:
from rest_framework.routers import SimpleRouter
import test_app.views as views
router = SimpleRouter()
router.register('test', views.TestViewSet, 'test')
urlpatterns = router.urls
test_proj/urls.py:
from django.conf.urls import url, include
from django.contrib import admin
from rest_framework.schemas import get_schema_view
schema_view = get_schema_view('API')
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/', schema_view),
url(r'^test', include('test_app.urls')),
]
Expected behavior
Should get a normally schema view.
Actual behavior
The get_schema_view raised exceptions.PermessionDenied()./
Further debug
I've reproduced this bug for many times, and digged into source code for a while, then I find the bug, get_schema_view
func use SchemaGenerator.get_schema
method, if this method return None, then the PermissionDenied will be raised.
in rest_framework.SchemaGenerator.get_schema
:
links = self.get_links(None if public else request)
if not links:
return None
further in SchemaGenerator.get_links
:
for path, method, view in view_endpoints:
print(path, method, view)
if not self.has_view_permissions(path, method, view):
continue
link = view.schema.get_link(path, method, base_url=self.url)
subpath = path[len(prefix):]
keys = self.get_keys(subpath, method, view)
insert_into(links, keys, link)
and even further in rest_framework.schemas.generators.insert_into
:
def insert_into(target, keys, value):
"""
Nested dictionary insertion.
>>> example = {}
>>> insert_into(example, ['a', 'b', 'c'], 123)
>>> example
LinkNode({'a': LinkNode({'b': LinkNode({'c': LinkNode(links=[123])}}})))
"""
for key in keys[:-1]:
if key not in target:
target[key] = LinkNode()
target = target[key]
try:
target.links.append((keys[-1], value))
except TypeError:
msg = INSERT_INTO_COLLISION_FMT.format(
value_url=value.url,
target_url=target.url,
keys=keys
)
raise ValueError(msg)
This insert_into
func does not react like it's annotation. When there's only one key, it's supposed to make target= LinkNode({'key': LinkNode(links=[value])})
, but this code will make target=LinkNode(links=[('key', 'value')])
, which caused not target
== Ture
.
By alter this insert_into
func will repaire the bug, but I'm not sure if the repaire is correct, so I put what I've got here and hope this bug get further discusses, Thanks.