From 39525316acbf211830c8d24264e4e7dd2aaebc00 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Wed, 21 Jun 2017 15:33:57 -0400 Subject: [PATCH] prefix all /api/v1/ view descriptions with a deprecation message additionally, fix a bug in /api/v1/credentials/ view descriptions that causes v2 fields to display (see: #6116) --- awx/api/generics.py | 30 +++++++++++++++++++++++----- awx/api/metadata.py | 6 +++++- awx/api/renderers.py | 2 ++ awx/api/templates/api/_deprecated.md | 1 + awx/api/templates/api/_new_in_awx.md | 1 - awx/api/versioning.py | 3 +++ 6 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 awx/api/templates/api/_deprecated.md diff --git a/awx/api/generics.py b/awx/api/generics.py index 2bf09211c9..2345867e83 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -31,7 +31,7 @@ from awx.api.filters import FieldLookupBackend from awx.main.models import * # noqa from awx.main.utils import * # noqa from awx.api.serializers import ResourceAccessListElementSerializer -from awx.api.versioning import URLPathVersioning +from awx.api.versioning import URLPathVersioning, get_request_version __all__ = ['APIView', 'GenericAPIView', 'ListAPIView', 'SimpleListAPIView', 'ListCreateAPIView', 'SubListAPIView', 'SubListCreateAPIView', @@ -66,13 +66,13 @@ def get_view_name(cls, suffix=None): return views.get_view_name(cls, suffix=None) -def get_view_description(cls, html=False): +def get_view_description(cls, request, html=False): ''' Wrapper around REST framework get_view_description() to support get_description() method and view_description property on a view class. ''' if hasattr(cls, 'get_description') and callable(cls.get_description): - desc = cls().get_description(html=html) + desc = cls().get_description(request, html=html) cls = type(cls.__name__, (object,), {'__doc__': desc}) elif hasattr(cls, 'view_description'): if callable(cls.view_description): @@ -150,6 +150,14 @@ class APIView(views.APIView): except NameError: pass + def get_view_description(self, html=False): + """ + Return some descriptive text for the view, as used in OPTIONS responses + and in the browsable API. + """ + func = self.settings.VIEW_DESCRIPTION_FUNCTION + return func(self.__class__, self._request, html) + def get_description_context(self): return { 'view': self, @@ -170,13 +178,25 @@ class APIView(views.APIView): 'deprecated': getattr(self, 'deprecated', False), } - def get_description(self, html=False): + def get_description(self, request, html=False): + self.request = request template_list = [] for klass in inspect.getmro(type(self)): template_basename = camelcase_to_underscore(klass.__name__) template_list.append('api/%s.md' % template_basename) context = self.get_description_context() - return render_to_string(template_list, context) + + # "v2" -> 2 + default_version = int(settings.REST_FRAMEWORK['DEFAULT_VERSION'].lstrip('v')) + request_version = get_request_version(self.request) + if request_version is not None and request_version < default_version: + context['deprecated'] = True + + description = render_to_string(template_list, context) + if context.get('deprecated'): + # render deprecation messages at the very top + description = '\n'.join([render_to_string('api/_deprecated.md', context), description]) + return description def update_raw_data(self, data): # Remove the parent key if the view is a sublist, since it will be set diff --git a/awx/api/metadata.py b/awx/api/metadata.py index f98316b459..0d05eedb98 100644 --- a/awx/api/metadata.py +++ b/awx/api/metadata.py @@ -175,7 +175,11 @@ class Metadata(metadata.SimpleMetadata): # (such as TOWER_URL_BASE) self.request = request - metadata = super(Metadata, self).determine_metadata(request, view) + try: + setattr(view, '_request', request) + metadata = super(Metadata, self).determine_metadata(request, view) + finally: + delattr(view, '_request') # Add version number in which view was added to Tower. added_in_version = '1.2' diff --git a/awx/api/renderers.py b/awx/api/renderers.py index fa039a2226..006057a09b 100644 --- a/awx/api/renderers.py +++ b/awx/api/renderers.py @@ -23,9 +23,11 @@ class BrowsableAPIRenderer(renderers.BrowsableAPIRenderer): # data form. try: setattr(renderer_context['view'], '_raw_data_response_status', renderer_context['response'].status_code) + setattr(renderer_context['view'], '_request', renderer_context['request']) return super(BrowsableAPIRenderer, self).get_context(data, accepted_media_type, renderer_context) finally: delattr(renderer_context['view'], '_raw_data_response_status') + delattr(renderer_context['view'], '_request') def get_raw_data_form(self, data, view, method, request): # Set a flag on the view to indiciate to the view/serializer that we're diff --git a/awx/api/templates/api/_deprecated.md b/awx/api/templates/api/_deprecated.md new file mode 100644 index 0000000000..b4dda2ceda --- /dev/null +++ b/awx/api/templates/api/_deprecated.md @@ -0,0 +1 @@ +> _This resource has been deprecated and will be removed in a future release_ diff --git a/awx/api/templates/api/_new_in_awx.md b/awx/api/templates/api/_new_in_awx.md index 3b50b9d433..994a18dc48 100644 --- a/awx/api/templates/api/_new_in_awx.md +++ b/awx/api/templates/api/_new_in_awx.md @@ -10,5 +10,4 @@ {% if new_in_300 %}> _Added in Ansible Tower 3.0.0_{% endif %} {% if new_in_310 %}> _New in Ansible Tower 3.1.0_{% endif %} {% if new_in_320 %}> _New in Ansible Tower 3.2.0_{% endif %} -{% if deprecated %}> _This resource has been deprecated and will be removed in a future release_{% endif %} {% endif %} diff --git a/awx/api/versioning.py b/awx/api/versioning.py index 9bf281d5ba..e9c089e37d 100644 --- a/awx/api/versioning.py +++ b/awx/api/versioning.py @@ -14,6 +14,9 @@ def get_request_version(request): version = settings.REST_FRAMEWORK['DEFAULT_VERSION'] if request and hasattr(request, 'version'): version = request.version + if version is None: + # For requests to /api/ + return None return int(version.lstrip('v'))