diff --git a/ansibleworks/main/renderers.py b/ansibleworks/main/renderers.py new file mode 100644 index 0000000000..69abdcab9c --- /dev/null +++ b/ansibleworks/main/renderers.py @@ -0,0 +1,14 @@ +import rest_framework.renderers + +class BrowsableAPIRenderer(rest_framework.renderers.BrowsableAPIRenderer): + ''' + Customizations to the default browsable API renderer. + ''' + + def get_form(self, view, method, request): + '''Never show auto-generated form (only raw form).''' + obj = getattr(view, 'object', None) + if not self.show_form_for_method(view, method, request, obj): + return + if method in ('DELETE', 'OPTIONS'): + return True # Don't actually need to return a form diff --git a/ansibleworks/main/urls.py b/ansibleworks/main/urls.py index 7a01659775..99d4bd466c 100644 --- a/ansibleworks/main/urls.py +++ b/ansibleworks/main/urls.py @@ -1,8 +1,7 @@ # Copyright (c) 2013 AnsibleWorks, Inc. # All Rights Reserved. -from django.conf.urls import include, patterns, url as original_url -import ansibleworks.main.views as views +from django.conf.urls import include, patterns, url as original_url def url(regex, view, kwargs=None, name=None, prefix=''): # Set default name from view name (if a string). @@ -135,3 +134,47 @@ urlpatterns = patterns('ansibleworks.main.views', url(r'^$', 'api_root_view'), url(r'^v1/', include(v1_urls)), ) + +# Monkeypatch get_view_name and get_view_description in Django REST Framework +# 2.3.x to allow a custom view name or description to be defined on the view +# class, instead of always using __name__ and __doc__. Used to be possible in +# 2.2.x by defining get_name() and get_description() methods on a view. + +try: + import rest_framework.utils.formatting + from django.utils.safestring import mark_safe + + original_get_view_name = rest_framework.utils.formatting.get_view_name + def get_view_name(cls, suffix=None): + name = '' + # Support for get_name method on views compatible with 2.2.x. + if hasattr(cls, 'get_name') and callable(cls.get_name): + name = cls().get_name() + elif hasattr(cls, 'view_name'): + if callable(cls.view_name): + name = cls.view_name() + else: + name = cls.view_name + if name: + return ('%s %s' % (name, suffix)) if suffix else name + return original_get_view_name(cls, suffix=None) + rest_framework.utils.formatting.get_view_name = get_view_name + + original_get_view_description = rest_framework.utils.formatting.get_view_description + def get_view_description(cls, html=False): + # Support for get_description method on views compatible with 2.2.x. + if hasattr(cls, 'get_description') and callable(cls.get_description): + desc = cls().get_description(html=html) + elif hasattr(cls, 'view_description'): + if callable(cls.view_description): + view_desc = cls.view_description() + else: + view_desc = cls.view_description + cls = type(cls.__name__, (object,), {'__doc__': view_desc}) + desc = original_get_view_description(cls, html=html) + if html: + desc = '
%s
' % desc + return mark_safe(desc) + rest_framework.utils.formatting.get_view_description = get_view_description +except ImportError: + pass diff --git a/ansibleworks/main/views.py b/ansibleworks/main/views.py index 2498200957..00886db008 100644 --- a/ansibleworks/main/views.py +++ b/ansibleworks/main/views.py @@ -49,18 +49,18 @@ def handle_500(request): class ApiRootView(APIView): ''' - Ansible Commander REST API + This resource is the root of the AnsibleWorks REST API and provides + information about the available API versions. ''' - def get_name(self): - return 'REST API' + view_name = 'REST API' def get(self, request, format=None): ''' list supported API versions ''' current = reverse('main:api_v1_root_view', args=[]) data = dict( - description = 'Ansible Commander REST API', + description = 'AnsibleWorks REST API', current_version = current, available_versions = dict( v1 = current @@ -71,10 +71,11 @@ class ApiRootView(APIView): class ApiV1RootView(APIView): ''' Version 1 of the REST API. + + Subject to change until the final 1.2 release. ''' - def get_name(self): - return 'Version 1' + view_name = 'Version 1' def get(self, request, format=None): ''' list top level resources ''' @@ -431,8 +432,7 @@ class UsersMeList(BaseList): permission_classes = (CustomRbac,) filter_fields = ('username',) - def get_name(self): - return 'Me!' + view_name = 'Me!' def post(self, request, *args, **kwargs): raise PermissionDenied() @@ -973,8 +973,7 @@ class BaseJobHostSummaryList(generics.ListAPIView): parent_model = None # Subclasses must define this attribute. relationship = 'job_host_summaries' - def get_name(self): - return 'Job Host Summary List' + view_name = 'Job Host Summary List' def get_queryset(self): # FIXME: Verify read permission on the parent object and job. diff --git a/ansibleworks/settings/defaults.py b/ansibleworks/settings/defaults.py index 95a4938886..08ffdaba96 100644 --- a/ansibleworks/settings/defaults.py +++ b/ansibleworks/settings/defaults.py @@ -29,10 +29,14 @@ REST_FRAMEWORK = { 'PAGINATE_BY': 25, 'PAGINATE_BY_PARAM': 'page_size', 'DEFAULT_AUTHENTICATION_CLASSES': ( - 'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.BasicAuthentication', + 'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.SessionAuthentication', - ) + ), + 'DEFAULT_RENDERER_CLASSES': ( + 'rest_framework.renderers.JSONRenderer', + 'ansibleworks.main.renderers.BrowsableAPIRenderer', + ), } DATABASES = { diff --git a/ansibleworks/templates/rest_framework/api.html b/ansibleworks/templates/rest_framework/api.html index 2b3f82577a..7f28690944 100644 --- a/ansibleworks/templates/rest_framework/api.html +++ b/ansibleworks/templates/rest_framework/api.html @@ -1,27 +1,51 @@ {% extends 'rest_framework/base.html' %} {% load i18n %} -{% block title %}{% trans 'AnsibleWorks API' %}{% endblock %} +{% block title %}{% trans 'AnsibleWorks REST API' %}{% endblock %} {% block style %} {{ block.super }} - + {% endblock %} {% block branding %} - {% trans 'AnsibleWorks API' %} - ({% trans 'powered by' %} {{ block.super }}) + {% trans 'REST API' %} {% endblock %} {% block userlinks %} @@ -64,6 +111,11 @@ html body .str a { {% endif %} {% endblock %} +{% block footer %} + +{% endblock %} + {% block script %} {{ block.super }} diff --git a/requirements/dev.txt b/requirements/dev.txt index 1fb5e1f6ac..6c9fd24c3f 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -7,7 +7,7 @@ django-extensions django-filter django-jsonfield django-taggit -djangorestframework +djangorestframework>=2.3.0,<2.4.0 Markdown pexpect python-dateutil diff --git a/setup.py b/setup.py index 8e0b494b93..ae5efa941c 100755 --- a/setup.py +++ b/setup.py @@ -30,28 +30,13 @@ setup( 'django-filter', 'django-jsonfield', 'django-taggit', - 'djangorestframework', + 'djangorestframework>=2.3.0,<2.4.0', 'pexpect', 'python-dateutil', 'PyYAML', - 'South', + 'South>=0.8,<2.0', ], setup_requires=[], - #tests_require=[ - # 'Django>=1.5', - # 'django-celery', - # 'django-extensions', - # 'django-filter', - # 'django-jsonfield', - # 'django-taggit', - # 'django-setuptest', - # 'djangorestframework', - # 'pexpect', - # 'python-dateutil', - # 'PyYAML', - # 'South', - #], - #test_suite='test_suite.TestSuite', classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment',