diff --git a/rest_framework/schemas.py b/rest_framework/schemas.py index 437413355..f20d14610 100644 --- a/rest_framework/schemas.py +++ b/rest_framework/schemas.py @@ -256,6 +256,38 @@ class EndpointInspector(object): ] +class APIViewSchemaDescriptor(object): + """ + Descriptor class on APIView. + + Responsible for per-view instrospection and schema generation. + """ + def __get__(self, instance, owner): + # ???: Is this TOO simple? (Option is to return a new instance each time.) + self.view = instance + return self + + def get_link(self, path, method, generator): + """ + Generate `coreapi.Link` for view. + + This is the main _public_ access point. + """ + assert self.view is not None, "Schema generation REQUIRES a view instance. (Hint: you accessed `schema` from the view CLASS rather than an instance.)" + view = self.view + + # TEMP: now we proxy back to the generator + link = generator.get_link(path, method, view) + + return link + +# TODO: Where should this live? +# - We import APIView here. So we can't import the descriptor into `views` +# - APIView is only used by SchemaView. +# - ???: Make `schemas` a package and move SchemaView to `schema.views` +APIView.schema = APIViewSchemaDescriptor() + + class SchemaGenerator(object): # Map HTTP methods onto actions. default_mapping = { @@ -341,7 +373,7 @@ class SchemaGenerator(object): for path, method, view in view_endpoints: if not self.has_view_permissions(path, method, view): continue - link = self.get_link(path, method, view) + link = self.get_link_proxy(path, method, view) subpath = path[len(prefix):] keys = self.get_keys(subpath, method, view) insert_into(links, keys, link) @@ -434,6 +466,18 @@ class SchemaGenerator(object): return path.replace('{pk}', '{%s}' % field_name) # Methods for generating each individual `Link` instance... + def get_link_proxy(self, path, method, view): + """ + Proxy to view's descriptor for link + + TEMPORARY NAME at least. + """ + schema = view.schema + + # To begin we pass generator — we still need its methods. + link = schema.get_link(path, method, self) + + return link def get_link(self, path, method, view): """ diff --git a/tests/test_schemas.py b/tests/test_schemas.py index b435dfdd7..84ea2e855 100644 --- a/tests/test_schemas.py +++ b/tests/test_schemas.py @@ -1,5 +1,6 @@ import unittest +import pytest from django.conf.urls import include, url from django.core.exceptions import PermissionDenied from django.http import Http404 @@ -10,7 +11,9 @@ from rest_framework.compat import coreapi, coreschema from rest_framework.decorators import detail_route, list_route from rest_framework.request import Request from rest_framework.routers import DefaultRouter -from rest_framework.schemas import SchemaGenerator, get_schema_view +from rest_framework.schemas import ( + APIViewSchemaDescriptor, SchemaGenerator, get_schema_view +) from rest_framework.test import APIClient, APIRequestFactory from rest_framework.views import APIView from rest_framework.viewsets import ModelViewSet @@ -496,3 +499,16 @@ class Test4605Regression(TestCase): '/auth/convert-token/' ]) assert prefix == '/' + + +class TestDescriptor(TestCase): + + def test_apiview_schema_descriptor(self): + view = APIView() + assert hasattr(view, 'schema') + assert isinstance(view.schema, APIViewSchemaDescriptor) + + def test_get_link_requires_instance(self): + descriptor = APIView.schema # Accessed from class + with pytest.raises(AssertionError): + descriptor.get_link(None, None, None) # ???: Do the dummy arguments require a tighter assert?