Add user tab in project details view.
Add an extra tab "users" in the project details view which displays the users which have a role on the project (the members of the project). The users are displayed in a table which is an extension (inheritance) of the user table used in the Users panel. An extra column is added to this table, displaying the roles of each user on project Only users which have directly a role on the project are displayed, the users which have a role through a group are not displayed. Change-Id: I88b40fcda300ee4640155347d479a972abb2df02 Partial-Bug: #1785263
This commit is contained in:
parent
c42aacb27b
commit
5cb96dd6d1
@ -13,10 +13,14 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import exceptions
|
||||||
from horizon import tabs
|
from horizon import tabs
|
||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.identity.projects.users \
|
||||||
|
import tables as users_tables
|
||||||
|
|
||||||
|
|
||||||
class OverviewTab(tabs.Tab):
|
class OverviewTab(tabs.Tab):
|
||||||
"""Overview of the project. """
|
"""Overview of the project. """
|
||||||
@ -37,6 +41,85 @@ class OverviewTab(tabs.Tab):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class UsersTab(tabs.TableTab):
|
||||||
|
"""Display users member of the project. (directly or through a group)."""
|
||||||
|
table_classes = (users_tables.UsersTable,)
|
||||||
|
name = _("Users")
|
||||||
|
slug = "users"
|
||||||
|
template_name = "horizon/common/_detail_table.html"
|
||||||
|
preload = False
|
||||||
|
|
||||||
|
def _update_user_roles_names_from_roles_id(self, user, users_roles,
|
||||||
|
roles_list):
|
||||||
|
"""Add roles names to user.roles, based on users_roles.
|
||||||
|
|
||||||
|
:param user: user to update
|
||||||
|
:param users_roles: list of roles ID
|
||||||
|
:param roles_list: list of roles obtained with keystone
|
||||||
|
"""
|
||||||
|
user_roles_names = [role.name for role in roles_list
|
||||||
|
if role.id in users_roles]
|
||||||
|
current_user_roles_names = set(getattr(user, "roles", []))
|
||||||
|
user.roles = list(current_user_roles_names.union(user_roles_names))
|
||||||
|
|
||||||
|
def _get_users_from_project(self, project_id, roles, project_users):
|
||||||
|
"""Update with users which have role on project NOT through a group.
|
||||||
|
|
||||||
|
:param project_id: ID of the project
|
||||||
|
:param roles: list of roles from keystone
|
||||||
|
:param project_users: list to be updated with the users found
|
||||||
|
"""
|
||||||
|
|
||||||
|
# For keystone.user_list project_id is not passed as argument because
|
||||||
|
# it is ignored when using admin credentials
|
||||||
|
# Get all users (to be able to find user name)
|
||||||
|
users = api.keystone.user_list(self.request)
|
||||||
|
users = {user.id: user for user in users}
|
||||||
|
|
||||||
|
# Get project_users_roles ({user_id: [role_id_1, role_id_2]})
|
||||||
|
project_users_roles = api.keystone.get_project_users_roles(
|
||||||
|
self.request,
|
||||||
|
project=project_id)
|
||||||
|
|
||||||
|
for user_id in project_users_roles:
|
||||||
|
|
||||||
|
if user_id not in project_users:
|
||||||
|
# Add user to the project_users
|
||||||
|
project_users[user_id] = users[user_id]
|
||||||
|
project_users[user_id].roles = []
|
||||||
|
project_users[user_id].roles_from_groups = []
|
||||||
|
|
||||||
|
# Update the project_user role in order to get:
|
||||||
|
# project_users[user_id].roles = [role_name1, role_name2]
|
||||||
|
self._update_user_roles_names_from_roles_id(
|
||||||
|
user=project_users[user_id],
|
||||||
|
users_roles=project_users_roles[user_id],
|
||||||
|
roles_list=roles
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_userstable_data(self):
|
||||||
|
"""Get users with roles on the project."""
|
||||||
|
project_users = {}
|
||||||
|
project = self.tab_group.kwargs['project']
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get all global roles once to avoid multiple requests.
|
||||||
|
roles = api.keystone.role_list(self.request)
|
||||||
|
|
||||||
|
# Update project_users with users which have role directly on
|
||||||
|
# the project, (NOT through a group)
|
||||||
|
self._get_users_from_project(project_id=project.id,
|
||||||
|
roles=roles,
|
||||||
|
project_users=project_users)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
exceptions.handle(self.request,
|
||||||
|
_("Unable to display the users of this project.")
|
||||||
|
)
|
||||||
|
|
||||||
|
return project_users.values()
|
||||||
|
|
||||||
|
|
||||||
class ProjectDetailTabs(tabs.DetailTabsGroup):
|
class ProjectDetailTabs(tabs.DetailTabsGroup):
|
||||||
slug = "project_details"
|
slug = "project_details"
|
||||||
tabs = (OverviewTab,)
|
tabs = (OverviewTab, UsersTab,)
|
||||||
|
@ -1350,6 +1350,101 @@ class DetailProjectViewTests(test.BaseAdminViewTests):
|
|||||||
self.tenant.id)
|
self.tenant.id)
|
||||||
self.mock_enabled_quotas.assert_called_once_with(test.IsHttpRequest())
|
self.mock_enabled_quotas.assert_called_once_with(test.IsHttpRequest())
|
||||||
|
|
||||||
|
def _project_user_roles(self, role_assignments):
|
||||||
|
roles = {}
|
||||||
|
for role_assignment in role_assignments:
|
||||||
|
if hasattr(role_assignment, 'user'):
|
||||||
|
roles[role_assignment.user['id']] = [
|
||||||
|
role_assignment.role["id"]]
|
||||||
|
return roles
|
||||||
|
|
||||||
|
@test.create_mocks({api.keystone: ('tenant_get',
|
||||||
|
'user_list',
|
||||||
|
'get_project_users_roles',
|
||||||
|
'role_list',),
|
||||||
|
quotas: ('enabled_quotas',)})
|
||||||
|
def test_detail_view_users_tab(self):
|
||||||
|
project = self.tenants.first()
|
||||||
|
users = self.users.filter(domain_id=project.domain_id)
|
||||||
|
role_assignments = self.role_assignments.filter(
|
||||||
|
scope={'project': {'id': project.id}})
|
||||||
|
project_users_roles = self._project_user_roles(role_assignments)
|
||||||
|
|
||||||
|
# Prepare mocks
|
||||||
|
self.mock_tenant_get.return_value = project
|
||||||
|
self.mock_enabled_quotas.return_value = ('instances',)
|
||||||
|
self.mock_role_list.return_value = self.roles.list()
|
||||||
|
|
||||||
|
self.mock_user_list.return_value = users
|
||||||
|
self.mock_get_project_users_roles.return_value = project_users_roles
|
||||||
|
|
||||||
|
# Get project details view on user tab
|
||||||
|
url = PROJECT_DETAIL_URL % [project.id]
|
||||||
|
detail_view = tabs.ProjectDetailTabs(self.request, group=project)
|
||||||
|
users_tab_link = "?%s=%s" % (
|
||||||
|
detail_view.param_name,
|
||||||
|
detail_view.get_tab("users").get_id()
|
||||||
|
)
|
||||||
|
url += users_tab_link
|
||||||
|
res = self.client.get(url)
|
||||||
|
|
||||||
|
self.assertTemplateUsed(res, "horizon/common/_detail_table.html")
|
||||||
|
|
||||||
|
# Check the content of the table
|
||||||
|
users_expected = {
|
||||||
|
'1': {'roles': ['admin'], },
|
||||||
|
'2': {'roles': ['_member_'], },
|
||||||
|
'3': {'roles': ['_member_'], },
|
||||||
|
}
|
||||||
|
|
||||||
|
users_id_observed = [user.id for user in
|
||||||
|
res.context["userstable_table"].data]
|
||||||
|
self.assertItemsEqual(users_expected.keys(), users_id_observed)
|
||||||
|
|
||||||
|
# Check the users roles
|
||||||
|
for user in res.context["userstable_table"].data:
|
||||||
|
self.assertItemsEqual(users_expected[user.id]["roles"],
|
||||||
|
user.roles)
|
||||||
|
|
||||||
|
self.mock_tenant_get.assert_called_once_with(test.IsHttpRequest(),
|
||||||
|
self.tenant.id)
|
||||||
|
self.mock_enabled_quotas.assert_called_once_with(test.IsHttpRequest())
|
||||||
|
self.mock_role_list.assert_called_once_with(test.IsHttpRequest())
|
||||||
|
self.mock_get_project_users_roles.assert_called_once_with(
|
||||||
|
test.IsHttpRequest(), project=project.id)
|
||||||
|
self.mock_user_list.assert_called_once_with(test.IsHttpRequest())
|
||||||
|
|
||||||
|
@test.create_mocks({api.keystone: ("tenant_get",
|
||||||
|
"role_list",),
|
||||||
|
quotas: ('enabled_quotas',)})
|
||||||
|
def test_detail_view_users_tab_exception(self):
|
||||||
|
project = self.tenants.first()
|
||||||
|
|
||||||
|
# Prepare mocks
|
||||||
|
self.mock_tenant_get.return_value = project
|
||||||
|
self.mock_enabled_quotas.return_value = ('instances',)
|
||||||
|
self.mock_role_list.side_effect = self.exceptions.keystone
|
||||||
|
|
||||||
|
# Get project details view on user tab
|
||||||
|
url = reverse('horizon:identity:projects:detail', args=[project.id])
|
||||||
|
detail_view = tabs.ProjectDetailTabs(self.request, group=project)
|
||||||
|
users_tab_link = "?%s=%s" % (
|
||||||
|
detail_view.param_name,
|
||||||
|
detail_view.get_tab("users").get_id()
|
||||||
|
)
|
||||||
|
url += users_tab_link
|
||||||
|
res = self.client.get(url)
|
||||||
|
|
||||||
|
# Check the projects table is empty
|
||||||
|
self.assertFalse(res.context["userstable_table"].data)
|
||||||
|
# Check one error message is displayed
|
||||||
|
self.assertMessageCount(res, error=1)
|
||||||
|
|
||||||
|
self.mock_tenant_get.assert_called_once_with(test.IsHttpRequest(),
|
||||||
|
self.tenant.id)
|
||||||
|
self.mock_enabled_quotas.assert_called_once_with(test.IsHttpRequest())
|
||||||
|
self.mock_role_list.assert_called_once_with(test.IsHttpRequest())
|
||||||
|
|
||||||
|
|
||||||
@tag('selenium')
|
@tag('selenium')
|
||||||
class SeleniumTests(test.SeleniumAdminTestCase, test.TestCase):
|
class SeleniumTests(test.SeleniumAdminTestCase, test.TestCase):
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import forms
|
||||||
|
from horizon import tables
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.identity.users \
|
||||||
|
import tables as users_tables
|
||||||
|
|
||||||
|
|
||||||
|
class UsersTable(users_tables.UsersTable):
|
||||||
|
"""Display Users of the project with roles."""
|
||||||
|
roles = tables.Column(
|
||||||
|
lambda obj: ", ".join(getattr(obj, 'roles', [])),
|
||||||
|
verbose_name=_('Roles'),
|
||||||
|
form_field=forms.CharField(
|
||||||
|
widget=forms.Textarea(attrs={'rows': 4}),
|
||||||
|
required=False))
|
||||||
|
|
||||||
|
class Meta(object):
|
||||||
|
name = "userstable"
|
||||||
|
verbose_name = _("Users")
|
Loading…
Reference in New Issue
Block a user