Repository: encode/django-rest-framework

Issue:
3.9: AttributeError when Schema Endpoint requires authentication
## Checklist

- [x] I have verified that that issue exists against the `master` branch of Django REST framework.
- [x] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
- [x] This is not a usage question. (Those should be directed to the [discussion group](https://groups.google.com/forum/#!forum/django-rest-framework) instead.)
- [x] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](https://www.django-rest-framework.org/topics/third-party-resources/#about-third-party-packages) where possible.)
- [x] 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
* Update to 3.9
* Implement Schema Endpoint as described in Announcement: https://www.django-rest-framework.org/community/3.9-announcement/
```
from rest_framework.schemas import get_schema_view

schema_view = get_schema_view(
    title='Server Monitoring API',
    url='https://www.example.org/api/'
)

urlpatterns = [
    url('^schema.json$', schema_view),
]
```
* Set Permission `IsAuthenticated`:
```
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
```
* Open `/schema.json`

## Expected behavior
* Authorization Error

## Actual behavior
```
Internal Server Error: /schema.json
Traceback (most recent call last):
  File "./.venv/lib/python3.6/site-packages/django/core/handlers/exception.py", line 35, in inner
    response = get_response(request)
  File "./.venv/lib/python3.6/site-packages/django/core/handlers/base.py", line 158, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "./.venv/lib/python3.6/site-packages/django/core/handlers/base.py", line 156, in _get_response
    response = response.render()
  File "./.venv/lib/python3.6/site-packages/django/template/response.py", line 106, in render
    self.content = self.rendered_content
  File "./.venv/lib/python3.6/site-packages/rest_framework/response.py", line 72, in rendered_content
    ret = renderer.render(self.data, accepted_media_type, context)
  File "./.venv/lib/python3.6/site-packages/rest_framework/renderers.py", line 732, in render
    context = self.get_context(data, accepted_media_type, renderer_context)
  File "./.venv/lib/python3.6/site-packages/rest_framework/renderers.py", line 687, in get_context
    'content': self.get_content(renderer, data, accepted_media_type, renderer_context),
  File "./.venv/lib/python3.6/site-packages/rest_framework/renderers.py", line 423, in get_content
    content = renderer.render(data, accepted_media_type, renderer_context)
  File "./.venv/lib/python3.6/site-packages/rest_framework/renderers.py", line 1036, in render
    structure = self.get_structure(data)
  File "./.venv/lib/python3.6/site-packages/rest_framework/renderers.py", line 1016, in get_structure
    'title': data.title,
AttributeError: 'dict' object has no attribute 'title'
```

```
.venv/lib/python3.6/site-packages/rest_framework/renderers.py in get_structure
data = {'detail': ErrorDetail(string='Authentication credentials were not provided.', code='not_authenticated')}

```


Discussion:
I'm having the same issue after upgrading to 3.9. Am able to work around the issue by logging by other means, but the schema view results in the following error:

```Traceback (most recent call last):
  File "/Users/.../anaconda3/envs/datjango/lib/python3.6/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "/Users/.../anaconda3/envs/datjango/lib/python3.6/site-packages/django/core/handlers/base.py", line 156, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/Users/.../anaconda3/envs/datjango/lib/python3.6/site-packages/django/core/handlers/base.py", line 154, in _get_response
    response = response.render()
  File "/Users/.../anaconda3/envs/datjango/lib/python3.6/site-packages/django/template/response.py", line 106, in render
    self.content = self.rendered_content
  File "/Users/.../anaconda3/envs/datjango/lib/python3.6/site-packages/rest_framework/response.py", line 72, in rendered_content
    ret = renderer.render(self.data, accepted_media_type, context)
  File "/Users/.../anaconda3/envs/datjango/lib/python3.6/site-packages/rest_framework/renderers.py", line 732, in render
    context = self.get_context(data, accepted_media_type, renderer_context)
  File "/Users/.../anaconda3/envs/datjango/lib/python3.6/site-packages/rest_framework/renderers.py", line 687, in get_context
    'content': self.get_content(renderer, data, accepted_media_type, renderer_context),
  File "/Users/.../anaconda3/envs/datjango/lib/python3.6/site-packages/rest_framework/renderers.py", line 423, in get_content
    content = renderer.render(data, accepted_media_type, renderer_context)
  File "/Users/.../anaconda3/envs/datjango/lib/python3.6/site-packages/rest_framework/renderers.py", line 1036, in render
    structure = self.get_structure(data)
  File "/Users/.../anaconda3/envs/datjango/lib/python3.6/site-packages/rest_framework/renderers.py", line 1016, in get_structure
    'title': data.title,
AttributeError: 'dict' object has no attribute 'title'
```

I duplicated the error with a clean install of Django==2.1.2 and djangorestframework==3.9.0 with "urls.py" as:
```
from django.contrib import admin
from django.urls import path, include
from rest_framework.schemas import get_schema_view
from rest_framework.renderers import JSONOpenAPIRenderer

schema_view = get_schema_view(
    title='Server Monitoring API',
    url='https://www.example.org/api/',
    renderer_classes=[JSONOpenAPIRenderer]
)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api-auth/', include('rest_framework.urls')),
    path('api/', get_schema_view()),
]
```
Let me know if there is any info I can provide to help.
Thanks for the minimal reproduction and traceback.

cc @carltongibson? 
OK, so yes... 

This looks to me like a variant of the same issue that came up with 3.6, when we introduced the interactive docs. Basically (I guess) the schema view is picking up `DEFAULT_AUTHENTICATION_CLASSES`. See e.g. #5399, #5309, #5448...

`get_schema_view()` takes `authentication_classes`. First this would be to try passing an empty list in there... 

(Does that help?)

Is this a duplicate of #6258?
@carltongibson this is #6258 😄 
Brough you this ☕️ 
> ... this is #6258 😄

Thought it looked relevant. 

> Brough you this ☕️

Thanks! 
For those that are stuck here, you can just pass the `AllowAny` permission to the `schema_view` as a workaround.

    schema_view = get_schema_view(
        title="My API",
        permission_classes=[rest_framework.permissions.AllowAny],
    )
FYI, I'm still stuck here. I have a custom permission class that doesn't require authentication, so the workaround proposed by @devsnd doesn't work for me.  As another alternative fix, I override the default renderer:

```python
from rest_framework import exceptions, renderers

class OpenAPIRendererV2(renderers.OpenAPIRenderer):
    # temporary fix for: https://github.com/encode/django-rest-framework/issues/6258
    def get_structure(self, data):
        if "detail" in data and isinstance(data["detail"], exceptions.ErrorDetail):
            return {"errror": str(data["detail"])}
        return super().get_structure(data)
```

Resolving patch:
diff --git a/rest_framework/schemas/views.py b/rest_framework/schemas/views.py
--- a/rest_framework/schemas/views.py
+++ b/rest_framework/schemas/views.py
@@ -31,3 +31,11 @@ def get(self, request, *args, **kwargs):
         if schema is None:
             raise exceptions.PermissionDenied()
         return Response(schema)
+
+    def handle_exception(self, exc):
+        # Schema renderers do not render exceptions, so re-perform content
+        # negotiation with default renderers.
+        self.renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
+        neg = self.perform_content_negotiation(self.request, force=True)
+        self.request.accepted_renderer, self.request.accepted_media_type = neg
+        return super(SchemaView, self).handle_exception(exc)


Test patch:
diff --git a/tests/test_schemas.py b/tests/test_schemas.py
--- a/tests/test_schemas.py
+++ b/tests/test_schemas.py
@@ -1304,3 +1304,13 @@ def test_foo(self):
 
     def test_FOO(self):
         assert not self._test('FOO')
+
+
+@pytest.mark.skipif(not coreapi, reason='coreapi is not installed')
+def test_schema_handles_exception():
+    schema_view = get_schema_view(permission_classes=[DenyAllUsingPermissionDenied])
+    request = factory.get('/')
+    response = schema_view(request)
+    response.render()
+    assert response.status_code == 403
+    assert "You do not have permission to perform this action." in str(response.content)