blob: af9cd4935fb92fd6842307f678f705b0f471dbda [file] [log] [blame]
# 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
"""A class to display details about each project member."""
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 flaskservlet
from framework import framework_bizobj
from framework import framework_helpers
from framework import framework_views
from framework import permissions
from framework import servlet
from framework import template_helpers
from framework import urls
from project import project_helpers
from project import project_views
class PeopleDetail(servlet.Servlet):
"""People detail page documents one partipant's involvement in a project."""
_PAGE_TEMPLATE = 'project/people-detail-page.ezt'
_MAIN_TAB_MODE = flaskservlet.FlaskServlet.MAIN_TAB_PEOPLE
def AssertBasePermission(self, mr):
"""Check that the user is allowed to access this servlet."""
super(PeopleDetail, self).AssertBasePermission(mr)
member_id = self.ValidateMemberID(mr.cnxn, mr.specified_user_id, mr.project)
# For now, contributors who cannot view other contributors are further
# restricted from viewing any part of the member list or detail pages.
if (not permissions.CanViewContributorList(mr, mr.project) and
member_id != mr.auth.user_id):
raise permissions.PermissionException(
'User is not allowed to view other people\'s details')
def GatherPageData(self, mr):
"""Build up a dictionary of data values to use when rendering the page."""
member_id = self.ValidateMemberID(mr.cnxn, mr.specified_user_id, mr.project)
group_ids =
mr.cnxn, [member_id])
users_by_id = framework_views.MakeAllUserViews(
mr.cnxn,, [member_id])
mr.cnxn,, mr.auth, users_by_id, mr.project)
project_commitments =
mr.cnxn, mr.project_id)
(ac_exclusion_ids, no_expand_ids
) =
mr.cnxn, mr.project_id)
member_view = project_views.MemberView(
mr.auth.user_id, member_id, users_by_id[member_id], mr.project,
ac_exclusion=(member_id in ac_exclusion_ids),
no_expand=(member_id in no_expand_ids),
is_group=(member_id in group_ids))
member_user =, member_id)
# This ignores indirect memberships, which is ok because we are viewing
# the page for a member directly involved in the project
role_perms = permissions.GetPermissions(
member_user, {member_id}, mr.project)
# TODO(jrobbins): clarify in the UI which permissions are built-in to
# the user's direct role, vs. which are granted via a group membership,
# vs. which ones are extra_perms that have been added specifically for
# this user.
member_perms = template_helpers.EZTItem()
for perm in CHECKBOX_PERMS:
setattr(member_perms, perm,
ezt.boolean(role_perms.HasPerm(perm, member_id, mr.project)))
displayed_extra_perms = [perm for perm in member_view.extra_perms
if perm not in CHECKBOX_PERMS]
viewing_self = mr.auth.user_id == member_id
warn_abandonment = (viewing_self and
return {
'subtab_mode': None,
'member': member_view,
'role_perms': role_perms,
'member_perms': member_perms,
'displayed_extra_perms': displayed_extra_perms,
'offer_edit_perms': ezt.boolean(self.CanEditPerms(mr)),
'offer_edit_member_notes': ezt.boolean(
self.CanEditMemberNotes(mr, member_id)),
'offer_remove_role': ezt.boolean(self.CanRemoveRole(mr, member_id)),
'expand_perms': ezt.boolean(mr.auth.user_pb.keep_people_perms_open),
'warn_abandonment': ezt.boolean(warn_abandonment),
'total_num_owners': len(mr.project.owner_ids),
def ValidateMemberID(self, cnxn, member_id, project):
"""Lookup a project member by user_id.
cnxn: connection to SQL database.
member_id: int user_id, same format as user profile page.
project: the current Project PB.
The user ID of the project member. Raises an exception if the username
cannot be looked up, or if that user is not in the project.
if not member_id:
self.abort(404, 'project member not specified')
member_username = None
member_username =, member_id)
except exceptions.NoSuchUserException:'user_id %s not found', member_id)
if not member_username:'There is no such user id %r', member_id)
self.abort(404, 'project member not found')
if not framework_bizobj.UserIsInProject(project, {member_id}):'User %r is not a member of %r',
member_username, project.project_name)
self.abort(404, 'project member not found')
return member_id
def ProcessFormData(self, mr, post_data):
"""Process the posted form."""
# 1. Parse and validate user input.
user_id, role, extra_perms, notes, ac_exclusion, no_expand = (
self.ParsePersonData(mr, post_data))
member_id = self.ValidateMemberID(mr.cnxn, user_id, mr.project)
# 2. Call services layer to save changes.
if 'remove' in post_data:
self.ProcessRemove(mr, member_id)
mr, role, extra_perms, notes, member_id, ac_exclusion, no_expand)
# 3. Determine the next page in the UI flow.
if 'remove' in post_data:
return framework_helpers.FormatAbsoluteURL(
mr, urls.PEOPLE_LIST, saved=1, ts=int(time.time()))
return framework_helpers.FormatAbsoluteURL(
mr, urls.PEOPLE_DETAIL, u=user_id, saved=1, ts=int(time.time()))
def ProcessRemove(self, mr, member_id):
"""Process the posted form when the user pressed 'Remove'."""
if not self.CanRemoveRole(mr, member_id):
raise permissions.PermissionException(
'User is not allowed to remove this member from the project')
self.RemoveRole(mr.cnxn, mr.project, member_id)
def ProcessSave(
self, mr, role, extra_perms, notes, member_id, ac_exclusion,
"""Process the posted form when the user pressed 'Save'."""
if (not self.CanEditPerms(mr) and
not self.CanEditMemberNotes(mr, member_id)):
raise permissions.PermissionException(
'User is not allowed to edit people in this project')
if self.CanEditPerms(mr):
mr.cnxn, mr.project_id, member_id, extra_perms)
self.UpdateRole(mr.cnxn, mr.project, role, member_id)
if self.CanEditMemberNotes(mr, member_id):
mr.cnxn, mr.project_id, member_id, notes)
if self.CanEditPerms(mr):
mr.cnxn, mr.project_id, member_id, ac_exclusion, no_expand)
def CanEditMemberNotes(self, mr, member_id):
"""Return true if the logged in user can edit the current user's notes."""
return (self.CheckPerm(mr, permissions.EDIT_ANY_MEMBER_NOTES) or
member_id == mr.auth.user_id)
def CanEditPerms(self, mr):
"""Return true if the logged in user can edit the current user's perms."""
return self.CheckPerm(mr, permissions.EDIT_PROJECT)
def CanRemoveRole(self, mr, member_id):
"""Return true if the logged in user can remove the current user's role."""
return (self.CheckPerm(mr, permissions.EDIT_PROJECT) or
member_id == mr.auth.user_id)
def ParsePersonData(self, mr, post_data):
"""Parse the POST data for a project member.
mr: common information parsed from the user's request.
post_data: dictionary of lists of values for each HTML
form field.
A tuple with user_id, role, extra_perms, and notes.
if not mr.specified_user_id:
raise exceptions.InputException('Field user_id is missing')
role = post_data.get('role', '').lower()
extra_perms = []
# TODO( getall in Flask is getlist
# for ep in post_data.getlist('extra_perms'):
for ep in post_data.getall('extra_perms'):
perm = framework_bizobj.CanonicalizeLabel(ep)
# Perms with leading underscores are reserved.
perm = perm.strip('_')
if perm:
notes = post_data.get('notes', '').strip()
ac_exclusion = not post_data.get('ac_include', False)
no_expand = not post_data.get('ac_expand', False)
return (mr.specified_user_id, role, extra_perms, notes, ac_exclusion,
def RemoveRole(self, cnxn, project, member_id):
"""Remove the given member from the project."""
(owner_ids, committer_ids,
contributor_ids) = project_helpers.MembersWithoutGivenIDs(
project, {member_id})
cnxn, project.project_id, owner_ids, committer_ids, contributor_ids)
def UpdateRole(self, cnxn, project, role, member_id):
"""If the user's role was changed, update that in the Project."""
if not role:
return # Role was not in the form data
if role == framework_helpers.GetRoleName({member_id}, project).lower():
return # No change needed
(owner_ids, committer_ids,
contributor_ids) = project_helpers.MembersWithGivenIDs(
project, {member_id}, role)
cnxn, project.project_id, owner_ids, committer_ids, contributor_ids)
# def GetPeopleDetailPage(self, **kwargs):
# return self.handler(**kwargs)
# def PostPeopleDetailPage(self, **kwargs):
# return self.handler(**kwargs)