def test_extension_not_found_for_installed_app(capsys): class FixXFunctionView(OpenApiViewExtension): target_class = 'tests.test_extensions.NotExistingClass' def view_replacement(self): pass # pragma: no cover OpenApiViewExtension.get_match(object()) assert 'target class was not found' in capsys.readouterr().err
def create_view(self, callback, method, request=None): """ customized create_view which is called when all routes are traversed. part of this is instantiating views with default params. in case of custom routes (@action) the custom AutoSchema is injected properly through 'initkwargs' on view. However, when decorating plain views like retrieve, this initialization logic is not running. Therefore forcefully set the schema if @extend_schema decorator was used. """ override_view = OpenApiViewExtension.get_match(callback.cls) if override_view: callback.cls = override_view.view_replacement() view = super().create_view(callback, method, request) if isinstance(view, viewsets.GenericViewSet) or isinstance( view, viewsets.ViewSet): action = getattr(view, view.action) elif isinstance(view, views.APIView): action = getattr(view, method.lower()) else: error( 'Using not supported View class. Class must be derived from APIView ' 'or any of its subclasses like GenericApiView, GenericViewSet.' ) return view # in case of @extend_schema, manually init custom schema class here due to # weakref reverse schema.view bug for multi annotations. schema = getattr(action, 'kwargs', {}).get('schema', None) if schema and inspect.isclass(schema): view.schema = schema() return view
def create_view(self, callback, method, request=None): """ customized create_view which is called when all routes are traversed. part of this is instantiating views with default params. in case of custom routes (@action) the custom AutoSchema is injected properly through 'initkwargs' on view. However, when decorating plain views like retrieve, this initialization logic is not running. Therefore forcefully set the schema if @extend_schema decorator was used. """ override_view = OpenApiViewExtension.get_match(callback.cls) if override_view: original_cls = callback.cls callback.cls = override_view.view_replacement() view = super().create_view(callback, method, request) # callback.cls is hosted in urlpatterns and is therefore not an ephemeral modification. # restore after view creation so potential revisits have a clean state as basis. if override_view: callback.cls = original_cls if isinstance(view, viewsets.ViewSetMixin): action = getattr(view, view.action) elif isinstance(view, views.APIView): action = getattr(view, method.lower()) else: error( 'Using not supported View class. Class must be derived from APIView ' 'or any of its subclasses like GenericApiView, GenericViewSet.' ) return view action_schema = getattr(action, 'kwargs', {}).get('schema', None) if not action_schema: # there is no method/action customized schema so we are done here. return view # action_schema is either a class or instance. when @extend_schema is used, it # is always a class to prevent the weakref reverse "schema.view" bug for multi # annotations. The bug is prevented by delaying the instantiation of the schema # class until create_view (here) and not doing it immediately in @extend_schema. action_schema_class = get_class(action_schema) view_schema_class = get_class(callback.cls.schema) if not issubclass(action_schema_class, view_schema_class): # this handles the case of having a manually set custom AutoSchema on the # view together with extend_schema. In most cases, the decorator mechanics # prevent extend_schema from having access to the view's schema class. So # extend_schema is forced to use DEFAULT_SCHEMA_CLASS as fallback base class # instead of the correct base class set in view. We remedy this chicken-egg # problem here by rearranging the class hierarchy. mro = tuple( cls for cls in action_schema_class.__mro__ if cls not in api_settings.DEFAULT_SCHEMA_CLASS.__mro__ ) + view_schema_class.__mro__ action_schema_class = type('ExtendedRearrangedSchema', mro, {}) view.schema = action_schema_class() return view