Angularize metadata update modals
Update table action to use new angular modal dialog. Remove "Metadata Update" views. Implemenents: blueprint angularize-metadata-update-modals Change-Id: I7b3d2eab8c0830dc44a45901981eb0e71009c038
This commit is contained in:
parent
f030262521
commit
3c2ce866df
@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
(function () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
angular
|
|
||||||
.module('horizon.framework.util.tech-debt')
|
|
||||||
.controller('hzModalFormUpdateMetadataController', hzModalFormUpdateMetadataController);
|
|
||||||
|
|
||||||
hzModalFormUpdateMetadataController.$inject = ['$scope', '$window'];
|
|
||||||
|
|
||||||
function hzModalFormUpdateMetadataController($scope, $window) {
|
|
||||||
var ctrl = this;
|
|
||||||
|
|
||||||
ctrl.tree = null;
|
|
||||||
ctrl.available = $window.available_metadata.namespaces;
|
|
||||||
ctrl.existing = $window.existing_metadata;
|
|
||||||
|
|
||||||
ctrl.saveMetadata = function () {
|
|
||||||
var metadata = [];
|
|
||||||
angular.forEach(ctrl.tree.getExisting(), function (value, key) {
|
|
||||||
metadata.push({
|
|
||||||
key: key,
|
|
||||||
value: value
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
ctrl.metadata = angular.toJson(metadata);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}());
|
|
@ -1,29 +0,0 @@
|
|||||||
{% extends "horizon/common/_modal_form.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block form_name %}metadataForm{% endblock %}
|
|
||||||
{% block form_validation %}novalidate{% endblock %}
|
|
||||||
{% block ng_controller %}hzModalFormUpdateMetadataController as metadataCtrl{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-body %}
|
|
||||||
<metadata-tree available="metadataCtrl.available"
|
|
||||||
existing="metadataCtrl.existing"
|
|
||||||
model="metadataCtrl.tree"></metadata-tree>
|
|
||||||
<script type="text/javascript">
|
|
||||||
var existing_metadata = JSON.parse('{{ existing_metadata|escapejs }}');
|
|
||||||
var available_metadata = JSON.parse('{{ available_metadata|escapejs }}');
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-footer %}
|
|
||||||
<div>
|
|
||||||
<input class="btn btn-primary pull-right"
|
|
||||||
ng-disabled="metadataForm.$invalid"
|
|
||||||
ng-click="metadataCtrl.saveMetadata()" type="submit"
|
|
||||||
value="{% trans "Save" %}"/>
|
|
||||||
<a class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
|
||||||
<input type="hidden" name="metadata" ng-value="metadataCtrl.metadata"
|
|
||||||
ng-model="metadataCtrl.metadata">
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -17,9 +17,5 @@ AGGREGATES_CREATE_URL = 'horizon:admin:aggregates:create'
|
|||||||
AGGREGATES_CREATE_VIEW_TEMPLATE = 'admin/aggregates/create.html'
|
AGGREGATES_CREATE_VIEW_TEMPLATE = 'admin/aggregates/create.html'
|
||||||
AGGREGATES_MANAGE_HOSTS_URL = 'horizon:admin:aggregates:manage_hosts'
|
AGGREGATES_MANAGE_HOSTS_URL = 'horizon:admin:aggregates:manage_hosts'
|
||||||
AGGREGATES_MANAGE_HOSTS_TEMPLATE = 'admin/aggregates/manage_hosts.html'
|
AGGREGATES_MANAGE_HOSTS_TEMPLATE = 'admin/aggregates/manage_hosts.html'
|
||||||
AGGREGATES_UPDATE_METADATA_URL = 'horizon:admin:aggregates:update_metadata'
|
|
||||||
AGGREGATES_UPDATE_METADATA_TEMPLATE = 'admin/aggregates/update_metadata.html'
|
|
||||||
AGGREGATES_UPDATE_METADATA_SUBTEMPLATE = \
|
|
||||||
'admin/aggregates/_update_metadata.html'
|
|
||||||
AGGREGATES_UPDATE_URL = 'horizon:admin:aggregates:update'
|
AGGREGATES_UPDATE_URL = 'horizon:admin:aggregates:update'
|
||||||
AGGREGATES_UPDATE_VIEW_TEMPLATE = 'admin/aggregates/update.html'
|
AGGREGATES_UPDATE_VIEW_TEMPLATE = 'admin/aggregates/update.html'
|
||||||
|
@ -10,8 +10,6 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from horizon import exceptions
|
from horizon import exceptions
|
||||||
@ -49,32 +47,3 @@ class UpdateAggregateForm(forms.SelfHandlingForm):
|
|||||||
exceptions.handle(request,
|
exceptions.handle(request,
|
||||||
_('Unable to update the aggregate.'))
|
_('Unable to update the aggregate.'))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class UpdateMetadataForm(forms.SelfHandlingForm):
|
|
||||||
|
|
||||||
def handle(self, request, data):
|
|
||||||
id = self.initial['id']
|
|
||||||
old_metadata = self.initial['metadata']
|
|
||||||
|
|
||||||
try:
|
|
||||||
new_metadata = json.loads(self.data['metadata'])
|
|
||||||
|
|
||||||
metadata = dict(
|
|
||||||
(item['key'], str(item['value']))
|
|
||||||
for item in new_metadata
|
|
||||||
)
|
|
||||||
|
|
||||||
for key in old_metadata:
|
|
||||||
if key not in metadata:
|
|
||||||
metadata[key] = None
|
|
||||||
|
|
||||||
api.nova.aggregate_set_metadata(request, id, metadata)
|
|
||||||
message = _('Metadata successfully updated.')
|
|
||||||
messages.success(request, message)
|
|
||||||
except Exception:
|
|
||||||
msg = _('Unable to update the aggregate metadata.')
|
|
||||||
exceptions.handle(request, msg)
|
|
||||||
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
@ -62,9 +62,19 @@ class ManageHostsAction(tables.LinkAction):
|
|||||||
class UpdateMetadataAction(tables.LinkAction):
|
class UpdateMetadataAction(tables.LinkAction):
|
||||||
name = "update-metadata"
|
name = "update-metadata"
|
||||||
verbose_name = _("Update Metadata")
|
verbose_name = _("Update Metadata")
|
||||||
url = constants.AGGREGATES_UPDATE_METADATA_URL
|
ajax = False
|
||||||
classes = ("ajax-modal",)
|
|
||||||
icon = "pencil"
|
icon = "pencil"
|
||||||
|
attrs = {"ng-controller": "MetadataModalHelperController as modal"}
|
||||||
|
|
||||||
|
def __init__(self, attrs=None, **kwargs):
|
||||||
|
kwargs['preempt'] = True
|
||||||
|
super(UpdateMetadataAction, self).__init__(attrs, **kwargs)
|
||||||
|
|
||||||
|
def get_link_url(self, datum):
|
||||||
|
image_id = self.table.get_object_id(datum)
|
||||||
|
self.attrs['ng-click'] = (
|
||||||
|
"modal.openMetadataModal('aggregate', '%s', true)" % image_id)
|
||||||
|
return "javascript:void(0);"
|
||||||
|
|
||||||
|
|
||||||
class UpdateAggregateAction(tables.LinkAction):
|
class UpdateAggregateAction(tables.LinkAction):
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
{% extends 'horizon/common/_modal_form_update_metadata.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load url from future %}
|
|
||||||
{% block title %}{% trans "Update Aggregate Metadata" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block page_header %}
|
|
||||||
{% include "horizon/common/_page_header.html" with title=_("Update Aggregate Metadata") %}
|
|
||||||
{% endblock page_header %}
|
|
||||||
|
|
||||||
{% block form_action %}{% url 'horizon:admin:aggregates:update_metadata' id %}{% endblock %}
|
|
||||||
{% block modal-header %}{% trans "Update Aggregate Metadata" %}{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Update Aggregate Metadata" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include 'admin/aggregates/_update_metadata.html' %}
|
|
||||||
{% endblock %}
|
|
@ -10,8 +10,6 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
@ -451,82 +449,3 @@ class ManageHostsTests(test.BaseAdminViewTests):
|
|||||||
form_data,
|
form_data,
|
||||||
addAggregate=False,
|
addAggregate=False,
|
||||||
cleanAggregates=True)
|
cleanAggregates=True)
|
||||||
|
|
||||||
|
|
||||||
class HostAggregateMetadataTests(test.BaseAdminViewTests):
|
|
||||||
|
|
||||||
@test.create_stubs({api.nova: ('aggregate_get',),
|
|
||||||
api.glance: ('metadefs_namespace_list',
|
|
||||||
'metadefs_namespace_get')})
|
|
||||||
def test_host_aggregate_metadata_get(self):
|
|
||||||
aggregate = self.aggregates.first()
|
|
||||||
api.nova.aggregate_get(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
str(aggregate.id)
|
|
||||||
).AndReturn(aggregate)
|
|
||||||
|
|
||||||
namespaces = self.metadata_defs.list()
|
|
||||||
|
|
||||||
api.glance.metadefs_namespace_list(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
filters={'resource_types': ['OS::Nova::Aggregate']}
|
|
||||||
).AndReturn((namespaces, False, False))
|
|
||||||
|
|
||||||
for namespace in namespaces:
|
|
||||||
api.glance.metadefs_namespace_get(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
namespace.namespace,
|
|
||||||
'OS::Nova::Aggregate'
|
|
||||||
).AndReturn(namespace)
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
res = self.client.get(
|
|
||||||
reverse(constants.AGGREGATES_UPDATE_METADATA_URL,
|
|
||||||
args=[aggregate.id]))
|
|
||||||
|
|
||||||
self.assertEqual(res.status_code, 200)
|
|
||||||
self.assertTemplateUsed(
|
|
||||||
res,
|
|
||||||
constants.AGGREGATES_UPDATE_METADATA_TEMPLATE
|
|
||||||
)
|
|
||||||
self.assertTemplateUsed(
|
|
||||||
res,
|
|
||||||
constants.AGGREGATES_UPDATE_METADATA_SUBTEMPLATE
|
|
||||||
)
|
|
||||||
self.assertContains(res, 'namespace_1')
|
|
||||||
self.assertContains(res, 'namespace_2')
|
|
||||||
self.assertContains(res, 'namespace_3')
|
|
||||||
self.assertContains(res, 'namespace_4')
|
|
||||||
|
|
||||||
@test.create_stubs({api.nova: ('aggregate_get', 'aggregate_set_metadata')})
|
|
||||||
def test_host_aggregate_metadata_update(self):
|
|
||||||
aggregate = self.aggregates.first()
|
|
||||||
aggregate.metadata = {'key': 'test_key', 'value': 'test_value'}
|
|
||||||
|
|
||||||
api.nova.aggregate_get(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
str(aggregate.id)
|
|
||||||
).AndReturn(aggregate)
|
|
||||||
|
|
||||||
api.nova.aggregate_set_metadata(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
str(aggregate.id),
|
|
||||||
{'value': None, 'key': None, 'test_key': 'test_value'}
|
|
||||||
).AndReturn(None)
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
form_data = {"metadata": json.dumps([aggregate.metadata])}
|
|
||||||
|
|
||||||
res = self.client.post(
|
|
||||||
reverse(constants.AGGREGATES_UPDATE_METADATA_URL,
|
|
||||||
args=(aggregate.id,)), form_data)
|
|
||||||
|
|
||||||
self.assertEqual(res.status_code, 302)
|
|
||||||
self.assertNoFormErrors(res)
|
|
||||||
self.assertMessageCount(success=1)
|
|
||||||
self.assertRedirectsNoFollow(
|
|
||||||
res,
|
|
||||||
reverse(constants.AGGREGATES_INDEX_URL)
|
|
||||||
)
|
|
||||||
|
@ -25,8 +25,6 @@ urlpatterns = patterns(
|
|||||||
views.CreateView.as_view(), name='create'),
|
views.CreateView.as_view(), name='create'),
|
||||||
url(r'^(?P<id>[^/]+)/update/$',
|
url(r'^(?P<id>[^/]+)/update/$',
|
||||||
views.UpdateView.as_view(), name='update'),
|
views.UpdateView.as_view(), name='update'),
|
||||||
url(r'^(?P<id>[^/]+)/update_metadata/$',
|
|
||||||
views.UpdateMetadataView.as_view(), name='update_metadata'),
|
|
||||||
url(r'^(?P<id>[^/]+)/manage_hosts/$',
|
url(r'^(?P<id>[^/]+)/manage_hosts/$',
|
||||||
views.ManageHostsView.as_view(), name='manage_hosts'),
|
views.ManageHostsView.as_view(), name='manage_hosts'),
|
||||||
)
|
)
|
||||||
|
@ -10,16 +10,12 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.core.urlresolvers import reverse_lazy
|
from django.core.urlresolvers import reverse_lazy
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from horizon import exceptions
|
from horizon import exceptions
|
||||||
from horizon import forms
|
from horizon import forms
|
||||||
from horizon import tables
|
from horizon import tables
|
||||||
from horizon.utils import memoized
|
|
||||||
from horizon import workflows
|
from horizon import workflows
|
||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
@ -101,55 +97,6 @@ class UpdateView(forms.ModalFormView):
|
|||||||
return self._object
|
return self._object
|
||||||
|
|
||||||
|
|
||||||
class UpdateMetadataView(forms.ModalFormView):
|
|
||||||
template_name = constants.AGGREGATES_UPDATE_METADATA_TEMPLATE
|
|
||||||
form_class = aggregate_forms.UpdateMetadataForm
|
|
||||||
success_url = reverse_lazy(constants.AGGREGATES_INDEX_URL)
|
|
||||||
page_title = _("Update Aggregate Metadata")
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
aggregate = self.get_object()
|
|
||||||
return {'id': self.kwargs["id"], 'metadata': aggregate.metadata}
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(UpdateMetadataView, self).get_context_data(**kwargs)
|
|
||||||
|
|
||||||
aggregate = self.get_object()
|
|
||||||
context['existing_metadata'] = json.dumps(aggregate.metadata)
|
|
||||||
|
|
||||||
resource_type = 'OS::Nova::Aggregate'
|
|
||||||
namespaces = []
|
|
||||||
try:
|
|
||||||
# metadefs_namespace_list() returns a tuple with list as 1st elem
|
|
||||||
namespaces = [
|
|
||||||
api.glance.metadefs_namespace_get(self.request, x.namespace,
|
|
||||||
resource_type)
|
|
||||||
for x in api.glance.metadefs_namespace_list(
|
|
||||||
self.request,
|
|
||||||
filters={'resource_types': [resource_type]}
|
|
||||||
)[0]
|
|
||||||
]
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
msg = _('Unable to retrieve available metadata for aggregate.')
|
|
||||||
exceptions.handle(self.request, msg)
|
|
||||||
|
|
||||||
context['available_metadata'] = json.dumps({'namespaces': namespaces})
|
|
||||||
context['id'] = self.kwargs['id']
|
|
||||||
return context
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def get_object(self):
|
|
||||||
aggregate_id = self.kwargs['id']
|
|
||||||
try:
|
|
||||||
return api.nova.aggregate_get(self.request, aggregate_id)
|
|
||||||
except Exception:
|
|
||||||
msg = _('Unable to retrieve the aggregate to be '
|
|
||||||
'updated.')
|
|
||||||
exceptions.handle(
|
|
||||||
self.request, msg, redirect=reverse(INDEX_URL))
|
|
||||||
|
|
||||||
|
|
||||||
class ManageHostsView(workflows.WorkflowView):
|
class ManageHostsView(workflows.WorkflowView):
|
||||||
template_name = constants.AGGREGATES_MANAGE_HOSTS_TEMPLATE
|
template_name = constants.AGGREGATES_MANAGE_HOSTS_TEMPLATE
|
||||||
workflow_class = aggregate_workflows.ManageAggregateHostsWorkflow
|
workflow_class = aggregate_workflows.ManageAggregateHostsWorkflow
|
||||||
|
@ -16,6 +16,3 @@ FLAVORS_CREATE_URL = 'horizon:admin:flavors:create'
|
|||||||
FLAVORS_CREATE_VIEW_TEMPLATE = 'admin/flavors/create.html'
|
FLAVORS_CREATE_VIEW_TEMPLATE = 'admin/flavors/create.html'
|
||||||
FLAVORS_UPDATE_URL = 'horizon:admin:flavors:update'
|
FLAVORS_UPDATE_URL = 'horizon:admin:flavors:update'
|
||||||
FLAVORS_UPDATE_VIEW_TEMPLATE = 'admin/flavors/update.html'
|
FLAVORS_UPDATE_VIEW_TEMPLATE = 'admin/flavors/update.html'
|
||||||
FLAVORS_UPDATE_METADATA_URL = 'horizon:admin:flavors:update_metadata'
|
|
||||||
FLAVORS_UPDATE_METADATA_TEMPLATE = 'admin/flavors/update_metadata.html'
|
|
||||||
FLAVORS_UPDATE_METADATA_SUBTEMPLATE = 'admin/flavors/_update_metadata.html'
|
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
# 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 json
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon import forms
|
|
||||||
from horizon import messages
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateMetadataForm(forms.SelfHandlingForm):
|
|
||||||
|
|
||||||
def handle(self, request, data):
|
|
||||||
id = self.initial['id']
|
|
||||||
old_metadata = self.initial['metadata']
|
|
||||||
|
|
||||||
try:
|
|
||||||
new_metadata = json.loads(self.data['metadata'])
|
|
||||||
|
|
||||||
metadata = dict(
|
|
||||||
(item['key'], str(item['value']))
|
|
||||||
for item in new_metadata
|
|
||||||
)
|
|
||||||
api.nova.flavor_extra_set(request, id, metadata)
|
|
||||||
|
|
||||||
remove_keys = [key for key in old_metadata if key not in metadata]
|
|
||||||
|
|
||||||
api.nova.flavor_extra_delete(request, id, remove_keys)
|
|
||||||
|
|
||||||
message = _('Metadata successfully updated.')
|
|
||||||
messages.success(request, message)
|
|
||||||
except Exception:
|
|
||||||
exceptions.handle(request,
|
|
||||||
_('Unable to update the flavor metadata.'))
|
|
||||||
return False
|
|
||||||
return True
|
|
@ -66,11 +66,29 @@ class UpdateFlavor(tables.LinkAction):
|
|||||||
|
|
||||||
|
|
||||||
class UpdateMetadata(tables.LinkAction):
|
class UpdateMetadata(tables.LinkAction):
|
||||||
url = "horizon:admin:flavors:update_metadata"
|
|
||||||
name = "update_metadata"
|
name = "update_metadata"
|
||||||
verbose_name = _("Update Metadata")
|
verbose_name = _("Update Metadata")
|
||||||
classes = ("ajax-modal",)
|
ajax = False
|
||||||
icon = "pencil"
|
icon = "pencil"
|
||||||
|
attrs = {"ng-controller": "MetadataModalHelperController as modal"}
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
kwargs['preempt'] = True
|
||||||
|
super(UpdateMetadata, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
def get_link_url(self, datum):
|
||||||
|
obj_id = self.table.get_object_id(datum)
|
||||||
|
self.attrs['ng-click'] = (
|
||||||
|
"modal.openMetadataModal('flavor', '%s', true)" % obj_id)
|
||||||
|
return "javascript:void(0);"
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateMetadataColumn(tables.Column):
|
||||||
|
def get_link_url(self, datum):
|
||||||
|
obj_id = self.table.get_object_id(datum)
|
||||||
|
self.link_attrs['ng-click'] = (
|
||||||
|
"modal.openMetadataModal('flavor', '%s', true)" % obj_id)
|
||||||
|
return "javascript:void(0);"
|
||||||
|
|
||||||
|
|
||||||
class ModifyAccess(tables.LinkAction):
|
class ModifyAccess(tables.LinkAction):
|
||||||
@ -138,12 +156,13 @@ class FlavorsTable(tables.DataTable):
|
|||||||
verbose_name=_("Public"),
|
verbose_name=_("Public"),
|
||||||
empty_value=False,
|
empty_value=False,
|
||||||
filters=(filters.yesno, filters.capfirst))
|
filters=(filters.yesno, filters.capfirst))
|
||||||
extra_specs = tables.Column(get_extra_specs,
|
extra_specs = UpdateMetadataColumn(
|
||||||
|
get_extra_specs,
|
||||||
verbose_name=_("Metadata"),
|
verbose_name=_("Metadata"),
|
||||||
link="horizon:admin:flavors:update_metadata",
|
link=True,
|
||||||
link_classes=("ajax-modal",),
|
|
||||||
empty_value=False,
|
empty_value=False,
|
||||||
filters=(filters.yesno, filters.capfirst))
|
filters=(filters.yesno, filters.capfirst),
|
||||||
|
link_attrs={'ng-controller': 'MetadataModalHelperController as modal'})
|
||||||
|
|
||||||
class Meta(object):
|
class Meta(object):
|
||||||
name = "flavors"
|
name = "flavors"
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
{% extends 'horizon/common/_modal_form_update_metadata.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load url from future %}
|
|
||||||
{% block title %}{% trans "Update Flavor Metadata" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block page_header %}
|
|
||||||
{% include "horizon/common/_page_header.html" with title=_("Update Flavor Metadata") %}
|
|
||||||
{% endblock page_header %}
|
|
||||||
|
|
||||||
{% block form_action %}{% url 'horizon:admin:flavors:update_metadata' id %}{% endblock %}
|
|
||||||
{% block modal-header %}{% trans "Update Flavor Metadata" %}{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Update Flavor Metadata" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include 'admin/flavors/_update_metadata.html' %}
|
|
||||||
{% endblock %}
|
|
@ -10,8 +10,6 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django import http
|
from django import http
|
||||||
from mox3.mox import IsA # noqa
|
from mox3.mox import IsA # noqa
|
||||||
@ -764,117 +762,3 @@ class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests):
|
|||||||
data = {'eph_gb': -1}
|
data = {'eph_gb': -1}
|
||||||
self.generic_update_flavor_invalid_data_form_fails(override_data=data,
|
self.generic_update_flavor_invalid_data_form_fails(override_data=data,
|
||||||
error_msg=error)
|
error_msg=error)
|
||||||
|
|
||||||
|
|
||||||
class FlavorUpdateMetadataViewTest(test.BaseAdminViewTests):
|
|
||||||
@test.create_stubs({api.nova: ('flavor_get_extras',),
|
|
||||||
api.glance: ('metadefs_namespace_list',
|
|
||||||
'metadefs_namespace_get')})
|
|
||||||
def test_flavor_metadata_get(self):
|
|
||||||
# <Flavor: m1.metadata>
|
|
||||||
flavor = self.flavors.list()[3]
|
|
||||||
|
|
||||||
namespaces = self.metadata_defs.list()
|
|
||||||
|
|
||||||
api.nova.flavor_get_extras(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
flavor.id
|
|
||||||
).AndReturn([flavor.extra_specs])
|
|
||||||
api.glance.metadefs_namespace_list(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
filters={
|
|
||||||
'resource_types': ['OS::Nova::Flavor']
|
|
||||||
}
|
|
||||||
).AndReturn((namespaces, False, False))
|
|
||||||
|
|
||||||
for namespace in namespaces:
|
|
||||||
api.glance.metadefs_namespace_get(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
namespace.namespace,
|
|
||||||
'OS::Nova::Flavor'
|
|
||||||
).AndReturn(namespace)
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
res = self.client.get(
|
|
||||||
reverse(
|
|
||||||
constants.FLAVORS_UPDATE_METADATA_URL,
|
|
||||||
kwargs={'id': flavor.id}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(res.status_code, 200)
|
|
||||||
self.assertTemplateUsed(
|
|
||||||
res,
|
|
||||||
constants.FLAVORS_UPDATE_METADATA_TEMPLATE
|
|
||||||
)
|
|
||||||
self.assertTemplateUsed(
|
|
||||||
res,
|
|
||||||
constants.FLAVORS_UPDATE_METADATA_SUBTEMPLATE
|
|
||||||
)
|
|
||||||
self.assertContains(res, 'namespace_1')
|
|
||||||
self.assertContains(res, 'namespace_2')
|
|
||||||
self.assertContains(res, 'namespace_3')
|
|
||||||
self.assertContains(res, 'namespace_4')
|
|
||||||
|
|
||||||
@test.create_stubs({api.nova: ('flavor_get_extras',
|
|
||||||
'flavor_extra_set',
|
|
||||||
'flavor_extra_delete')})
|
|
||||||
def test_flavor_metadata_update(self):
|
|
||||||
# <Flavor: m1.metadata>
|
|
||||||
flavor = self.flavors.list()[3]
|
|
||||||
|
|
||||||
api.nova.flavor_get_extras(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
flavor.id
|
|
||||||
).AndReturn([flavor.extra_specs])
|
|
||||||
api.nova.flavor_extra_set(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
flavor.id,
|
|
||||||
{'key_mock': 'value_mock'}
|
|
||||||
).AndReturn(None)
|
|
||||||
api.nova.flavor_extra_delete(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
flavor.id,
|
|
||||||
[]
|
|
||||||
).AndReturn(None)
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
metadata = [{'value': 'value_mock', 'key': 'key_mock'}]
|
|
||||||
formData = {'metadata': json.dumps(metadata)}
|
|
||||||
|
|
||||||
res = self.client.post(
|
|
||||||
reverse(
|
|
||||||
constants.FLAVORS_UPDATE_METADATA_URL,
|
|
||||||
kwargs={'id': flavor.id}
|
|
||||||
),
|
|
||||||
formData
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(res.status_code, 302)
|
|
||||||
self.assertNoFormErrors(res)
|
|
||||||
self.assertRedirectsNoFollow(res, reverse(constants.FLAVORS_INDEX_URL))
|
|
||||||
self.assertMessageCount(success=1)
|
|
||||||
|
|
||||||
@test.create_stubs({api.nova: ('flavor_get_extras',)})
|
|
||||||
def test_flavor_metadata_get_get_extras_fails(self):
|
|
||||||
# <Flavor: m1.metadata>
|
|
||||||
flavor = self.flavors.list()[3]
|
|
||||||
|
|
||||||
api.nova.flavor_get_extras(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
flavor.id
|
|
||||||
).AndRaise(self.exceptions.nova)
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
res = self.client.get(
|
|
||||||
reverse(
|
|
||||||
constants.FLAVORS_UPDATE_METADATA_URL,
|
|
||||||
kwargs={'id': flavor.id}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(res.status_code, 302)
|
|
||||||
self.assertRedirectsNoFollow(res, reverse(constants.FLAVORS_INDEX_URL))
|
|
||||||
self.assertMessageCount(error=1)
|
|
||||||
|
@ -26,7 +26,5 @@ urlpatterns = patterns(
|
|||||||
'openstack_dashboard.dashboards.admin.flavors.views',
|
'openstack_dashboard.dashboards.admin.flavors.views',
|
||||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||||
url(r'^create/$', views.CreateView.as_view(), name='create'),
|
url(r'^create/$', views.CreateView.as_view(), name='create'),
|
||||||
url(r'^(?P<id>[^/]+)/update_metadata/$',
|
|
||||||
views.UpdateMetadataView.as_view(), name='update_metadata'),
|
|
||||||
url(r'^(?P<id>[^/]+)/update/$', views.UpdateView.as_view(), name='update'),
|
url(r'^(?P<id>[^/]+)/update/$', views.UpdateView.as_view(), name='update'),
|
||||||
)
|
)
|
||||||
|
@ -16,22 +16,15 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.core.urlresolvers import reverse_lazy
|
from django.core.urlresolvers import reverse_lazy
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from horizon import exceptions
|
from horizon import exceptions
|
||||||
from horizon import forms
|
|
||||||
from horizon import tables
|
from horizon import tables
|
||||||
from horizon.utils import memoized
|
|
||||||
from horizon import workflows
|
from horizon import workflows
|
||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.admin.flavors \
|
|
||||||
import forms as project_forms
|
|
||||||
from openstack_dashboard.dashboards.admin.flavors \
|
from openstack_dashboard.dashboards.admin.flavors \
|
||||||
import tables as project_tables
|
import tables as project_tables
|
||||||
from openstack_dashboard.dashboards.admin.flavors \
|
from openstack_dashboard.dashboards.admin.flavors \
|
||||||
@ -88,54 +81,3 @@ class UpdateView(workflows.WorkflowView):
|
|||||||
'disk_gb': flavor.disk,
|
'disk_gb': flavor.disk,
|
||||||
'swap_mb': flavor.swap or 0,
|
'swap_mb': flavor.swap or 0,
|
||||||
'eph_gb': getattr(flavor, 'OS-FLV-EXT-DATA:ephemeral', None)}
|
'eph_gb': getattr(flavor, 'OS-FLV-EXT-DATA:ephemeral', None)}
|
||||||
|
|
||||||
|
|
||||||
class UpdateMetadataView(forms.ModalFormView):
|
|
||||||
template_name = "admin/flavors/update_metadata.html"
|
|
||||||
form_class = project_forms.UpdateMetadataForm
|
|
||||||
success_url = reverse_lazy('horizon:admin:flavors:index')
|
|
||||||
page_title = _("Update Flavor Metadata")
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
extra_specs_dict = self.get_object()
|
|
||||||
return {'id': self.kwargs["id"], 'metadata': extra_specs_dict}
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(UpdateMetadataView, self).get_context_data(**kwargs)
|
|
||||||
|
|
||||||
extra_specs_dict = self.get_object()
|
|
||||||
|
|
||||||
context['existing_metadata'] = json.dumps(extra_specs_dict)
|
|
||||||
|
|
||||||
resource_type = 'OS::Nova::Flavor'
|
|
||||||
|
|
||||||
namespaces = []
|
|
||||||
try:
|
|
||||||
# metadefs_namespace_list() returns a tuple with list as 1st elem
|
|
||||||
namespaces = [
|
|
||||||
api.glance.metadefs_namespace_get(self.request, x.namespace,
|
|
||||||
resource_type)
|
|
||||||
for x in api.glance.metadefs_namespace_list(
|
|
||||||
self.request,
|
|
||||||
filters={'resource_types': [resource_type]}
|
|
||||||
)[0]
|
|
||||||
]
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
msg = _('Unable to retrieve available metadata for flavors.')
|
|
||||||
exceptions.handle(self.request, msg)
|
|
||||||
|
|
||||||
context['available_metadata'] = json.dumps({'namespaces': namespaces})
|
|
||||||
context['id'] = self.kwargs['id']
|
|
||||||
return context
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def get_object(self):
|
|
||||||
flavor_id = self.kwargs['id']
|
|
||||||
try:
|
|
||||||
extra_specs = api.nova.flavor_get_extras(self.request, flavor_id)
|
|
||||||
return dict((i.key, i.value) for i in extra_specs)
|
|
||||||
except Exception:
|
|
||||||
msg = _('Unable to retrieve the flavor metadata.')
|
|
||||||
exceptions.handle(self.request, msg,
|
|
||||||
redirect=reverse(INDEX_URL))
|
|
||||||
|
@ -16,16 +16,6 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon import forms
|
|
||||||
from horizon import messages
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
|
||||||
from openstack_dashboard.dashboards.project.images.images \
|
from openstack_dashboard.dashboards.project.images.images \
|
||||||
import forms as images_forms
|
import forms as images_forms
|
||||||
|
|
||||||
@ -36,32 +26,3 @@ class AdminCreateImageForm(images_forms.CreateImageForm):
|
|||||||
|
|
||||||
class AdminUpdateImageForm(images_forms.UpdateImageForm):
|
class AdminUpdateImageForm(images_forms.UpdateImageForm):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UpdateMetadataForm(forms.SelfHandlingForm):
|
|
||||||
|
|
||||||
def handle(self, request, data):
|
|
||||||
id = self.initial['id']
|
|
||||||
old_metadata = self.initial['metadata']
|
|
||||||
|
|
||||||
try:
|
|
||||||
new_metadata = json.loads(self.data['metadata'])
|
|
||||||
|
|
||||||
metadata = dict(
|
|
||||||
(item['key'], str(item['value']))
|
|
||||||
for item in new_metadata
|
|
||||||
)
|
|
||||||
|
|
||||||
remove_props = [key for key in old_metadata if key not in metadata]
|
|
||||||
|
|
||||||
api.glance.image_update_properties(request,
|
|
||||||
id,
|
|
||||||
remove_props,
|
|
||||||
**metadata)
|
|
||||||
message = _('Metadata successfully updated.')
|
|
||||||
messages.success(request, message)
|
|
||||||
except Exception:
|
|
||||||
exceptions.handle(request,
|
|
||||||
_('Unable to update the image metadata.'))
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
@ -41,11 +41,21 @@ class AdminEditImage(project_tables.EditImage):
|
|||||||
|
|
||||||
|
|
||||||
class UpdateMetadata(tables.LinkAction):
|
class UpdateMetadata(tables.LinkAction):
|
||||||
url = "horizon:admin:images:update_metadata"
|
|
||||||
name = "update_metadata"
|
name = "update_metadata"
|
||||||
verbose_name = _("Update Metadata")
|
verbose_name = _("Update Metadata")
|
||||||
classes = ("ajax-modal",)
|
ajax = False
|
||||||
icon = "pencil"
|
icon = "pencil"
|
||||||
|
attrs = {"ng-controller": "MetadataModalHelperController as modal"}
|
||||||
|
|
||||||
|
def __init__(self, attrs=None, **kwargs):
|
||||||
|
kwargs['preempt'] = True
|
||||||
|
super(UpdateMetadata, self).__init__(attrs, **kwargs)
|
||||||
|
|
||||||
|
def get_link_url(self, datum):
|
||||||
|
image_id = self.table.get_object_id(datum)
|
||||||
|
self.attrs['ng-click'] = (
|
||||||
|
"modal.openMetadataModal('image', '%s', true)" % image_id)
|
||||||
|
return "javascript:void(0);"
|
||||||
|
|
||||||
|
|
||||||
class UpdateRow(tables.Row):
|
class UpdateRow(tables.Row):
|
||||||
|
@ -1 +0,0 @@
|
|||||||
{% extends 'horizon/common/_modal_form_update_metadata.html' %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Update Image Metadata" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include 'admin/images/_update_metadata.html' %}
|
|
||||||
{% endblock %}
|
|
@ -12,8 +12,6 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django import http
|
from django import http
|
||||||
@ -26,10 +24,6 @@ from openstack_dashboard.test import helpers as test
|
|||||||
|
|
||||||
from openstack_dashboard.dashboards.admin.images import tables
|
from openstack_dashboard.dashboards.admin.images import tables
|
||||||
|
|
||||||
IMAGE_METADATA_URL = reverse('horizon:admin:images:update_metadata',
|
|
||||||
kwargs={
|
|
||||||
"id": "007e7d55-fe1e-4c5c-bf08-44b4a4964822"})
|
|
||||||
|
|
||||||
|
|
||||||
class ImageCreateViewTest(test.BaseAdminViewTests):
|
class ImageCreateViewTest(test.BaseAdminViewTests):
|
||||||
@test.create_stubs({api.glance: ('image_list_detailed',)})
|
@test.create_stubs({api.glance: ('image_list_detailed',)})
|
||||||
@ -138,64 +132,6 @@ class ImagesViewTest(test.BaseAdminViewTests):
|
|||||||
1)
|
1)
|
||||||
self.assertContains(res, 'test_tenant', 2, 200)
|
self.assertContains(res, 'test_tenant', 2, 200)
|
||||||
|
|
||||||
@test.create_stubs({api.glance: ('image_get',
|
|
||||||
'metadefs_namespace_list',
|
|
||||||
'metadefs_namespace_get')})
|
|
||||||
def test_images_metadata_get(self):
|
|
||||||
image = self.images.first()
|
|
||||||
|
|
||||||
api.glance.image_get(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
image.id
|
|
||||||
).AndReturn(image)
|
|
||||||
|
|
||||||
namespaces = self.metadata_defs.list()
|
|
||||||
|
|
||||||
api.glance.metadefs_namespace_list(IsA(http.HttpRequest), filters={
|
|
||||||
'resource_types': ['OS::Glance::Image']}).AndReturn(
|
|
||||||
(namespaces, False, False))
|
|
||||||
|
|
||||||
for namespace in namespaces:
|
|
||||||
api.glance.metadefs_namespace_get(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
namespace.namespace,
|
|
||||||
'OS::Glance::Image'
|
|
||||||
).AndReturn(namespace)
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
res = self.client.get(IMAGE_METADATA_URL)
|
|
||||||
|
|
||||||
self.assertTemplateUsed(res, 'admin/images/update_metadata.html')
|
|
||||||
self.assertContains(res, 'namespace_1')
|
|
||||||
self.assertContains(res, 'namespace_2')
|
|
||||||
self.assertContains(res, 'namespace_3')
|
|
||||||
self.assertContains(res, 'namespace_4')
|
|
||||||
|
|
||||||
@test.create_stubs({api.glance: ('image_get', 'image_update_properties')})
|
|
||||||
def test_images_metadata_update(self):
|
|
||||||
image = self.images.first()
|
|
||||||
|
|
||||||
api.glance.image_get(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
image.id
|
|
||||||
).AndReturn(image)
|
|
||||||
api.glance.image_update_properties(
|
|
||||||
IsA(http.HttpRequest), image.id, ['image_type'],
|
|
||||||
hw_machine_type='mock_value').AndReturn(None)
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
metadata = [{"value": "mock_value", "key": "hw_machine_type"}]
|
|
||||||
formData = {"metadata": json.dumps(metadata)}
|
|
||||||
|
|
||||||
res = self.client.post(IMAGE_METADATA_URL, formData)
|
|
||||||
|
|
||||||
self.assertNoFormErrors(res)
|
|
||||||
self.assertMessageCount(success=1)
|
|
||||||
self.assertRedirectsNoFollow(
|
|
||||||
res, reverse('horizon:admin:images:index')
|
|
||||||
)
|
|
||||||
|
|
||||||
@override_settings(API_RESULT_PAGE_SIZE=2)
|
@override_settings(API_RESULT_PAGE_SIZE=2)
|
||||||
@test.create_stubs({api.glance: ('image_list_detailed',),
|
@test.create_stubs({api.glance: ('image_list_detailed',),
|
||||||
api.keystone: ('tenant_list',)})
|
api.keystone: ('tenant_list',)})
|
||||||
|
@ -28,8 +28,6 @@ urlpatterns = patterns(
|
|||||||
url(r'^create/$', views.CreateView.as_view(), name='create'),
|
url(r'^create/$', views.CreateView.as_view(), name='create'),
|
||||||
url(r'^(?P<image_id>[^/]+)/update/$',
|
url(r'^(?P<image_id>[^/]+)/update/$',
|
||||||
views.UpdateView.as_view(), name='update'),
|
views.UpdateView.as_view(), name='update'),
|
||||||
url(r'^(?P<id>[^/]+)/update_metadata/$',
|
|
||||||
views.UpdateMetadataView.as_view(), name='update_metadata'),
|
|
||||||
url(r'^(?P<image_id>[^/]+)/detail/$',
|
url(r'^(?P<image_id>[^/]+)/detail/$',
|
||||||
views.DetailView.as_view(), name='detail')
|
views.DetailView.as_view(), name='detail')
|
||||||
)
|
)
|
||||||
|
@ -16,21 +16,16 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
import six
|
|
||||||
|
|
||||||
from django import conf
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.core.urlresolvers import reverse_lazy
|
from django.core.urlresolvers import reverse_lazy
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from horizon import exceptions
|
from horizon import exceptions
|
||||||
from horizon import forms
|
|
||||||
from horizon import tables
|
from horizon import tables
|
||||||
from horizon.utils import memoized
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
from openstack_dashboard.dashboards.project.images.images import views
|
from openstack_dashboard.dashboards.project.images.images import views
|
||||||
@ -149,81 +144,3 @@ class DetailView(views.DetailView):
|
|||||||
context["url"] = reverse('horizon:admin:images:index')
|
context["url"] = reverse('horizon:admin:images:index')
|
||||||
context["actions"] = table.render_row_actions(context["image"])
|
context["actions"] = table.render_row_actions(context["image"])
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class UpdateMetadataView(forms.ModalFormView):
|
|
||||||
template_name = "admin/images/update_metadata.html"
|
|
||||||
modal_header = _("Update Image")
|
|
||||||
form_id = "update_image_form"
|
|
||||||
form_class = project_forms.UpdateMetadataForm
|
|
||||||
submit_url = "horizon:admin:images:update_metadata"
|
|
||||||
success_url = reverse_lazy('horizon:admin:images:index')
|
|
||||||
page_title = _("Update Image Metadata")
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
image = self.get_object()
|
|
||||||
return {'id': self.kwargs["id"], 'metadata': image.properties}
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(UpdateMetadataView, self).get_context_data(**kwargs)
|
|
||||||
|
|
||||||
image = self.get_object()
|
|
||||||
reserved_props = getattr(conf.settings,
|
|
||||||
'IMAGE_RESERVED_CUSTOM_PROPERTIES', [])
|
|
||||||
image.properties = dict((k, v)
|
|
||||||
for (k, v) in six.iteritems(image.properties)
|
|
||||||
if k not in reserved_props)
|
|
||||||
context['existing_metadata'] = json.dumps(image.properties)
|
|
||||||
args = (self.kwargs['id'],)
|
|
||||||
context['submit_url'] = reverse(self.submit_url, args=args)
|
|
||||||
|
|
||||||
resource_type = 'OS::Glance::Image'
|
|
||||||
namespaces = []
|
|
||||||
try:
|
|
||||||
# metadefs_namespace_list() returns a tuple with list as 1st elem
|
|
||||||
available_namespaces = [x.namespace for x in
|
|
||||||
api.glance.metadefs_namespace_list(
|
|
||||||
self.request,
|
|
||||||
filters={"resource_types":
|
|
||||||
[resource_type]}
|
|
||||||
)[0]]
|
|
||||||
for namespace in available_namespaces:
|
|
||||||
details = api.glance.metadefs_namespace_get(self.request,
|
|
||||||
namespace,
|
|
||||||
resource_type)
|
|
||||||
# Filter out reserved custom properties from namespace
|
|
||||||
if reserved_props:
|
|
||||||
if hasattr(details, 'properties'):
|
|
||||||
details.properties = dict(
|
|
||||||
(k, v)
|
|
||||||
for (k, v) in six.iteritems(details.properties)
|
|
||||||
if k not in reserved_props
|
|
||||||
)
|
|
||||||
|
|
||||||
if hasattr(details, 'objects'):
|
|
||||||
for obj in details.objects:
|
|
||||||
obj['properties'] = dict(
|
|
||||||
(k, v)
|
|
||||||
for (k, v) in six.iteritems(obj['properties'])
|
|
||||||
if k not in reserved_props
|
|
||||||
)
|
|
||||||
|
|
||||||
namespaces.append(details)
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
msg = _('Unable to retrieve available properties for image.')
|
|
||||||
exceptions.handle(self.request, msg)
|
|
||||||
|
|
||||||
context['available_metadata'] = json.dumps({'namespaces': namespaces})
|
|
||||||
context['id'] = self.kwargs['id']
|
|
||||||
return context
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def get_object(self):
|
|
||||||
image_id = self.kwargs['id']
|
|
||||||
try:
|
|
||||||
return api.glance.image_get(self.request, image_id)
|
|
||||||
except Exception:
|
|
||||||
msg = _('Unable to retrieve the image to be updated.')
|
|
||||||
exceptions.handle(self.request, msg,
|
|
||||||
redirect=reverse('horizon:admin:images:index'))
|
|
||||||
|
Loading…
Reference in New Issue
Block a user