mirror of
https://github.com/ansible/awx.git
synced 2026-05-20 07:17:40 -02:30
Updates to work with REST framework 2.3.x, update browseable API style to mimic UI.
This commit is contained in:
14
ansibleworks/main/renderers.py
Normal file
14
ansibleworks/main/renderers.py
Normal file
@@ -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
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import include, patterns, url as original_url
|
from django.conf.urls import include, patterns, url as original_url
|
||||||
import ansibleworks.main.views as views
|
|
||||||
|
|
||||||
def url(regex, view, kwargs=None, name=None, prefix=''):
|
def url(regex, view, kwargs=None, name=None, prefix=''):
|
||||||
# Set default name from view name (if a string).
|
# 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'^$', 'api_root_view'),
|
||||||
url(r'^v1/', include(v1_urls)),
|
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 = '<div class="description">%s</div>' % desc
|
||||||
|
return mark_safe(desc)
|
||||||
|
rest_framework.utils.formatting.get_view_description = get_view_description
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|||||||
@@ -49,18 +49,18 @@ def handle_500(request):
|
|||||||
|
|
||||||
class ApiRootView(APIView):
|
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):
|
view_name = 'REST API'
|
||||||
return 'REST API'
|
|
||||||
|
|
||||||
def get(self, request, format=None):
|
def get(self, request, format=None):
|
||||||
''' list supported API versions '''
|
''' list supported API versions '''
|
||||||
|
|
||||||
current = reverse('main:api_v1_root_view', args=[])
|
current = reverse('main:api_v1_root_view', args=[])
|
||||||
data = dict(
|
data = dict(
|
||||||
description = 'Ansible Commander REST API',
|
description = 'AnsibleWorks REST API',
|
||||||
current_version = current,
|
current_version = current,
|
||||||
available_versions = dict(
|
available_versions = dict(
|
||||||
v1 = current
|
v1 = current
|
||||||
@@ -71,10 +71,11 @@ class ApiRootView(APIView):
|
|||||||
class ApiV1RootView(APIView):
|
class ApiV1RootView(APIView):
|
||||||
'''
|
'''
|
||||||
Version 1 of the REST API.
|
Version 1 of the REST API.
|
||||||
|
|
||||||
|
Subject to change until the final 1.2 release.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def get_name(self):
|
view_name = 'Version 1'
|
||||||
return 'Version 1'
|
|
||||||
|
|
||||||
def get(self, request, format=None):
|
def get(self, request, format=None):
|
||||||
''' list top level resources '''
|
''' list top level resources '''
|
||||||
@@ -431,8 +432,7 @@ class UsersMeList(BaseList):
|
|||||||
permission_classes = (CustomRbac,)
|
permission_classes = (CustomRbac,)
|
||||||
filter_fields = ('username',)
|
filter_fields = ('username',)
|
||||||
|
|
||||||
def get_name(self):
|
view_name = 'Me!'
|
||||||
return 'Me!'
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
@@ -973,8 +973,7 @@ class BaseJobHostSummaryList(generics.ListAPIView):
|
|||||||
parent_model = None # Subclasses must define this attribute.
|
parent_model = None # Subclasses must define this attribute.
|
||||||
relationship = 'job_host_summaries'
|
relationship = 'job_host_summaries'
|
||||||
|
|
||||||
def get_name(self):
|
view_name = 'Job Host Summary List'
|
||||||
return 'Job Host Summary List'
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
# FIXME: Verify read permission on the parent object and job.
|
# FIXME: Verify read permission on the parent object and job.
|
||||||
|
|||||||
@@ -29,10 +29,14 @@ REST_FRAMEWORK = {
|
|||||||
'PAGINATE_BY': 25,
|
'PAGINATE_BY': 25,
|
||||||
'PAGINATE_BY_PARAM': 'page_size',
|
'PAGINATE_BY_PARAM': 'page_size',
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
'rest_framework.authentication.TokenAuthentication',
|
|
||||||
'rest_framework.authentication.BasicAuthentication',
|
'rest_framework.authentication.BasicAuthentication',
|
||||||
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
'rest_framework.authentication.SessionAuthentication',
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
)
|
),
|
||||||
|
'DEFAULT_RENDERER_CLASSES': (
|
||||||
|
'rest_framework.renderers.JSONRenderer',
|
||||||
|
'ansibleworks.main.renderers.BrowsableAPIRenderer',
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
|
|||||||
@@ -1,27 +1,51 @@
|
|||||||
{% extends 'rest_framework/base.html' %}
|
{% extends 'rest_framework/base.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block title %}{% trans 'AnsibleWorks API' %}{% endblock %}
|
{% block title %}{% trans 'AnsibleWorks REST API' %}{% endblock %}
|
||||||
|
|
||||||
{% block style %}
|
{% block style %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
<link href="{{ STATIC_URL }}favicon.ico" rel="shortcut icon" />
|
<link href="{{ STATIC_URL }}img/favicon.ico" rel="shortcut icon" />
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
html body {
|
html body {
|
||||||
background: #ddd;
|
background: #ddd;
|
||||||
}
|
}
|
||||||
html body .navbar .navbar-inner {
|
html body .navbar .navbar-inner {
|
||||||
background: #1778c3;
|
|
||||||
border-top: none;
|
border-top: none;
|
||||||
border-bottom: solid 3px #074979;
|
|
||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
html body .navbar-inverse .navbar-inner {
|
||||||
|
background-color: #36454F;
|
||||||
|
background-image: -moz-linear-gradient(top, #36454F, #36454F);
|
||||||
|
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#36454F), to(#36454F));
|
||||||
|
background-image: -webkit-linear-gradient(top, #36454F, #36454F);
|
||||||
|
background-image: -o-linear-gradient(top, #36454F, #36454F);
|
||||||
|
background-image: linear-gradient(to bottom, #36454F, #36454F);
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
border-color: #36454F;
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#36454F', endColorstr='#36454F', GradientType=0);
|
||||||
|
}
|
||||||
|
html body .navbar-inverse .nav > li > a {
|
||||||
|
color: #A9A9A9;
|
||||||
|
}
|
||||||
|
html body .navbar-inverse .nav > li > a:hover,
|
||||||
|
html body .navbar-inverse .nav > li > a:focus {
|
||||||
|
color: #2078be;
|
||||||
|
}
|
||||||
|
html body .navbar .brand img {
|
||||||
|
width: 130px;
|
||||||
|
margin-top: -6px;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
html body .navbar-inverse .nav li.dropdown.open > .dropdown-toggle,
|
html body .navbar-inverse .nav li.dropdown.open > .dropdown-toggle,
|
||||||
html body .navbar-inverse .nav li.dropdown.active > .dropdown-toggle,
|
html body .navbar-inverse .nav li.dropdown.active > .dropdown-toggle,
|
||||||
html body .navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle {
|
html body .navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle {
|
||||||
background-color: #074979;
|
background-color: #074979;
|
||||||
}
|
}
|
||||||
html body .navbar-inverse .brand a,
|
html body .navbar-inverse .brand {
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
span.powered-by .version {
|
span.powered-by .version {
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
}
|
}
|
||||||
@@ -35,25 +59,48 @@ html body .hero-unit h2,
|
|||||||
html body .hero-unit h1,
|
html body .hero-unit h1,
|
||||||
html body a,
|
html body a,
|
||||||
html body a {
|
html body a {
|
||||||
color: #2773ae;
|
color: #2078be;
|
||||||
}
|
}
|
||||||
html body .navbar .navbar-inner .dropdown-menu li a:hover,
|
html body .navbar .navbar-inner .dropdown-menu li a:hover,
|
||||||
html body a:hover {
|
html body a:hover {
|
||||||
color: #074979;
|
color: #2078be;
|
||||||
|
}
|
||||||
|
html body ul.breadcrumb,
|
||||||
|
html body .prettyprint,
|
||||||
|
html body .well.tab-content {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
html body .prettyprint {
|
||||||
|
background: #f5f5f5;
|
||||||
}
|
}
|
||||||
html body .str,
|
html body .str,
|
||||||
html body .atv {
|
html body .atv {
|
||||||
color: #1778c3;
|
color: #074979;
|
||||||
}
|
}
|
||||||
html body .str a {
|
html body .str a {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
html body .page-header {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
html body .description {
|
||||||
|
padding-bottom: 0;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
font-size: 0.8em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.footer a,
|
||||||
|
.footer a:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block branding %}
|
{% block branding %}
|
||||||
{% trans 'AnsibleWorks API' %}
|
<a class="brand" href="/api/"><img class="logo" src="{{ STATIC_URL }}img/ansibleworks-logo.png">{% trans 'REST API' %}</a>
|
||||||
<span class="powered-by">({% trans 'powered by' %} {{ block.super }})</span>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block userlinks %}
|
{% block userlinks %}
|
||||||
@@ -64,6 +111,11 @@ html body .str a {
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer %}
|
||||||
|
<div class="footer">Copyright © 2013 <a href="http://www.ansibleworks.com/">AnsibleWorks, Inc.</a> All rights reserved.<br />
|
||||||
|
1482 East Valley Road, Suite 888 · Montecito, California 9308 · <a href="tel:18008250212">+1-800-825-0212<a/></div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@@ -75,6 +127,19 @@ $(function() {
|
|||||||
$(this).html('"<a href=' + s + '>' + s.replace(/\"/g, '') + '</a>"');
|
$(this).html('"<a href=' + s + '>' + s.replace(/\"/g, '') + '</a>"');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if ($('.description').html()) {
|
||||||
|
$('.description').addClass('well').addClass('well-small').addClass('prettyprint');
|
||||||
|
$('.description').prepend('<a class="hide-description pull-right" href="#" title="Hide Description"><i class="icon-remove"></i></a>');
|
||||||
|
$('a.hide-description').click(function() {
|
||||||
|
$('.description').slideUp('fast');
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
$('.page-header h1').append('<a class="toggle-description" href="#" title="Show/Hide Description"><i class="icon-question-sign"></i></a>');
|
||||||
|
$('a.toggle-description').click(function() {
|
||||||
|
$('.description').slideToggle('fast');
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
$('.btn-primary').removeClass('btn-primary').addClass('btn-success');
|
$('.btn-primary').removeClass('btn-primary').addClass('btn-success');
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ django-extensions
|
|||||||
django-filter
|
django-filter
|
||||||
django-jsonfield
|
django-jsonfield
|
||||||
django-taggit
|
django-taggit
|
||||||
djangorestframework
|
djangorestframework>=2.3.0,<2.4.0
|
||||||
Markdown
|
Markdown
|
||||||
pexpect
|
pexpect
|
||||||
python-dateutil
|
python-dateutil
|
||||||
|
|||||||
19
setup.py
19
setup.py
@@ -30,28 +30,13 @@ setup(
|
|||||||
'django-filter',
|
'django-filter',
|
||||||
'django-jsonfield',
|
'django-jsonfield',
|
||||||
'django-taggit',
|
'django-taggit',
|
||||||
'djangorestframework',
|
'djangorestframework>=2.3.0,<2.4.0',
|
||||||
'pexpect',
|
'pexpect',
|
||||||
'python-dateutil',
|
'python-dateutil',
|
||||||
'PyYAML',
|
'PyYAML',
|
||||||
'South',
|
'South>=0.8,<2.0',
|
||||||
],
|
],
|
||||||
setup_requires=[],
|
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=[
|
classifiers=[
|
||||||
'Development Status :: 4 - Beta',
|
'Development Status :: 4 - Beta',
|
||||||
'Environment :: Web Environment',
|
'Environment :: Web Environment',
|
||||||
|
|||||||
Reference in New Issue
Block a user