Implement creating new App from other App Creation dialog.
Any field type unspecified in dynamic UI code is considered to be fully qualified name (FQN) of some Application. Currently only apps of that exact FQN will be present in drop-down list, in future all descendants will also be supported. New form field/widget (as well as js-code) is taken almost ready from horizon.forms.fields.DynamicChoiceFields, with a bit of copy-paste from horizon in django Wizard done() method (it is not possible to directly inherit the required functionality from horizon.forms.views.ModalFormView, because it is incompatible with django WizardView. Change-Id: I942884a9a98857c13002095c8d8bb96331d2e555 Implements: blueprint new-web-ui-prototype
This commit is contained in:
parent
ef985ae7a1
commit
21604e9329
@ -79,6 +79,15 @@ class AppCatalogObjects(object):
|
||||
def get(self, **kwargs):
|
||||
return self.app_list[kwargs.pop('app_id')]
|
||||
|
||||
def filter(self, **kwargs):
|
||||
def filter_func(_app):
|
||||
for key, value in kwargs.iteritems():
|
||||
if not hasattr(_app, key) or getattr(_app, key) != value:
|
||||
return False
|
||||
return True
|
||||
|
||||
return [app for app in self.app_list.itervalues() if filter_func(app)]
|
||||
|
||||
def get_ui(self, **kwargs):
|
||||
app_id = kwargs.pop('app_id')
|
||||
url = '{0}/{1}/ui'.format(self.url, app_id)
|
||||
|
@ -28,6 +28,7 @@ import copy
|
||||
import types
|
||||
import logging
|
||||
import horizon.tables as tables
|
||||
import horizon.forms as hz_forms
|
||||
import floppyforms
|
||||
from django.template.loader import render_to_string
|
||||
from muranoclient.common import exceptions as muranoclient_exc
|
||||
@ -657,3 +658,23 @@ class PostgreSqlChoiceField(ChoiceField):
|
||||
self.choices = [('', _('(No Database)'))]
|
||||
psql = api.service_list_by_type(request, environment_id, 'postgreSql')
|
||||
self.choices.extend([(srv.id, srv.name) for srv in psql])
|
||||
|
||||
|
||||
def make_select_cls(fqn):
|
||||
class DynamicSelect(hz_forms.DynamicChoiceField, CustomPropertiesField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DynamicSelect, self).__init__(*args, **kwargs)
|
||||
self.widget.add_item_link = 'horizon:murano:catalog:add'
|
||||
|
||||
@with_request
|
||||
def update(self, request, environment_id, **kwargs):
|
||||
app_id = api.app_id_by_fqn(request, fqn)
|
||||
if app_id is None:
|
||||
msg = "Application with FQN='{0}' doesn't exist"
|
||||
raise KeyError(msg.format(fqn))
|
||||
self.widget.add_item_link_args = (environment_id, app_id)
|
||||
self.choices = [('', _('Select Application'))]
|
||||
apps = api.service_list_by_id(request, environment_id, app_id)
|
||||
self.choices.extend([(app.id, app.name) for app in apps])
|
||||
|
||||
return DynamicSelect
|
||||
|
@ -12,6 +12,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
import logging
|
||||
import types
|
||||
|
||||
@ -24,7 +25,14 @@ import yaql
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
TYPES = {
|
||||
|
||||
class AnyFieldDict(collections.defaultdict):
|
||||
def __missing__(self, key):
|
||||
return fields.make_select_cls(key)
|
||||
|
||||
|
||||
TYPES = AnyFieldDict()
|
||||
TYPES.update({
|
||||
'string': fields.CharField,
|
||||
'boolean': fields.BooleanField,
|
||||
'clusterip': fields.ClusterIPField,
|
||||
@ -40,7 +48,7 @@ TYPES = {
|
||||
'text': (fields.CharField, forms.Textarea),
|
||||
'floatingip': fields.FloatingIpBooleanField,
|
||||
'psqlDatabase': fields.PostgreSqlChoiceField
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
def _collect_fields(field_specs, form_name, service):
|
||||
|
@ -25,6 +25,7 @@ from consts import STATUS_ID_READY, STATUS_ID_NEW
|
||||
from .network import get_network_params
|
||||
|
||||
from muranodashboard.environments import format
|
||||
from muranodashboard.catalog import models
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -258,11 +259,15 @@ def services_list(request, environment_id):
|
||||
return [bunch.bunchify(service) for service in services]
|
||||
|
||||
|
||||
def service_list_by_type(request, environment_id, service_type):
|
||||
def app_id_by_fqn(request, fqn):
|
||||
apps = models.AppCatalogModel().objects.filter(fqn=fqn)
|
||||
return apps[0].id if apps else None
|
||||
|
||||
|
||||
def service_list_by_id(request, environment_id, app_id):
|
||||
services = services_list(request, environment_id)
|
||||
log.debug('Service::Instances::List')
|
||||
return [service for service in services
|
||||
if service['type'] == service_type]
|
||||
return [service for service in services if service['app_id'] == app_id]
|
||||
|
||||
|
||||
def service_create(request, environment_id, parameters):
|
||||
|
@ -12,6 +12,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import copy
|
||||
from functools import update_wrapper
|
||||
@ -27,7 +28,7 @@ from horizon import tabs
|
||||
from horizon import tables
|
||||
from horizon import workflows
|
||||
from horizon import messages
|
||||
from horizon.forms.views import ModalFormMixin
|
||||
from horizon.forms import views as hz_views
|
||||
from tables import EnvironmentsTable
|
||||
from tables import DeploymentsTable
|
||||
from tables import EnvConfigTable
|
||||
@ -41,6 +42,7 @@ from muranodashboard.dynamic_ui.services import get_service_name
|
||||
from muranodashboard.dynamic_ui.services import get_service_field_descriptions
|
||||
from muranodashboard.catalog import models
|
||||
from muranodashboard.dynamic_ui import helpers
|
||||
from muranodashboard import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -89,9 +91,14 @@ class LazyWizard(SessionWizardView):
|
||||
return view
|
||||
|
||||
|
||||
class Wizard(ModalFormMixin, LazyWizard):
|
||||
class Wizard(hz_views.ModalFormMixin, LazyWizard):
|
||||
template_name = 'services/wizard_create.html'
|
||||
|
||||
def get_prefix(self, *args, **kwargs):
|
||||
base = super(Wizard, self).get_prefix(*args, **kwargs)
|
||||
fmt = utils.BlankFormatter()
|
||||
return fmt.format('{0}_{environment_id}_{app_id}', base, **kwargs)
|
||||
|
||||
def done(self, form_list, **kwargs):
|
||||
environment_id = kwargs.get('environment_id')
|
||||
url = reverse('horizon:murano:environments:services',
|
||||
@ -105,22 +112,35 @@ class Wizard(ModalFormMixin, LazyWizard):
|
||||
attributes = helpers.insert_hidden_ids(attributes)
|
||||
|
||||
try:
|
||||
api.service_create(self.request, environment_id, attributes)
|
||||
srv = api.service_create(self.request, environment_id, attributes)
|
||||
except HTTPForbidden:
|
||||
msg = _('Sorry, you can\'t create service right now.'
|
||||
'The environment is deploying.')
|
||||
msg = _("Sorry, you can\'t add application right now."
|
||||
"The environment is deploying.")
|
||||
redirect = reverse("horizon:murano:environments:index")
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
except Exception:
|
||||
redirect = reverse("horizon:murano:environments:index")
|
||||
exceptions.handle(self.request,
|
||||
_('Sorry, you can\'t create service right now.'),
|
||||
_("Sorry, you can't add application right now."),
|
||||
redirect=redirect)
|
||||
else:
|
||||
message = "The %s service successfully created." % \
|
||||
get_service_name(self.request, app_id)
|
||||
messages.success(self.request, message)
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
return self.create_hacked_response(srv.id, attributes['name'])
|
||||
#return HttpResponseRedirect(url)
|
||||
|
||||
def create_hacked_response(self, obj_id, obj_name):
|
||||
# copy-paste from horizon.forms.views.ModalFormView; should be done
|
||||
# that way until we move here from django Wizard to horizon workflow
|
||||
if hz_views.ADD_TO_FIELD_HEADER in self.request.META:
|
||||
field_id = self.request.META[hz_views.ADD_TO_FIELD_HEADER]
|
||||
response = HttpResponse(json.dumps([obj_id, obj_name]))
|
||||
response["X-Horizon-Add-To-Field"] = field_id
|
||||
return response
|
||||
else:
|
||||
return HttpResponse('Done')
|
||||
|
||||
def get_form_initial(self, step):
|
||||
init_dict = {}
|
||||
@ -140,7 +160,8 @@ class Wizard(ModalFormMixin, LazyWizard):
|
||||
context.update({'type': app.fqn,
|
||||
'service_name': app.name,
|
||||
'app_id': app_id,
|
||||
'environment_id': self.kwargs.get('environment_id')
|
||||
'environment_id': self.kwargs.get('environment_id'),
|
||||
'prefix': self.prefix
|
||||
})
|
||||
return context
|
||||
|
||||
|
@ -41,14 +41,18 @@
|
||||
{% block modal-footer %}
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
$(".btn-wizard").click(function() {
|
||||
$('#wizard_goto_step').val('{{ wizard.steps.prev }}')
|
||||
{# Make element ids unique per-wizard to avoid interference #}
|
||||
{# upon pressing 'Back' button while creating one application #}
|
||||
{# from another #}
|
||||
var btn_id = '#{{ prefix }}_btn', val_id = '#{{ prefix }}_val';
|
||||
$(btn_id).click(function() {
|
||||
$(val_id).val('{{ wizard.steps.prev }}')
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{ wizard.form.media }}
|
||||
|
||||
<input type="hidden" name="wizard_goto_step" id="wizard_goto_step"/>
|
||||
<input type="hidden" name="wizard_goto_step" id="{{ prefix }}_val"/>
|
||||
{% if wizard.steps.next %}
|
||||
{% trans "Next" as next %}
|
||||
{% else %}
|
||||
@ -56,10 +60,10 @@
|
||||
{% endif %}
|
||||
{% if wizard.steps.index > 0 %}
|
||||
<input type="submit" class="btn btn-primary pull-right" value="{{ next }}"/>
|
||||
<button name="wizard_goto_step" type="submit" class="btn btn-small btn-wizard">
|
||||
{% trans "Back" %}</button>
|
||||
<button name="wizard_goto_step" type="submit" class="btn btn-small"
|
||||
id="{{ prefix }}_btn">{% trans "Back" %}</button>
|
||||
{% else %}
|
||||
<button name="wizard_goto_step" type="submit" class="btn btn-small btn-wizard">
|
||||
<button name="wizard_goto_step" type="submit" class="btn btn-small">
|
||||
{{ next }}</button>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
29
muranodashboard/utils.py
Normal file
29
muranodashboard/utils.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Copyright (c) 2014 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import string
|
||||
|
||||
|
||||
class BlankFormatter(string.Formatter):
|
||||
"""
|
||||
Utility class aimed to provide empty string for non-existent keys.
|
||||
"""
|
||||
def __init__(self, default=''):
|
||||
self.default = default
|
||||
|
||||
def get_value(self, key, args, kwargs):
|
||||
if isinstance(key, str):
|
||||
return kwargs.get(key, self.default)
|
||||
else:
|
||||
return string.Formatter.get_value(self, key, args, kwargs)
|
@ -6,5 +6,5 @@ six>=1.5.2
|
||||
PyYAML>=3.1.0
|
||||
django-floppyforms>=1.1
|
||||
yaql==0.2
|
||||
python-muranoclient==0.4.1
|
||||
http://tarballs.openstack.org/python-muranoclient/python-muranoclient-master.tar.gz#egg=muranoclient-0.5
|
||||
murano-metadataclient==0.4.1
|
||||
|
Loading…
Reference in New Issue
Block a user