Merge "Add user tab in project details view."

This commit is contained in:
Zuul 2018-09-11 18:12:58 +00:00 committed by Gerrit Code Review
commit 91c3038ac6
4 changed files with 212 additions and 1 deletions

View File

@ -13,10 +13,14 @@
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from openstack_dashboard import api
from openstack_dashboard.dashboards.identity.projects.users \
import tables as users_tables
class OverviewTab(tabs.Tab):
"""Overview of the project. """
@ -37,6 +41,85 @@ class OverviewTab(tabs.Tab):
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):
slug = "project_details"
tabs = (OverviewTab,)
tabs = (OverviewTab, UsersTab,)

View File

@ -1350,6 +1350,101 @@ class DetailProjectViewTests(test.BaseAdminViewTests):
self.tenant.id)
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')
class SeleniumTests(test.SeleniumAdminTestCase, test.TestCase):

View File

@ -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")