Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 1 | # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style |
| 3 | # license that can be found in the LICENSE file or at |
| 4 | # https://developers.google.com/open-source/licenses/bsd |
| 5 | |
| 6 | """Classes to implement the hotlistpeople page and related forms.""" |
| 7 | from __future__ import print_function |
| 8 | from __future__ import division |
| 9 | from __future__ import absolute_import |
| 10 | |
| 11 | import logging |
| 12 | import time |
| 13 | |
| 14 | import ezt |
| 15 | |
| 16 | from features import hotlist_helpers |
| 17 | from features import hotlist_views |
| 18 | from framework import framework_helpers |
| 19 | from framework import framework_views |
| 20 | from framework import paginate |
| 21 | from framework import permissions |
| 22 | from framework import servlet |
| 23 | from framework import urls |
| 24 | from project import project_helpers |
| 25 | |
| 26 | MEMBERS_PER_PAGE = 50 |
| 27 | |
| 28 | |
| 29 | class HotlistPeopleList(servlet.Servlet): |
| 30 | _PAGE_TEMPLATE = 'project/people-list-page.ezt' |
| 31 | # Note: using the project's peoplelist page template. minor edits were |
| 32 | # to make it compatible with HotlistPeopleList |
| 33 | _MAIN_TAB_MODE = servlet.Servlet.HOTLIST_TAB_PEOPLE |
| 34 | |
| 35 | def AssertBasePermission(self, mr): |
| 36 | super(HotlistPeopleList, self).AssertBasePermission(mr) |
| 37 | if not permissions.CanViewHotlist( |
| 38 | mr.auth.effective_ids, mr.perms, mr.hotlist): |
| 39 | raise permissions.PermissionException( |
| 40 | 'User is now allowed to view the hotlist people list') |
| 41 | |
| 42 | def GatherPageData(self, mr): |
| 43 | """Build up a dictionary of data values to use when rendering the page.""" |
| 44 | if mr.auth.user_id: |
| 45 | self.services.user.AddVisitedHotlist( |
| 46 | mr.cnxn, mr.auth.user_id, mr.hotlist_id) |
| 47 | |
| 48 | all_members = (mr.hotlist.owner_ids + |
| 49 | mr.hotlist.editor_ids + mr.hotlist.follower_ids) |
| 50 | |
| 51 | hotlist_url = hotlist_helpers.GetURLOfHotlist( |
| 52 | mr.cnxn, mr.hotlist, self.services.user) |
| 53 | |
| 54 | with mr.profiler.Phase('gathering members on this page'): |
| 55 | users_by_id = framework_views.MakeAllUserViews( |
| 56 | mr.cnxn, self.services.user, all_members) |
| 57 | framework_views.RevealAllEmailsToMembers( |
| 58 | mr.cnxn, self.services, mr.auth, users_by_id, mr.project) |
| 59 | |
| 60 | untrusted_user_group_proxies = [] |
| 61 | # TODO(jojwang): implement FindUntrustedGroups() |
| 62 | |
| 63 | with mr.profiler.Phase('making member views'): |
| 64 | owner_views = self._MakeMemberViews(mr, mr.hotlist.owner_ids, users_by_id) |
| 65 | editor_views = self._MakeMemberViews(mr, mr.hotlist.editor_ids, |
| 66 | users_by_id) |
| 67 | follower_views = self._MakeMemberViews(mr, mr.hotlist.follower_ids, |
| 68 | users_by_id) |
| 69 | all_member_views = owner_views + editor_views + follower_views |
| 70 | |
| 71 | url_params = [(name, mr.GetParam(name)) for name in |
| 72 | framework_helpers.RECOGNIZED_PARAMS] |
| 73 | # We are passing in None for the project_name because we are not operating |
| 74 | # under any project. |
| 75 | pagination = paginate.ArtifactPagination( |
| 76 | all_member_views, mr.GetPositiveIntParam('num', MEMBERS_PER_PAGE), |
| 77 | mr.GetPositiveIntParam('start'), None, |
| 78 | '%s%s' % (hotlist_url, urls.HOTLIST_PEOPLE), url_params=url_params) |
| 79 | |
| 80 | offer_membership_editing = permissions.CanAdministerHotlist( |
| 81 | mr.auth.effective_ids, mr.perms, mr.hotlist) |
| 82 | |
| 83 | offer_remove_self = ( |
| 84 | not offer_membership_editing and |
| 85 | mr.auth.user_id and |
| 86 | mr.auth.user_id in mr.hotlist.editor_ids) |
| 87 | |
| 88 | newly_added_views = [mv for mv in all_member_views |
| 89 | if str(mv.user.user_id) in mr.GetParam('new', [])] |
| 90 | |
| 91 | return { |
| 92 | 'is_hotlist': ezt.boolean(True), |
| 93 | 'untrusted_user_groups': untrusted_user_group_proxies, |
| 94 | 'pagination': pagination, |
| 95 | 'initial_add_members': '', |
| 96 | 'subtab_mode': None, |
| 97 | 'initially_expand_form': ezt.boolean(False), |
| 98 | 'newly_added_views': newly_added_views, |
| 99 | 'offer_membership_editing': ezt.boolean(offer_membership_editing), |
| 100 | 'offer_remove_self': ezt.boolean(offer_remove_self), |
| 101 | 'total_num_owners': len(mr.hotlist.owner_ids), |
| 102 | 'check_abandonment': ezt.boolean(True), |
| 103 | 'initial_new_owner_username': '', |
| 104 | 'placeholder': 'new-owner-username', |
| 105 | 'open_dialog': ezt.boolean(False), |
| 106 | 'viewing_user_page': ezt.boolean(True), |
| 107 | 'new_ui_url': '%s/%s/people' % (urls.HOTLISTS, mr.hotlist_id), |
| 108 | } |
| 109 | |
| 110 | def ProcessFormData(self, mr, post_data): |
| 111 | """Process the posted form.""" |
| 112 | permit_edit = permissions.CanAdministerHotlist( |
| 113 | mr.auth.effective_ids, mr.perms, mr.hotlist) |
| 114 | can_remove_self = ( |
| 115 | not permit_edit and |
| 116 | mr.auth.user_id and |
| 117 | mr.auth.user_id in mr.hotlist.editor_ids) |
| 118 | if not can_remove_self and not permit_edit: |
| 119 | raise permissions.PermissionException( |
| 120 | 'User is not permitted to edit hotlist membership') |
| 121 | hotlist_url = hotlist_helpers.GetURLOfHotlist( |
| 122 | mr.cnxn, mr.hotlist, self.services.user) |
| 123 | if permit_edit: |
| 124 | if 'addbtn' in post_data: |
| 125 | return self.ProcessAddMembers(mr, post_data, hotlist_url) |
| 126 | elif 'removebtn' in post_data: |
| 127 | return self.ProcessRemoveMembers(mr, post_data, hotlist_url) |
| 128 | elif 'changeowners' in post_data: |
| 129 | return self.ProcessChangeOwnership(mr, post_data) |
| 130 | if can_remove_self: |
| 131 | if 'removeself' in post_data: |
| 132 | return self.ProcessRemoveSelf(mr, hotlist_url) |
| 133 | |
| 134 | def _MakeMemberViews(self, mr, member_ids, users_by_id): |
| 135 | """Return a sorted list of MemberViews for display by EZT.""" |
| 136 | member_views = [hotlist_views.MemberView( |
| 137 | mr.auth.user_id, member_id, users_by_id[member_id], |
| 138 | mr.hotlist) for member_id in member_ids] |
| 139 | member_views.sort(key=lambda mv: mv.user.email) |
| 140 | return member_views |
| 141 | |
| 142 | def ProcessChangeOwnership(self, mr, post_data): |
| 143 | new_owner_id_set = project_helpers.ParseUsernames( |
| 144 | mr.cnxn, self.services.user, post_data.get('changeowners')) |
| 145 | remain_as_editor = post_data.get('becomeeditor') == 'on' |
| 146 | if len(new_owner_id_set) != 1: |
| 147 | mr.errors.transfer_ownership = ( |
| 148 | 'Please add one valid user email.') |
| 149 | else: |
| 150 | new_owner_id = new_owner_id_set.pop() |
| 151 | if self.services.features.LookupHotlistIDs( |
| 152 | mr.cnxn, [mr.hotlist.name], [new_owner_id]): |
| 153 | mr.errors.transfer_ownership = ( |
| 154 | 'This user already owns a hotlist with the same name') |
| 155 | |
| 156 | if mr.errors.AnyErrors(): |
| 157 | self.PleaseCorrect( |
| 158 | mr, initial_new_owner_username=post_data.get('changeowners'), |
| 159 | open_dialog=ezt.boolean(True)) |
| 160 | else: |
| 161 | old_and_new_owner_ids = [new_owner_id] + mr.hotlist.owner_ids |
| 162 | (_, editor_ids, follower_ids) = hotlist_helpers.MembersWithoutGivenIDs( |
| 163 | mr.hotlist, old_and_new_owner_ids) |
| 164 | if remain_as_editor and mr.hotlist.owner_ids: |
| 165 | editor_ids.append(mr.hotlist.owner_ids[0]) |
| 166 | |
| 167 | self.services.features.UpdateHotlistRoles( |
| 168 | mr.cnxn, mr.hotlist_id, [new_owner_id], editor_ids, follower_ids) |
| 169 | |
| 170 | hotlist = self.services.features.GetHotlist(mr.cnxn, mr.hotlist_id) |
| 171 | hotlist_url = hotlist_helpers.GetURLOfHotlist( |
| 172 | mr.cnxn, hotlist, self.services.user) |
| 173 | return framework_helpers.FormatAbsoluteURL( |
| 174 | mr,'%s%s' % (hotlist_url, urls.HOTLIST_PEOPLE), |
| 175 | saved=1, ts=int(time.time()), |
| 176 | include_project=False) |
| 177 | |
| 178 | def ProcessAddMembers(self, mr, post_data, hotlist_url): |
| 179 | """Process the user's request to add members. |
| 180 | |
| 181 | Args: |
| 182 | mr: common information parsed from the HTTP request. |
| 183 | post_data: dictionary of form data |
| 184 | hotlist_url: hotlist_url to return to after data has been processed. |
| 185 | |
| 186 | Returns: |
| 187 | String URL to redirect the user to after processing |
| 188 | """ |
| 189 | # NOTE: using project_helpers function |
| 190 | new_member_ids = project_helpers.ParseUsernames( |
| 191 | mr.cnxn, self.services.user, post_data.get('addmembers')) |
| 192 | if not new_member_ids or not post_data.get('addmembers'): |
| 193 | mr.errors.incorrect_email_input = ( |
| 194 | 'Please give full emails seperated by commas.') |
| 195 | role = post_data['role'] |
| 196 | |
| 197 | (owner_ids, editor_ids, follower_ids) = hotlist_helpers.MembersWithGivenIDs( |
| 198 | mr.hotlist, new_member_ids, role) |
| 199 | # TODO(jojwang): implement MAX_HOTLIST_PEOPLE |
| 200 | |
| 201 | if not owner_ids: |
| 202 | mr.errors.addmembers = ( |
| 203 | 'Cannot have a hotlist without an owner; please leave at least one.') |
| 204 | |
| 205 | if mr.errors.AnyErrors(): |
| 206 | add_members_str = post_data.get('addmembers', '') |
| 207 | self.PleaseCorrect( |
| 208 | mr, initial_add_members=add_members_str, initially_expand_form=True) |
| 209 | else: |
| 210 | self.services.features.UpdateHotlistRoles( |
| 211 | mr.cnxn, mr.hotlist_id, owner_ids, editor_ids, follower_ids) |
| 212 | return framework_helpers.FormatAbsoluteURL( |
| 213 | mr, '%s%s' % ( |
| 214 | hotlist_url, urls.HOTLIST_PEOPLE), |
| 215 | saved=1, ts=int(time.time()), |
| 216 | new=','.join([str(u) for u in new_member_ids]), |
| 217 | include_project=False) |
| 218 | |
| 219 | def ProcessRemoveMembers(self, mr, post_data, hotlist_url): |
| 220 | """Process the user's request to remove members.""" |
| 221 | remove_strs = post_data.getall('remove') |
| 222 | logging.info('remove_strs = %r', remove_strs) |
| 223 | remove_ids = set( |
| 224 | self.services.user.LookupUserIDs(mr.cnxn, remove_strs).values()) |
| 225 | (owner_ids, editor_ids, |
| 226 | follower_ids) = hotlist_helpers.MembersWithoutGivenIDs( |
| 227 | mr.hotlist, remove_ids) |
| 228 | |
| 229 | self.services.features.UpdateHotlistRoles( |
| 230 | mr.cnxn, mr.hotlist_id, owner_ids, editor_ids, follower_ids) |
| 231 | |
| 232 | return framework_helpers.FormatAbsoluteURL( |
| 233 | mr, '%s%s' % ( |
| 234 | hotlist_url, urls.HOTLIST_PEOPLE), |
| 235 | saved=1, ts=int(time.time()), include_project=False) |
| 236 | |
| 237 | def ProcessRemoveSelf(self, mr, hotlist_url): |
| 238 | """Process the request to remove the logged-in user.""" |
| 239 | remove_ids = [mr.auth.user_id] |
| 240 | |
| 241 | # This function does no permission checking; that's done by the caller. |
| 242 | (owner_ids, editor_ids, |
| 243 | follower_ids) = hotlist_helpers.MembersWithoutGivenIDs( |
| 244 | mr.hotlist, remove_ids) |
| 245 | |
| 246 | self.services.features.UpdateHotlistRoles( |
| 247 | mr.cnxn, mr.hotlist_id, owner_ids, editor_ids, follower_ids) |
| 248 | |
| 249 | return framework_helpers.FormatAbsoluteURL( |
| 250 | mr, '%s%s' % ( |
| 251 | hotlist_url, urls.HOTLIST_PEOPLE), |
| 252 | saved=1, ts=int(time.time()), include_project=False) |