Add "Preview Stack" action to Stacks table

This patch set adds "Preview Stack" button to Stacks table
to provide user with a possibility to preview stack without
creating it, as it is already implemented in CLI.

Partially implements blueprint: heat-ui-improvement

Change-Id: Idf92deb57f8213a403f102db467828087d91e79a
This commit is contained in:
Tatiana Ovchinnikova 2015-02-20 07:34:37 +03:00
parent d844b0f13b
commit cab3912b69
13 changed files with 247 additions and 0 deletions

View File

@ -102,6 +102,10 @@ def stack_create(request, password=None, **kwargs):
return heatclient(request, password).stacks.create(**kwargs)
def stack_preview(request, password=None, **kwargs):
return heatclient(request, password).stacks.preview(**kwargs)
def stack_update(request, stack_id, password=None, **kwargs):
return heatclient(request, password).stacks.update(stack_id, **kwargs)

View File

@ -4,6 +4,7 @@
"cloudformation:ListStacks": "rule:deny_stack_user",
"cloudformation:CreateStack": "rule:deny_stack_user",
"cloudformation:PreviewStack": "rule:deny_stack_user",
"cloudformation:DescribeStacks": "rule:deny_stack_user",
"cloudformation:DeleteStack": "rule:deny_stack_user",
"cloudformation:UpdateStack": "rule:deny_stack_user",

View File

@ -235,6 +235,12 @@ class ChangeTemplateForm(TemplateForm):
'readonly'}))
class PreviewTemplateForm(TemplateForm):
class Meta(object):
name = _('Preview Template')
help_text = _('Select a new template to preview a stack.')
class CreateStackForm(forms.SelfHandlingForm):
param_prefix = '__param_'
@ -429,3 +435,40 @@ class EditStackForm(CreateStackForm):
return True
except Exception:
exceptions.handle(request)
class PreviewStackForm(CreateStackForm):
class Meta(object):
name = _('Preview Stack Parameters')
def __init__(self, *args, **kwargs):
self.next_view = kwargs.pop('next_view')
super(CreateStackForm, self).__init__(*args, **kwargs)
def handle(self, request, data):
prefix_length = len(self.param_prefix)
params_list = [(k[prefix_length:], v) for (k, v) in six.iteritems(data)
if k.startswith(self.param_prefix)]
fields = {
'stack_name': data.get('stack_name'),
'timeout_mins': data.get('timeout_mins'),
'disable_rollback': not(data.get('enable_rollback')),
'parameters': dict(params_list),
}
if data.get('template_data'):
fields['template'] = data.get('template_data')
else:
fields['template_url'] = data.get('template_url')
if data.get('environment_data'):
fields['environment'] = data.get('environment_data')
try:
stack_preview = api.heat.stack_preview(self.request, **fields)
request.method = 'GET'
return self.next_view.as_view()(request,
stack_preview=stack_preview)
except Exception:
exceptions.handle(request)

View File

@ -36,6 +36,14 @@ class LaunchStack(tables.LinkAction):
policy_rules = (("orchestration", "cloudformation:CreateStack"),)
class PreviewStack(tables.LinkAction):
name = "preview"
verbose_name = _("Preview Stack")
url = "horizon:project:stacks:preview_template"
classes = ("ajax-modal",)
policy_rules = (("orchestration", "cloudformation:PreviewStack"),)
class CheckStack(tables.BatchAction):
name = "check"
verbose_name = _("Check Stack")
@ -277,6 +285,7 @@ class StacksTable(tables.DataTable):
status_columns = ["status", ]
row_class = StacksUpdateRow
table_actions = (LaunchStack,
PreviewStack,
CheckStack,
SuspendStack,
ResumeStack,

View File

@ -0,0 +1,6 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<h3>{% trans "Description:" %}</h3>
<p>{% trans "Preview a new stack with the provided values." %}</p>
{% endblock %}

View File

@ -0,0 +1,59 @@
{% extends "horizon/common/_modal.html" %}
{% load i18n %}
{% load url from future %}
{% block modal-header %}{% trans "Stack Preview" %}{% endblock %}
{% block modal-body %}
<div class="row-fluid stack-preview detail">
<form>
<dl class="dl-horizontal">
{% for key, value in stack_preview.items %}
{% if key != 'parameters' and key != 'resources' and key != 'links' %}
<dt>{{ key }}</dt>
<dd>{{ value }}</dd>
{% endif %}
{% endfor %}
</dl>
{% if stack_preview.parameters %}
<dt>{% trans "Parameters" %}</dt>
<hr class="header_rule">
<dl class="dl-horizontal">
{% for key, value in stack_preview.parameters.items %}
<dt>{{ key }}</dt>
<dd>{{ value }}</dd>
{% endfor %}
</dl>
{% endif %}
{% if stack_preview.links %}
<dt>{% trans "Links" %}</dt>
<hr class="header_rule">
{% for link in stack_preview.links %}
<dl class="dl-horizontal">
<dt>{{ link.rel }}</dt>
<dd>{{ link.href }}</dd>
</dl>
{% endfor %}
{% endif %}
{% if stack_preview.resources %}
<dt>{% trans "Resources" %}</dt>
{% for resource in stack_preview.resources %}
<hr class="header_rule">
<dl class="dl-horizontal">
{% for key, value in resource.items %}
<dt>{{ key }}</dt>
<dd>{{ value }}</dd>
{% endfor %}
</dl>
{% endfor %}
{% endif %}
</form>
</div>
{% endblock %}
{% block modal-footer %}
<a href="{% url 'horizon:project:access_and_security:index' %}" class="btn btn-default secondary cancel close">{% trans "Close" %}</a>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
{% block modal-body-right %}
<h3>{% trans "Description:" %}</h3>
<p>{% trans "Use one of the available template source options to specify the template to be used in previewing this stack." %}</p>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Preview Stack" %}{% endblock %}
{% block main %}
{% include 'project/stacks/_preview.html' %}
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Preview Stack Details" %}{% endblock %}
{% block main %}
{% include 'project/stacks/_preview_details.html' %}
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Preview Template" %}{% endblock %}
{% block main %}
{% include 'project/stacks/_preview_template.html' %}
{% endblock %}

View File

@ -740,6 +740,54 @@ class StackTests(test.TestCase):
def test_resume_stack(self):
self._test_stack_action('resume')
@test.create_stubs({api.heat: ('stack_preview', 'template_validate')})
def test_preview_stack(self):
template = self.stack_templates.first()
stack = self.stacks.first()
api.heat.template_validate(IsA(http.HttpRequest),
template=template.data) \
.AndReturn(json.loads(template.validate))
api.heat.stack_preview(IsA(http.HttpRequest),
stack_name=stack.stack_name,
timeout_mins=60,
disable_rollback=True,
template=template.data,
parameters=IsA(dict)).AndReturn(stack)
self.mox.ReplayAll()
url = reverse('horizon:project:stacks:preview_template')
res = self.client.get(url)
self.assertTemplateUsed(res, 'project/stacks/preview_template.html')
form_data = {'template_source': 'raw',
'template_data': template.data,
'method': forms.PreviewTemplateForm.__name__}
res = self.client.post(url, form_data)
self.assertTemplateUsed(res, 'project/stacks/preview.html')
url = reverse('horizon:project:stacks:preview')
form_data = {'template_source': 'raw',
'template_data': template.data,
'parameters': template.validate,
'stack_name': stack.stack_name,
"timeout_mins": 60,
"disable_rollback": True,
"__param_DBUsername": "admin",
"__param_LinuxDistribution": "F17",
"__param_InstanceType": "m1.small",
"__param_KeyName": "test",
"__param_DBPassword": "admin",
"__param_DBRootPassword": "admin",
"__param_DBName": "wordpress",
'method': forms.PreviewStackForm.__name__}
res = self.client.post(url, form_data)
self.assertTemplateUsed(res, 'project/stacks/preview_details.html')
self.assertEqual(res.context['stack_preview']['stack_name'],
stack.stack_name)
class TemplateFormTests(test.TestCase):

View File

@ -22,6 +22,11 @@ urlpatterns = patterns(
views.SelectTemplateView.as_view(),
name='select_template'),
url(r'^launch$', views.CreateStackView.as_view(), name='launch'),
url(r'^preview_template$',
views.PreviewTemplateView.as_view(), name='preview_template'),
url(r'^preview$', views.PreviewStackView.as_view(), name='preview'),
url(r'^preview_details$',
views.PreviewStackDetailsView.as_view(), name='preview_details'),
url(r'^stack/(?P<stack_id>[^/]+)/$',
views.DetailView.as_view(), name='detail'),
url(r'^(?P<stack_id>[^/]+)/change_template$',

View File

@ -27,6 +27,7 @@ from horizon import forms
from horizon import tables
from horizon import tabs
from horizon.utils import memoized
from horizon import views
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.stacks \
import api as project_api
@ -130,6 +131,22 @@ class ChangeTemplateView(forms.ModalFormView):
return kwargs
class PreviewTemplateView(forms.ModalFormView):
template_name = 'project/stacks/preview_template.html'
modal_header = _("Preview Template")
form_id = "preview_template"
form_class = project_forms.PreviewTemplateForm
submit_label = _("Next")
submit_url = reverse_lazy('horizon:project:stacks:preview_template')
success_url = reverse_lazy('horizon:project:stacks:preview')
page_title = _("Preview Template")
def get_form_kwargs(self):
kwargs = super(PreviewTemplateView, self).get_form_kwargs()
kwargs['next_view'] = PreviewStackView
return kwargs
class CreateStackView(forms.ModalFormView):
form_class = project_forms.CreateStackForm
template_name = 'project/stacks/create.html'
@ -198,6 +215,33 @@ class EditStackView(CreateStackView):
return self._stack
class PreviewStackView(CreateStackView):
template_name = 'project/stacks/preview.html'
modal_header = _("Preview Stack")
form_id = "preview_stack"
form_class = project_forms.PreviewStackForm
submit_label = _("Preview")
submit_url = reverse_lazy('horizon:project:stacks:preview')
success_url = reverse_lazy('horizon:project:stacks:index')
page_title = _("Preview Stack")
def get_form_kwargs(self):
kwargs = super(CreateStackView, self).get_form_kwargs()
kwargs['next_view'] = PreviewStackDetailsView
return kwargs
class PreviewStackDetailsView(forms.ModalFormMixin, views.HorizonTemplateView):
template_name = 'project/stacks/preview_details.html'
page_title = _("Preview Stack Details")
def get_context_data(self, **kwargs):
context = super(
PreviewStackDetailsView, self).get_context_data(**kwargs)
context['stack_preview'] = self.kwargs['stack_preview'].to_dict()
return context
class DetailView(tabs.TabView):
tab_group_class = project_tabs.StackDetailTabs
template_name = 'project/stacks/detail.html'