| Index: appengine/monorail/sitewide/groupdetail.py
|
| diff --git a/appengine/monorail/sitewide/groupdetail.py b/appengine/monorail/sitewide/groupdetail.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..4d66e0336b335df53d6cba06bd5afa89f7f2dfe7
|
| --- /dev/null
|
| +++ b/appengine/monorail/sitewide/groupdetail.py
|
| @@ -0,0 +1,196 @@
|
| +# Copyright 2016 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is govered by a BSD-style
|
| +# license that can be found in the LICENSE file or at
|
| +# https://developers.google.com/open-source/licenses/bsd
|
| +
|
| +"""A class to display a user group, including a paginated list of members."""
|
| +
|
| +import logging
|
| +import time
|
| +
|
| +from third_party import ezt
|
| +
|
| +from framework import framework_helpers
|
| +from framework import framework_views
|
| +from framework import paginate
|
| +from framework import permissions
|
| +from framework import servlet
|
| +from project import project_helpers
|
| +from proto import usergroup_pb2
|
| +from services import usergroup_svc
|
| +from sitewide import group_helpers
|
| +from sitewide import sitewide_views
|
| +
|
| +MEMBERS_PER_PAGE = 50
|
| +
|
| +
|
| +class GroupDetail(servlet.Servlet):
|
| + """The group detail page presents information about one user group."""
|
| +
|
| + _PAGE_TEMPLATE = 'sitewide/group-detail-page.ezt'
|
| +
|
| + def AssertBasePermission(self, mr):
|
| + """Assert that the user has the permissions needed to view this page."""
|
| + super(GroupDetail, self).AssertBasePermission(mr)
|
| +
|
| + group_id = mr.viewed_user_auth.user_id
|
| + group_settings = self.services.usergroup.GetGroupSettings(
|
| + mr.cnxn, group_id)
|
| +
|
| + member_ids, owner_ids = self.services.usergroup.LookupAllMembers(
|
| + mr.cnxn, [group_id])
|
| + (owned_project_ids, membered_project_ids,
|
| + contrib_project_ids) = self.services.project.GetUserRolesInAllProjects(
|
| + mr.cnxn, mr.auth.effective_ids)
|
| + project_ids = owned_project_ids.union(
|
| + membered_project_ids).union(contrib_project_ids)
|
| + if not permissions.CanViewGroup(
|
| + mr.perms, mr.auth.effective_ids, group_settings, member_ids[group_id],
|
| + owner_ids[group_id], project_ids):
|
| + raise permissions.PermissionException(
|
| + 'User is not allowed to view a user group')
|
| +
|
| + def GatherPageData(self, mr):
|
| + """Build up a dictionary of data values to use when rendering the page."""
|
| + group_id = mr.viewed_user_auth.user_id
|
| + group_settings = self.services.usergroup.GetGroupSettings(
|
| + mr.cnxn, group_id)
|
| +
|
| + member_ids_dict, owner_ids_dict = (
|
| + self.services.usergroup.LookupVisibleMembers(
|
| + mr.cnxn, [group_id], mr.perms, mr.auth.effective_ids,
|
| + self.services))
|
| + member_ids = member_ids_dict[group_id]
|
| + owner_ids = owner_ids_dict[group_id]
|
| + member_pbs_dict = self.services.user.GetUsersByIDs(
|
| + mr.cnxn, member_ids)
|
| + owner_pbs_dict = self.services.user.GetUsersByIDs(
|
| + mr.cnxn, owner_ids)
|
| + member_dict = {}
|
| + for user_id, user_pb in member_pbs_dict.iteritems():
|
| + member_view = group_helpers.GroupMemberView(
|
| + user_id, user_pb.email, user_pb.obscure_email, group_id, 'member')
|
| + member_dict[user_id] = member_view
|
| + owner_dict = {}
|
| + for user_id, user_pb in owner_pbs_dict.iteritems():
|
| + member_view = group_helpers.GroupMemberView(
|
| + user_id, user_pb.email, user_pb.obscure_email, group_id, 'owner')
|
| + owner_dict[user_id] = member_view
|
| +
|
| + member_user_views = []
|
| + member_user_views.extend(
|
| + sorted(owner_dict.values(), key=lambda u: u.email))
|
| + member_user_views.extend(
|
| + sorted(member_dict.values(), key=lambda u: u.email))
|
| +
|
| + group_view = sitewide_views.GroupView(
|
| + mr.viewed_user_auth.email, len(member_ids), group_settings,
|
| + mr.viewed_user_auth.user_id)
|
| + pagination = paginate.ArtifactPagination(
|
| + mr, member_user_views, MEMBERS_PER_PAGE, group_view.detail_url)
|
| +
|
| + is_imported_group = bool(group_settings.ext_group_type)
|
| +
|
| + offer_membership_editing = permissions.CanEditGroup(
|
| + mr.perms, mr.auth.effective_ids, owner_ids) and not is_imported_group
|
| +
|
| + return {
|
| + 'admin_tab_mode': self.ADMIN_TAB_META,
|
| + 'offer_membership_editing': ezt.boolean(offer_membership_editing),
|
| + 'initial_add_members': '',
|
| + 'initially_expand_form': ezt.boolean(False),
|
| + 'groupid': group_id,
|
| + 'groupname': mr.viewed_username,
|
| + 'settings': group_settings,
|
| + 'pagination': pagination,
|
| + }
|
| +
|
| + def ProcessFormData(self, mr, post_data):
|
| + """Process the posted form."""
|
| + _, owner_ids_dict = self.services.usergroup.LookupMembers(
|
| + mr.cnxn, [mr.viewed_user_auth.user_id])
|
| + owner_ids = owner_ids_dict[mr.viewed_user_auth.user_id]
|
| + permit_edit = permissions.CanEditGroup(
|
| + mr.perms, mr.auth.effective_ids, owner_ids)
|
| + if not permit_edit:
|
| + raise permissions.PermissionException(
|
| + 'User is not permitted to edit group membership')
|
| +
|
| + group_settings = self.services.usergroup.GetGroupSettings(
|
| + mr.cnxn, mr.viewed_user_auth.user_id)
|
| + if bool(group_settings.ext_group_type):
|
| + raise permissions.PermissionException(
|
| + 'Imported groups are read-only')
|
| +
|
| + if 'addbtn' in post_data:
|
| + return self.ProcessAddMembers(mr, post_data)
|
| + elif 'removebtn' in post_data:
|
| + return self.ProcessRemoveMembers(mr, post_data)
|
| +
|
| + def ProcessAddMembers(self, mr, post_data):
|
| + """Process the user's request to add members.
|
| +
|
| + Args:
|
| + mr: common information parsed from the HTTP request.
|
| + post_data: dictionary of form data.
|
| +
|
| + Returns:
|
| + String URL to redirect the user to after processing.
|
| + """
|
| + # 1. Gather data from the request.
|
| + group_id = mr.viewed_user_auth.user_id
|
| + add_members_str = post_data.get('addmembers')
|
| + new_member_ids = project_helpers.ParseUsernames(
|
| + mr.cnxn, self.services.user, add_members_str)
|
| + role = post_data['role']
|
| +
|
| + # 2. Call services layer to save changes.
|
| + if not mr.errors.AnyErrors():
|
| + try:
|
| + self.services.usergroup.UpdateMembers(
|
| + mr.cnxn, group_id, new_member_ids, role)
|
| + except usergroup_svc.CircularGroupException:
|
| + mr.errors.addmembers = (
|
| + 'The members are already ancestors of current group.')
|
| +
|
| + # 3. Determine the next page in the UI flow.
|
| + if mr.errors.AnyErrors():
|
| + self.PleaseCorrect(
|
| + mr, initial_add_members=add_members_str,
|
| + initially_expand_form=ezt.boolean(True))
|
| + else:
|
| + return framework_helpers.FormatAbsoluteURL(
|
| + mr, '/g/%s/' % mr.viewed_username, include_project=False,
|
| + saved=1, ts=int(time.time()))
|
| +
|
| + def ProcessRemoveMembers(self, mr, post_data):
|
| + """Process the user's request to remove members.
|
| +
|
| + Args:
|
| + mr: common information parsed from the HTTP request.
|
| + post_data: dictionary of form data.
|
| +
|
| + Returns:
|
| + String URL to redirect the user to after processing.
|
| + """
|
| + # 1. Gather data from the request.
|
| + remove_strs = post_data.getall('remove')
|
| + logging.info('remove_strs = %r', remove_strs)
|
| +
|
| + if not remove_strs:
|
| + mr.errors.remove = 'No users specified'
|
| +
|
| + # 2. Call services layer to save changes.
|
| + if not mr.errors.AnyErrors():
|
| + remove_ids = set(
|
| + self.services.user.LookupUserIDs(mr.cnxn, remove_strs).values())
|
| + self.services.usergroup.RemoveMembers(
|
| + mr.cnxn, mr.viewed_user_auth.user_id, remove_ids)
|
| +
|
| + # 3. Determine the next page in the UI flow.
|
| + if mr.errors.AnyErrors():
|
| + self.PleaseCorrect(mr)
|
| + else:
|
| + return framework_helpers.FormatAbsoluteURL(
|
| + mr, '/g/%s/' % mr.viewed_username, include_project=False,
|
| + saved=1, ts=int(time.time()))
|
|
|