blob: 1eb00fff5152d7037dda2d0e53637a924895e4cd [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# 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."""
7from __future__ import print_function
8from __future__ import division
9from __future__ import absolute_import
10
11import logging
12import time
13
14import ezt
15
16from features import hotlist_helpers
17from features import hotlist_views
18from framework import framework_helpers
19from framework import framework_views
20from framework import paginate
21from framework import permissions
22from framework import servlet
23from framework import urls
24from project import project_helpers
25
26MEMBERS_PER_PAGE = 50
27
28
29class 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)