Project import generated by Copybara.
GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/sitewide/groupdetail.py b/sitewide/groupdetail.py
new file mode 100644
index 0000000..b28baa9
--- /dev/null
+++ b/sitewide/groupdetail.py
@@ -0,0 +1,210 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed 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."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import logging
+import time
+
+import ezt
+
+from framework import exceptions
+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 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)
+ if not group_settings:
+ return
+
+ 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.CanViewGroupMembers(
+ 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)
+ if not group_settings:
+ raise exceptions.NoSuchGroupException()
+
+ 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.items():
+ member_view = group_helpers.GroupMemberView(user_pb, group_id, 'member')
+ member_dict[user_id] = member_view
+ owner_dict = {}
+ for user_id, user_pb in owner_pbs_dict.items():
+ member_view = group_helpers.GroupMemberView(user_pb, group_id, 'owner')
+ owner_dict[user_id] = member_view
+
+ member_user_views = []
+ member_user_views.extend(
+ sorted(list(owner_dict.values()), key=lambda u: u.email))
+ member_user_views.extend(
+ sorted(list(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)
+ url_params = [(name, mr.GetParam(name)) for name in
+ framework_helpers.RECOGNIZED_PARAMS]
+ pagination = paginate.ArtifactPagination(
+ member_user_views, mr.GetPositiveIntParam('num', MEMBERS_PER_PAGE),
+ mr.GetPositiveIntParam('start'), mr.project_name, group_view.detail_url,
+ url_params=url_params)
+
+ 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
+
+ group_type = 'Monorail user group'
+ if group_settings.ext_group_type:
+ group_type = str(group_settings.ext_group_type).capitalize()
+
+ 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,
+ 'group_type': group_type,
+ '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 exceptions.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()))