Merge "Add user tab in project details view."
This commit is contained in:
commit
91c3038ac6
|
@ -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,)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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