Merge "Removed inline_edit functionality for tables"
This commit is contained in:
commit
de29bbce85
@ -90,7 +90,7 @@ Actions
|
|||||||
.. autoclass:: DeleteAction
|
.. autoclass:: DeleteAction
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. autoclass:: UpdateAction
|
.. autoclass:: UpdateAction **DEPRECATED**
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
Class-Based Views
|
Class-Based Views
|
||||||
|
@ -288,8 +288,8 @@ Example::
|
|||||||
admin=True)
|
admin=True)
|
||||||
return project_info
|
return project_info
|
||||||
|
|
||||||
Updating changed cell data
|
Updating changed cell data (DEPRECATED)
|
||||||
--------------------------
|
---------------------------------------
|
||||||
|
|
||||||
Define an ``update_cell`` method in the class inherited from
|
Define an ``update_cell`` method in the class inherited from
|
||||||
``tables.UpdateAction``. This method takes care of saving the data of the
|
``tables.UpdateAction``. This method takes care of saving the data of the
|
||||||
|
@ -287,7 +287,7 @@ There are also additional actions which are extensions of the basic Action class
|
|||||||
|
|
||||||
- :class:`~horizon.tables.BatchAction`
|
- :class:`~horizon.tables.BatchAction`
|
||||||
- :class:`~horizon.tables.DeleteAction`
|
- :class:`~horizon.tables.DeleteAction`
|
||||||
- :class:`~horizon.tables.UpdateAction`
|
- :class:`~horizon.tables.UpdateAction` **DEPRECATED**
|
||||||
- :class:`~horizon.tables.FixedFilterAction`
|
- :class:`~horizon.tables.FixedFilterAction`
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//TODO(lcastell):Inline edit is deprecated and will be removed in Horizon 12.0
|
||||||
horizon.inline_edit = {
|
horizon.inline_edit = {
|
||||||
get_cell_id: function (td_element) {
|
get_cell_id: function (td_element) {
|
||||||
return [
|
return [
|
||||||
|
@ -108,4 +108,5 @@ horizon.addInitFunction(horizon.tabs.init = function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//TODO(lcastell):Inline edit is deprecated and will be removed in Horizon 12.0
|
||||||
horizon.tabs.addTabLoadFunction(horizon.inline_edit.init);
|
horizon.tabs.addTabLoadFunction(horizon.inline_edit.init);
|
||||||
|
@ -961,8 +961,26 @@ class DeleteAction(BatchAction):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Deprecated(type):
|
||||||
|
# TODO(lcastell) Replace class with similar functionality from
|
||||||
|
# oslo_log.versionutils when it's finally added in 11.0
|
||||||
|
def __new__(meta, name, bases, kwargs):
|
||||||
|
cls = super(Deprecated, meta).__new__(meta, name, bases, kwargs)
|
||||||
|
message = ("WARNING:The UpdateAction class defined in module '%s'"
|
||||||
|
" is deprecated as of Newton and may be removed in "
|
||||||
|
"Horizon P (12.0). Class '%s' defined at module '%s' "
|
||||||
|
"shall no longer subclass it.")
|
||||||
|
if name != 'UpdateAction':
|
||||||
|
LOG.warning(message % (UpdateAction.__module__,
|
||||||
|
name,
|
||||||
|
kwargs['__module__']))
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(Deprecated)
|
||||||
class UpdateAction(object):
|
class UpdateAction(object):
|
||||||
"""A table action for cell updates by inline editing."""
|
"""A table action for cell updates by inline editing."""
|
||||||
|
|
||||||
name = "update"
|
name = "update"
|
||||||
|
|
||||||
def action(self, request, datum, obj_id, cell_name, new_cell_value):
|
def action(self, request, datum, obj_id, cell_name, new_cell_value):
|
||||||
|
@ -16,7 +16,6 @@ from django.template import defaultfilters as filters
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.translation import ungettext_lazy
|
from django.utils.translation import ungettext_lazy
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon import forms
|
from horizon import forms
|
||||||
from horizon import tables
|
from horizon import tables
|
||||||
|
|
||||||
@ -95,33 +94,6 @@ class UpdateRow(tables.Row):
|
|||||||
wrap=True)
|
wrap=True)
|
||||||
|
|
||||||
|
|
||||||
class UpdateCell(tables.UpdateAction):
|
|
||||||
policy_rules = (("image", "modify_metadef_namespace"),)
|
|
||||||
|
|
||||||
def update_cell(self, request, datum, namespace_name,
|
|
||||||
cell_name, new_cell_value):
|
|
||||||
# inline update namespace info
|
|
||||||
try:
|
|
||||||
namespace_obj = datum
|
|
||||||
# updating changed value by new value
|
|
||||||
if cell_name == 'public':
|
|
||||||
cell_name = 'visibility'
|
|
||||||
if new_cell_value:
|
|
||||||
new_cell_value = 'public'
|
|
||||||
else:
|
|
||||||
new_cell_value = 'private'
|
|
||||||
setattr(namespace_obj, cell_name, new_cell_value)
|
|
||||||
properties = {cell_name: new_cell_value}
|
|
||||||
glance.metadefs_namespace_update(
|
|
||||||
request,
|
|
||||||
namespace_name,
|
|
||||||
**properties)
|
|
||||||
except Exception:
|
|
||||||
exceptions.handle(request, ignore=True)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class AdminNamespacesTable(tables.DataTable):
|
class AdminNamespacesTable(tables.DataTable):
|
||||||
display_name = tables.Column(
|
display_name = tables.Column(
|
||||||
"display_name",
|
"display_name",
|
||||||
@ -143,15 +115,13 @@ class AdminNamespacesTable(tables.DataTable):
|
|||||||
verbose_name=_("Public"),
|
verbose_name=_("Public"),
|
||||||
empty_value=False,
|
empty_value=False,
|
||||||
form_field=forms.BooleanField(required=False),
|
form_field=forms.BooleanField(required=False),
|
||||||
filters=(filters.yesno, filters.capfirst),
|
filters=(filters.yesno, filters.capfirst))
|
||||||
update_action=UpdateCell)
|
|
||||||
protected = tables.Column(
|
protected = tables.Column(
|
||||||
"protected",
|
"protected",
|
||||||
verbose_name=_("Protected"),
|
verbose_name=_("Protected"),
|
||||||
empty_value=False,
|
empty_value=False,
|
||||||
form_field=forms.BooleanField(required=False),
|
form_field=forms.BooleanField(required=False),
|
||||||
filters=(filters.yesno, filters.capfirst),
|
filters=(filters.yesno, filters.capfirst))
|
||||||
update_action=UpdateCell)
|
|
||||||
|
|
||||||
def get_object_id(self, datum):
|
def get_object_id(self, datum):
|
||||||
return datum.namespace
|
return datum.namespace
|
||||||
|
@ -20,7 +20,6 @@ from horizon import forms
|
|||||||
from horizon import tables
|
from horizon import tables
|
||||||
|
|
||||||
from openstack_dashboard.api import cinder
|
from openstack_dashboard.api import cinder
|
||||||
from openstack_dashboard import policy
|
|
||||||
|
|
||||||
|
|
||||||
class CreateVolumeType(tables.LinkAction):
|
class CreateVolumeType(tables.LinkAction):
|
||||||
@ -196,51 +195,14 @@ class UpdateRow(tables.Row):
|
|||||||
return volume_type
|
return volume_type
|
||||||
|
|
||||||
|
|
||||||
class UpdateCell(tables.UpdateAction):
|
|
||||||
def allowed(self, request, volume_type, cell):
|
|
||||||
return policy.check(
|
|
||||||
("volume_extension", "volume_extension:types_manage"), request)
|
|
||||||
|
|
||||||
def update_cell(self, request, data, volume_type_id,
|
|
||||||
cell_name, new_cell_value):
|
|
||||||
# inline update volume type name and/or description
|
|
||||||
try:
|
|
||||||
vol_type_obj = data
|
|
||||||
# updating changed value by new value
|
|
||||||
setattr(vol_type_obj, cell_name, new_cell_value)
|
|
||||||
name_value = getattr(vol_type_obj, 'name', None)
|
|
||||||
desc_value = getattr(vol_type_obj, 'description', None)
|
|
||||||
public_value = getattr(vol_type_obj, 'public', None)
|
|
||||||
|
|
||||||
cinder.volume_type_update(
|
|
||||||
request,
|
|
||||||
volume_type_id,
|
|
||||||
name=name_value,
|
|
||||||
description=desc_value,
|
|
||||||
is_public=public_value)
|
|
||||||
except Exception as ex:
|
|
||||||
if ex.code and ex.code == 409:
|
|
||||||
error_message = _('New name conflicts with another '
|
|
||||||
'volume type.')
|
|
||||||
else:
|
|
||||||
error_message = _('Unable to update the volume type.')
|
|
||||||
exceptions.handle(request, error_message)
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class VolumeTypesTable(tables.DataTable):
|
class VolumeTypesTable(tables.DataTable):
|
||||||
name = tables.Column("name", verbose_name=_("Name"),
|
name = tables.Column("name", verbose_name=_("Name"),
|
||||||
form_field=forms.CharField(
|
form_field=forms.CharField(max_length=64))
|
||||||
max_length=64),
|
|
||||||
update_action=UpdateCell)
|
|
||||||
description = tables.Column(lambda obj: getattr(obj, 'description', None),
|
description = tables.Column(lambda obj: getattr(obj, 'description', None),
|
||||||
verbose_name=_('Description'),
|
verbose_name=_('Description'),
|
||||||
form_field=forms.CharField(
|
form_field=forms.CharField(
|
||||||
widget=forms.Textarea(attrs={'rows': 4}),
|
widget=forms.Textarea(attrs={'rows': 4}),
|
||||||
required=False),
|
required=False))
|
||||||
update_action=UpdateCell)
|
|
||||||
|
|
||||||
assoc_qos_spec = tables.Column("associated_qos_spec",
|
assoc_qos_spec = tables.Column("associated_qos_spec",
|
||||||
verbose_name=_("Associated QoS Spec"))
|
verbose_name=_("Associated QoS Spec"))
|
||||||
@ -250,7 +212,6 @@ class VolumeTypesTable(tables.DataTable):
|
|||||||
public = tables.Column("is_public",
|
public = tables.Column("is_public",
|
||||||
verbose_name=_("Public"),
|
verbose_name=_("Public"),
|
||||||
filters=(filters.yesno, filters.capfirst),
|
filters=(filters.yesno, filters.capfirst),
|
||||||
update_action=UpdateCell,
|
|
||||||
form_field=forms.BooleanField(
|
form_field=forms.BooleanField(
|
||||||
label=_('Public'), required=False))
|
label=_('Public'), required=False))
|
||||||
|
|
||||||
|
@ -10,17 +10,14 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError # noqa
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.template import defaultfilters as filters
|
from django.template import defaultfilters as filters
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.translation import ungettext_lazy
|
from django.utils.translation import ungettext_lazy
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon import forms
|
from horizon import forms
|
||||||
from horizon import tables
|
from horizon import tables
|
||||||
from keystoneclient.exceptions import Conflict # noqa
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
from openstack_dashboard import policy
|
from openstack_dashboard import policy
|
||||||
@ -223,58 +220,21 @@ class UpdateRow(tables.Row):
|
|||||||
return project_info
|
return project_info
|
||||||
|
|
||||||
|
|
||||||
class UpdateCell(tables.UpdateAction):
|
|
||||||
def allowed(self, request, project, cell):
|
|
||||||
policy_rule = (("identity", "identity:update_project"),)
|
|
||||||
return (
|
|
||||||
(cell.column.name != 'enabled' or
|
|
||||||
request.user.project_id != cell.datum.id) and
|
|
||||||
api.keystone.keystone_can_edit_project() and
|
|
||||||
policy.check(policy_rule, request))
|
|
||||||
|
|
||||||
def update_cell(self, request, datum, project_id,
|
|
||||||
cell_name, new_cell_value):
|
|
||||||
# inline update project info
|
|
||||||
try:
|
|
||||||
project_obj = datum
|
|
||||||
# updating changed value by new value
|
|
||||||
setattr(project_obj, cell_name, new_cell_value)
|
|
||||||
api.keystone.tenant_update(
|
|
||||||
request,
|
|
||||||
project_id,
|
|
||||||
name=project_obj.name,
|
|
||||||
description=project_obj.description,
|
|
||||||
enabled=project_obj.enabled)
|
|
||||||
|
|
||||||
except Conflict:
|
|
||||||
# Returning a nice error message about name conflict. The message
|
|
||||||
# from exception is not that clear for the users.
|
|
||||||
message = _("This name is already taken.")
|
|
||||||
raise ValidationError(message)
|
|
||||||
except Exception:
|
|
||||||
exceptions.handle(request, ignore=True)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class TenantsTable(tables.DataTable):
|
class TenantsTable(tables.DataTable):
|
||||||
name = tables.Column('name', verbose_name=_('Name'),
|
name = tables.Column('name', verbose_name=_('Name'),
|
||||||
link=("horizon:identity:projects:detail"),
|
link=("horizon:identity:projects:detail"),
|
||||||
form_field=forms.CharField(max_length=64),
|
form_field=forms.CharField(max_length=64))
|
||||||
update_action=UpdateCell)
|
|
||||||
description = tables.Column(lambda obj: getattr(obj, 'description', None),
|
description = tables.Column(lambda obj: getattr(obj, 'description', None),
|
||||||
verbose_name=_('Description'),
|
verbose_name=_('Description'),
|
||||||
form_field=forms.CharField(
|
form_field=forms.CharField(
|
||||||
widget=forms.Textarea(attrs={'rows': 4}),
|
widget=forms.Textarea(attrs={'rows': 4}),
|
||||||
required=False),
|
required=False))
|
||||||
update_action=UpdateCell)
|
|
||||||
id = tables.Column('id', verbose_name=_('Project ID'))
|
id = tables.Column('id', verbose_name=_('Project ID'))
|
||||||
enabled = tables.Column('enabled', verbose_name=_('Enabled'), status=True,
|
enabled = tables.Column('enabled', verbose_name=_('Enabled'), status=True,
|
||||||
filters=(filters.yesno, filters.capfirst),
|
filters=(filters.yesno, filters.capfirst),
|
||||||
form_field=forms.BooleanField(
|
form_field=forms.BooleanField(
|
||||||
label=_('Enabled'),
|
label=_('Enabled'),
|
||||||
required=False),
|
required=False))
|
||||||
update_action=UpdateCell)
|
|
||||||
|
|
||||||
if api.keystone.VERSIONS.active >= 3:
|
if api.keystone.VERSIONS.active >= 3:
|
||||||
domain_name = tables.Column(
|
domain_name = tables.Column(
|
||||||
@ -284,8 +244,7 @@ class TenantsTable(tables.DataTable):
|
|||||||
filters=(filters.yesno, filters.capfirst),
|
filters=(filters.yesno, filters.capfirst),
|
||||||
form_field=forms.BooleanField(
|
form_field=forms.BooleanField(
|
||||||
label=_('Enabled'),
|
label=_('Enabled'),
|
||||||
required=False),
|
required=False))
|
||||||
update_action=UpdateCell)
|
|
||||||
|
|
||||||
def get_project_detail_link(self, project):
|
def get_project_detail_link(self, project):
|
||||||
# this method is an ugly monkey patch, needed because
|
# this method is an ugly monkey patch, needed because
|
||||||
|
@ -12,7 +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 copy
|
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@ -36,13 +35,6 @@ from openstack_dashboard.test import helpers as test
|
|||||||
from openstack_dashboard import usage
|
from openstack_dashboard import usage
|
||||||
from openstack_dashboard.usage import quotas
|
from openstack_dashboard.usage import quotas
|
||||||
|
|
||||||
with_sel = os.environ.get('WITH_SELENIUM', False)
|
|
||||||
if with_sel:
|
|
||||||
from selenium.webdriver import ActionChains # noqa
|
|
||||||
from selenium.webdriver.common import keys
|
|
||||||
|
|
||||||
from socket import timeout as socket_timeout # noqa
|
|
||||||
|
|
||||||
|
|
||||||
INDEX_URL = reverse('horizon:identity:projects:index')
|
INDEX_URL = reverse('horizon:identity:projects:index')
|
||||||
USER_ROLE_PREFIX = workflows.PROJECT_USER_MEMBER_SLUG + "_role_"
|
USER_ROLE_PREFIX = workflows.PROJECT_USER_MEMBER_SLUG + "_role_"
|
||||||
@ -1646,143 +1638,6 @@ class DetailProjectViewTests(test.BaseAdminViewTests):
|
|||||||
@unittest.skipUnless(os.environ.get('WITH_SELENIUM', False),
|
@unittest.skipUnless(os.environ.get('WITH_SELENIUM', False),
|
||||||
"The WITH_SELENIUM env variable is not set.")
|
"The WITH_SELENIUM env variable is not set.")
|
||||||
class SeleniumTests(test.SeleniumAdminTestCase):
|
class SeleniumTests(test.SeleniumAdminTestCase):
|
||||||
@test.create_stubs(
|
|
||||||
{api.keystone: ('tenant_list', 'tenant_get', 'tenant_update',
|
|
||||||
'domain_lookup')})
|
|
||||||
def test_inline_editing_update(self):
|
|
||||||
# Tenant List
|
|
||||||
api.keystone.tenant_list(IgnoreArg(),
|
|
||||||
domain=None,
|
|
||||||
marker=None,
|
|
||||||
paginate=True) \
|
|
||||||
.AndReturn([self.tenants.list(), False])
|
|
||||||
api.keystone.domain_lookup(IgnoreArg()).AndReturn({None: None})
|
|
||||||
# Edit mod
|
|
||||||
api.keystone.tenant_get(IgnoreArg(),
|
|
||||||
u'1',
|
|
||||||
admin=True) \
|
|
||||||
.AndReturn(self.tenants.list()[0])
|
|
||||||
# Update - requires get and update
|
|
||||||
api.keystone.tenant_get(IgnoreArg(),
|
|
||||||
u'1',
|
|
||||||
admin=True) \
|
|
||||||
.AndReturn(self.tenants.list()[0])
|
|
||||||
api.keystone.tenant_update(
|
|
||||||
IgnoreArg(),
|
|
||||||
u'1',
|
|
||||||
description='a test tenant.',
|
|
||||||
enabled=True,
|
|
||||||
name=u'Changed test_tenant')
|
|
||||||
# Refreshing cell with changed name
|
|
||||||
changed_tenant = copy.copy(self.tenants.list()[0])
|
|
||||||
changed_tenant.name = u'Changed test_tenant'
|
|
||||||
api.keystone.tenant_get(IgnoreArg(),
|
|
||||||
u'1',
|
|
||||||
admin=True) \
|
|
||||||
.AndReturn(changed_tenant)
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
self.selenium.get("%s%s" % (self.live_server_url, INDEX_URL))
|
|
||||||
|
|
||||||
# Check the presence of the important elements
|
|
||||||
td_element = self.selenium.find_element_by_xpath(
|
|
||||||
"//td[@data-update-url='/identity/?action=cell_update"
|
|
||||||
"&table=tenants&cell_name=name&obj_id=1']")
|
|
||||||
cell_wrapper = td_element.find_element_by_class_name(
|
|
||||||
'table_cell_wrapper')
|
|
||||||
edit_button_wrapper = td_element.find_element_by_class_name(
|
|
||||||
'table_cell_action')
|
|
||||||
edit_button = edit_button_wrapper.find_element_by_tag_name('button')
|
|
||||||
# Hovering over td and clicking on edit button
|
|
||||||
action_chains = ActionChains(self.selenium)
|
|
||||||
action_chains.move_to_element(cell_wrapper).click(edit_button)
|
|
||||||
action_chains.perform()
|
|
||||||
# Waiting for the AJAX response for switching to editing mod
|
|
||||||
wait = self.ui.WebDriverWait(self.selenium, 10,
|
|
||||||
ignored_exceptions=[socket_timeout])
|
|
||||||
wait.until(lambda x: self.selenium.find_element_by_name("name__1"))
|
|
||||||
# Changing project name in cell form
|
|
||||||
td_element = self.selenium.find_element_by_xpath(
|
|
||||||
"//td[@data-update-url='/identity/?action=cell_update"
|
|
||||||
"&table=tenants&cell_name=name&obj_id=1']")
|
|
||||||
name_input = td_element.find_element_by_tag_name('input')
|
|
||||||
name_input.send_keys(keys.Keys.HOME)
|
|
||||||
name_input.send_keys("Changed ")
|
|
||||||
# Saving new project name by AJAX
|
|
||||||
td_element.find_element_by_class_name('inline-edit-submit').click()
|
|
||||||
# Waiting for the AJAX response of cell refresh
|
|
||||||
wait = self.ui.WebDriverWait(self.selenium, 10,
|
|
||||||
ignored_exceptions=[socket_timeout])
|
|
||||||
wait.until(lambda x: self.selenium.find_element_by_xpath(
|
|
||||||
"//td[@data-update-url='/identity/?action=cell_update"
|
|
||||||
"&table=tenants&cell_name=name&obj_id=1']"
|
|
||||||
"/div[@class='table_cell_wrapper']"
|
|
||||||
"/div[@class='table_cell_data_wrapper']"))
|
|
||||||
# Checking new project name after cell refresh
|
|
||||||
data_wrapper = self.selenium.find_element_by_xpath(
|
|
||||||
"//td[@data-update-url='/identity/?action=cell_update"
|
|
||||||
"&table=tenants&cell_name=name&obj_id=1']"
|
|
||||||
"/div[@class='table_cell_wrapper']"
|
|
||||||
"/div[@class='table_cell_data_wrapper']")
|
|
||||||
self.assertTrue(data_wrapper.text == u'Changed test_tenant',
|
|
||||||
"Error: saved tenant name is expected to be "
|
|
||||||
"'Changed test_tenant'")
|
|
||||||
|
|
||||||
@test.create_stubs(
|
|
||||||
{api.keystone: ('tenant_list', 'tenant_get', 'domain_lookup')})
|
|
||||||
def test_inline_editing_cancel(self):
|
|
||||||
# Tenant List
|
|
||||||
api.keystone.tenant_list(IgnoreArg(),
|
|
||||||
domain=None,
|
|
||||||
marker=None,
|
|
||||||
paginate=True) \
|
|
||||||
.AndReturn([self.tenants.list(), False])
|
|
||||||
api.keystone.domain_lookup(IgnoreArg()).AndReturn({None: None})
|
|
||||||
# Edit mod
|
|
||||||
api.keystone.tenant_get(IgnoreArg(),
|
|
||||||
u'1',
|
|
||||||
admin=True) \
|
|
||||||
.AndReturn(self.tenants.list()[0])
|
|
||||||
# Cancel edit mod is without the request
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
self.selenium.get("%s%s" % (self.live_server_url, INDEX_URL))
|
|
||||||
|
|
||||||
# Check the presence of the important elements
|
|
||||||
td_element = self.selenium.find_element_by_xpath(
|
|
||||||
"//td[@data-update-url='/identity/?action=cell_update"
|
|
||||||
"&table=tenants&cell_name=name&obj_id=1']")
|
|
||||||
cell_wrapper = td_element.find_element_by_class_name(
|
|
||||||
'table_cell_wrapper')
|
|
||||||
edit_button_wrapper = td_element.find_element_by_class_name(
|
|
||||||
'table_cell_action')
|
|
||||||
edit_button = edit_button_wrapper.find_element_by_tag_name('button')
|
|
||||||
# Hovering over td and clicking on edit
|
|
||||||
action_chains = ActionChains(self.selenium)
|
|
||||||
action_chains.move_to_element(cell_wrapper).click(edit_button)
|
|
||||||
action_chains.perform()
|
|
||||||
# Waiting for the AJAX response for switching to editing mod
|
|
||||||
wait = self.ui.WebDriverWait(self.selenium, 10,
|
|
||||||
ignored_exceptions=[socket_timeout])
|
|
||||||
wait.until(lambda x: self.selenium.find_element_by_name("name__1"))
|
|
||||||
# Click on cancel button
|
|
||||||
td_element = self.selenium.find_element_by_xpath(
|
|
||||||
"//td[@data-update-url='/identity/?action=cell_update"
|
|
||||||
"&table=tenants&cell_name=name&obj_id=1']")
|
|
||||||
td_element.find_element_by_class_name('inline-edit-cancel').click()
|
|
||||||
# Cancel is via javascript, so it should be immediate
|
|
||||||
# Checking that tenant name is not changed
|
|
||||||
data_wrapper = self.selenium.find_element_by_xpath(
|
|
||||||
"//td[@data-update-url='/identity/?action=cell_update"
|
|
||||||
"&table=tenants&cell_name=name&obj_id=1']"
|
|
||||||
"/div[@class='table_cell_wrapper']"
|
|
||||||
"/div[@class='table_cell_data_wrapper']")
|
|
||||||
self.assertTrue(data_wrapper.text == u'test_tenant',
|
|
||||||
"Error: saved tenant name is expected to be "
|
|
||||||
"'test_tenant'")
|
|
||||||
|
|
||||||
@test.create_stubs({api.keystone: ('get_default_domain',
|
@test.create_stubs({api.keystone: ('get_default_domain',
|
||||||
'get_default_role',
|
'get_default_role',
|
||||||
'user_list',
|
'user_list',
|
||||||
|
@ -10,14 +10,11 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from django.core import exceptions as django_exceptions
|
|
||||||
from django.template import defaultfilters
|
from django.template import defaultfilters
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.translation import ungettext_lazy
|
from django.utils.translation import ungettext_lazy
|
||||||
|
|
||||||
from horizon import exceptions as horizon_exceptions
|
|
||||||
from horizon import forms
|
from horizon import forms
|
||||||
from horizon import messages
|
|
||||||
from horizon import tables
|
from horizon import tables
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
from openstack_dashboard import policy
|
from openstack_dashboard import policy
|
||||||
@ -176,47 +173,6 @@ class UpdateRow(tables.Row):
|
|||||||
return user_info
|
return user_info
|
||||||
|
|
||||||
|
|
||||||
class UpdateCell(tables.UpdateAction):
|
|
||||||
def allowed(self, request, user, cell):
|
|
||||||
return api.keystone.keystone_can_edit_user() and \
|
|
||||||
policy.check((("identity", "identity:update_user"),),
|
|
||||||
request)
|
|
||||||
|
|
||||||
def update_cell(self, request, datum, user_id,
|
|
||||||
cell_name, new_cell_value):
|
|
||||||
try:
|
|
||||||
user_obj = datum
|
|
||||||
setattr(user_obj, cell_name, new_cell_value)
|
|
||||||
if ((not new_cell_value) or new_cell_value.isspace()) and \
|
|
||||||
(cell_name == 'name'):
|
|
||||||
message = _("The User Name field cannot be empty.")
|
|
||||||
messages.warning(request, message)
|
|
||||||
raise django_exceptions.ValidationError(message)
|
|
||||||
kwargs = {}
|
|
||||||
attr_to_keyword_map = {
|
|
||||||
'name': 'name',
|
|
||||||
'description': 'description',
|
|
||||||
'email': 'email',
|
|
||||||
'enabled': 'enabled',
|
|
||||||
'project_id': 'project'
|
|
||||||
}
|
|
||||||
for key in attr_to_keyword_map:
|
|
||||||
value = getattr(user_obj, key, None)
|
|
||||||
keyword_name = attr_to_keyword_map[key]
|
|
||||||
if value is not None:
|
|
||||||
kwargs[keyword_name] = value
|
|
||||||
api.keystone.user_update(request, user_obj, **kwargs)
|
|
||||||
|
|
||||||
except horizon_exceptions.Conflict:
|
|
||||||
message = _("This name is already taken.")
|
|
||||||
messages.warning(request, message)
|
|
||||||
raise django_exceptions.ValidationError(message)
|
|
||||||
except Exception:
|
|
||||||
horizon_exceptions.handle(request, ignore=True)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class UsersTable(tables.DataTable):
|
class UsersTable(tables.DataTable):
|
||||||
STATUS_CHOICES = (
|
STATUS_CHOICES = (
|
||||||
("true", True),
|
("true", True),
|
||||||
@ -225,19 +181,16 @@ class UsersTable(tables.DataTable):
|
|||||||
name = tables.Column('name',
|
name = tables.Column('name',
|
||||||
link="horizon:identity:users:detail",
|
link="horizon:identity:users:detail",
|
||||||
verbose_name=_('User Name'),
|
verbose_name=_('User Name'),
|
||||||
form_field=forms.CharField(required=False),
|
form_field=forms.CharField(required=False))
|
||||||
update_action=UpdateCell)
|
|
||||||
description = tables.Column(lambda obj: getattr(obj, 'description', None),
|
description = tables.Column(lambda obj: getattr(obj, 'description', None),
|
||||||
verbose_name=_('Description'),
|
verbose_name=_('Description'),
|
||||||
hidden=KEYSTONE_V2_ENABLED,
|
hidden=KEYSTONE_V2_ENABLED,
|
||||||
form_field=forms.CharField(
|
form_field=forms.CharField(
|
||||||
widget=forms.Textarea(attrs={'rows': 4}),
|
widget=forms.Textarea(attrs={'rows': 4}),
|
||||||
required=False),
|
required=False))
|
||||||
update_action=UpdateCell)
|
|
||||||
email = tables.Column(lambda obj: getattr(obj, 'email', None),
|
email = tables.Column(lambda obj: getattr(obj, 'email', None),
|
||||||
verbose_name=_('Email'),
|
verbose_name=_('Email'),
|
||||||
form_field=forms.EmailField(required=False),
|
form_field=forms.EmailField(required=False),
|
||||||
update_action=UpdateCell,
|
|
||||||
filters=(lambda v: defaultfilters
|
filters=(lambda v: defaultfilters
|
||||||
.default_if_none(v, ""),
|
.default_if_none(v, ""),
|
||||||
defaultfilters.escape,
|
defaultfilters.escape,
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
deprecations:
|
||||||
|
- Inline Edit functionality for Horizon tables is now deprecated and will be
|
||||||
|
removed in Horizon P (12.0)
|
||||||
|
The functionality was removed from the following tables.
|
||||||
|
Admin Volume Types table, Admin Metadata Definitions table, Identity
|
||||||
|
Projects table and Identity Users table
|
Loading…
Reference in New Issue
Block a user