Skip to content

Commit

Permalink
Change organization role (2/2)
Browse files Browse the repository at this point in the history
- Implement pypi#11081.
  • Loading branch information
divbzero committed May 3, 2022
1 parent 0c01698 commit 9c14f9b
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 59 deletions.
36 changes: 18 additions & 18 deletions warehouse/locale/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ msgstr ""
msgid "Successful WebAuthn assertion"
msgstr ""

#: warehouse/accounts/views.py:447 warehouse/manage/views.py:826
#: warehouse/accounts/views.py:447 warehouse/manage/views.py:827
msgid "Recovery code accepted. The supplied code cannot be used again."
msgstr ""

Expand Down Expand Up @@ -267,83 +267,83 @@ msgid ""
"Choose a different organization account name."
msgstr ""

#: warehouse/manage/forms.py:396
#: warehouse/manage/forms.py:408
msgid ""
"The organization name is too long. Choose a organization name with 100 "
"characters or less."
msgstr ""

#: warehouse/manage/forms.py:408
#: warehouse/manage/forms.py:420
msgid ""
"The organization URL is too long. Choose a organization URL with 400 "
"characters or less."
msgstr ""

#: warehouse/manage/forms.py:422
#: warehouse/manage/forms.py:434
msgid ""
"The organization description is too long. Choose a organization "
"description with 400 characters or less."
msgstr ""

#: warehouse/manage/views.py:257
#: warehouse/manage/views.py:258
msgid "Email ${email_address} added - check your email for a verification link"
msgstr ""

#: warehouse/manage/views.py:774
#: warehouse/manage/views.py:775
msgid "Recovery codes already generated"
msgstr ""

#: warehouse/manage/views.py:775
#: warehouse/manage/views.py:776
msgid "Generating new recovery codes will invalidate your existing codes."
msgstr ""

#: warehouse/manage/views.py:1230
#: warehouse/manage/views.py:1245
msgid "User '${username}' already has ${role_name} role for organization"
msgstr ""

#: warehouse/manage/views.py:1241
#: warehouse/manage/views.py:1256
msgid ""
"User '${username}' does not have a verified primary email address and "
"cannot be added as a ${role_name} for organization"
msgstr ""

#: warehouse/manage/views.py:1254 warehouse/manage/views.py:2321
#: warehouse/manage/views.py:1269 warehouse/manage/views.py:2413
msgid "User '${username}' already has an active invite. Please try again later."
msgstr ""

#: warehouse/manage/views.py:1296 warehouse/manage/views.py:2379
#: warehouse/manage/views.py:1311 warehouse/manage/views.py:2471
msgid "Invitation sent to '${username}'"
msgstr ""

#: warehouse/manage/views.py:1352
#: warehouse/manage/views.py:1367
msgid "Could not find organization invitation."
msgstr ""

#: warehouse/manage/views.py:1365 warehouse/manage/views.py:2437
#: warehouse/manage/views.py:1380 warehouse/manage/views.py:2529
msgid "Invitation already expired."
msgstr ""

#: warehouse/manage/views.py:1383 warehouse/manage/views.py:2461
#: warehouse/manage/views.py:1398 warehouse/manage/views.py:2553
msgid "Invitation revoked from '${username}'."
msgstr ""

#: warehouse/manage/views.py:1616
#: warehouse/manage/views.py:1708
msgid ""
"There have been too many attempted OpenID Connect registrations. Try "
"again later."
msgstr ""

#: warehouse/manage/views.py:2297
#: warehouse/manage/views.py:2389
msgid "User '${username}' already has ${role_name} role for project"
msgstr ""

#: warehouse/manage/views.py:2308
#: warehouse/manage/views.py:2400
msgid ""
"User '${username}' does not have a verified primary email address and "
"cannot be added as a ${role_name} for project"
msgstr ""

#: warehouse/manage/views.py:2426
#: warehouse/manage/views.py:2518
msgid "Could not find role invitation."
msgstr ""

Expand Down
12 changes: 12 additions & 0 deletions warehouse/manage/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,18 @@ def __init__(self, *args, orgtype, organization_service, user_service, **kwargs)
self.user_service = user_service


class ChangeOrganizationRoleForm(OrganizationRoleNameMixin, forms.Form):
def __init__(self, *args, orgtype, **kwargs):
super().__init__(*args, **kwargs)
if orgtype != OrganizationType.Company:
# Remove "Billing Manager" choice if organization is not a "Company"
self.role_name.choices = [
choice
for choice in self.role_name.choices
if "Billing Manager" not in choice
]


class CreateOrganizationForm(forms.Form, OrganizationNameMixin):

__params__ = ["name", "display_name", "link_url", "description", "orgtype"]
Expand Down
95 changes: 56 additions & 39 deletions warehouse/manage/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
from warehouse.macaroons.interfaces import IMacaroonService
from warehouse.manage.forms import (
AddEmailForm,
ChangeOrganizationRoleForm,
ChangePasswordForm,
ChangeRoleForm,
ConfirmPasswordForm,
Expand Down Expand Up @@ -1032,6 +1033,20 @@ def user_organizations(request):
}


def organization_owners(request, organization):
"""Return all users who are owners of the organization."""
owner_roles = (
request.db.query(User.id)
.join(OrganizationRole.user)
.filter(
OrganizationRole.role_name == OrganizationRoleType.Owner,
OrganizationRole.organization == organization,
)
.subquery()
)
return request.db.query(User).join(owner_roles, User.id == owner_roles.c.id).all()


@view_defaults(
route_name="manage.organizations",
renderer="manage/organizations.html",
Expand Down Expand Up @@ -1395,77 +1410,79 @@ def revoke_organization_invitation(organization, request):


@view_config(
route_name="manage.project.change_role",
context=Project,
route_name="manage.organization.change_role",
context=Organization,
uses_session=True,
require_methods=["POST"],
permission="manage:project",
# permission="manage:organization",
has_translations=True,
require_reauth=True,
)
def change_project_role(project, request, _form_class=ChangeRoleForm):
form = _form_class(request.POST)
def change_organization_role(
organization, request, _form_class=ChangeOrganizationRoleForm
):
form = _form_class(request.POST, orgtype=organization.orgtype)

if form.validate():
role_id = request.POST["role_id"]
try:
role = (
request.db.query(Role)
request.db.query(OrganizationRole)
.join(User)
.filter(Role.id == role_id, Role.project == project)
.filter(
OrganizationRole.id == role_id,
OrganizationRole.organization == organization,
)
.one()
)
if role.role_name == "Owner" and role.user == request.user:
if (
role.role_name == OrganizationRoleType.Owner
and role.user == request.user
):
request.session.flash("Cannot remove yourself as Owner", queue="error")
else:
request.db.add(
JournalEntry(
name=project.name,
action="change {} {} to {}".format(
role.role_name, role.user.username, form.role_name.data
),
submitted_by=request.user,
submitted_from=request.remote_addr,
)
)
role.role_name = form.role_name.data
project.record_event(
tag="project:role:change",
organization.record_event(
tag="organization:role:change",
ip_address=request.remote_addr,
additional={
"submitted_by": request.user.username,
"submitted_by_user_id": str(request.user.id),
"role_name": form.role_name.data,
"target_user": role.user.username,
"target_user_id": str(role.user.id),
},
)

owner_users = set(project_owners(request, project))
owner_users = set(organization_owners(request, organization))
# Don't send owner notification email to new user
# if they are now an owner
owner_users.discard(role.user)
send_collaborator_role_changed_email(
request,
owner_users,
user=role.user,
submitter=request.user,
project_name=project.name,
role=role.role_name,
)

send_role_changed_as_collaborator_email(
request,
role.user,
submitter=request.user,
project_name=project.name,
role=role.role_name,
)
# TODO: Send notification emails.
# send_member_role_changed_email(
# request,
# owner_users,
# user=role.user,
# submitter=request.user,
# organization_name=organization.name,
# role=role.role_name,
# )
#
# send_role_changed_as_member_email(
# request,
# role.user,
# submitter=request.user,
# organization_name=organization.name,
# role=role.role_name,
# )

request.session.flash("Changed role", queue="success")
except NoResultFound:
request.session.flash("Could not find role", queue="error")

return HTTPSeeOther(
request.route_path("manage.project.roles", project_name=project.name)
request.route_path(
"manage.organization.roles", organization_name=organization.name
)
)


Expand Down
7 changes: 7 additions & 0 deletions warehouse/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,13 @@ def includeme(config):
traverse="/{organization_name}",
domain=warehouse,
)
config.add_route(
"manage.organization.change_role",
"/manage/organization/{organization_name}/people/change/",
factory="warehouse.organizations.models:OrganizationFactory",
traverse="/{organization_name}",
domain=warehouse,
)
config.add_route("manage.projects", "/manage/projects/", domain=warehouse)
config.add_route(
"manage.project.settings",
Expand Down
4 changes: 2 additions & 2 deletions warehouse/templates/manage/organization/roles.html
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ <h2>{% trans %}People{% endtrans %}</h2>
{% trans %}Billing Manager{% endtrans %}
{% endif %}
{% else %}
<form class="table__change-role" method="POST" action="#" {# TODO action="{{ request.route_path('manage.organization.change_role', organization_name=organization.name) }}" #}>
<form class="table__change-role" method="POST" action="{{ request.route_path('manage.organization.change_role', organization_name=organization.name) }}">
<input name="csrf_token" type="hidden" value="{{ request.session.get_csrf_token() }}">
<input type="hidden" name="role_id" value="{{ role.id }}">

Expand All @@ -99,7 +99,7 @@ <h2>{% trans %}People{% endtrans %}</h2>
{# Reuse `.role_name.choices` from "Invite" form to include/exclude "Billing Manager" appropriately. #}
{% for role_name, role_name_label in form.role_name.choices if role_name %}
{% set role_name_label = gettext(role_name_label) %}
<option value="{{ role_name }}" {{ 'selected' if role_name == role.role_name else '' }}>
<option value="{{ role_name }}" {{ 'selected' if role_name == role.role_name.value else '' }}>
{{ role_name_label }}
</option>
{% endfor %}
Expand Down

0 comments on commit 9c14f9b

Please sign in to comment.