diff --git a/emails/team_rename.spt b/emails/team_rename.spt new file mode 100644 index 0000000000..a460f651f6 --- /dev/null +++ b/emails/team_rename.spt @@ -0,0 +1,4 @@ +{{ _("A team you're a member of has been renamed") }} + +[---] text/html +
{{ _("The team “{0}” has been renamed to “{1}” by {2}.", old_name, new_name, renamed_by) }}
diff --git a/liberapay/models/participant.py b/liberapay/models/participant.py index 13df6b993d..989925d6e1 100644 --- a/liberapay/models/participant.py +++ b/liberapay/models/participant.py @@ -123,7 +123,7 @@ def make_active(cls, kind, username=None, password=None, cursor=None): RETURNING participants.*::participants """.format(cols, placeholders), vals) if username: - p.change_username(username, c) + p.change_username(username, cursor=c) return p def make_team(self, name, email=None, email_lang=None, throttle_takes=True): @@ -143,7 +143,7 @@ def make_team(self, name, email=None, email_lang=None, throttle_takes=True): VALUES ('group', 'active', now(), %s) RETURNING participants.*::participants """, (throttle_takes,)) - t.change_username(name, c) + t.change_username(name, cursor=c) t.add_member(self, c) if email: t.set_email_lang(email_lang, cursor=c) @@ -1280,7 +1280,7 @@ def pay_invoice(self, invoice): # More Random Stuff # ================= - def change_username(self, suggested, cursor=None): + def change_username(self, suggested, cursor=None, recorder=None): suggested = suggested and suggested.strip() if not suggested: @@ -1316,11 +1316,11 @@ def change_username(self, suggested, cursor=None): if actual is None: return suggested assert (suggested, lowercased) == actual # sanity check - c.hit_rate_limit('change_username', self.id, TooManyUsernameChanges) # Deal with redirections last_rename = self.get_last_event_of_type('set_username') if last_rename: + c.hit_rate_limit('change_username', self.id, TooManyUsernameChanges) old_username = last_rename.payload prefixes = { 'old': '/%s/' % old_username.lower(), @@ -1334,21 +1334,38 @@ def change_username(self, suggested, cursor=None): , mtime = now() WHERE to_prefix = %(old)s; """, prefixes) - # Add a redirection if the old name was in use long enough (1 hour) - active_period = utcnow() - last_rename.ts - if active_period.total_seconds() > 3600: - c.run(""" - INSERT INTO redirections - (from_prefix, to_prefix) - VALUES (%(old)s || '%%', %(new)s) - ON CONFLICT (from_prefix) DO UPDATE - SET to_prefix = excluded.to_prefix - , mtime = now() - """, prefixes) + if prefixes['old'] != prefixes['new']: + # Add a redirection if the old name was in use long enough (1 hour) + active_period = utcnow() - last_rename.ts + if active_period.total_seconds() > 3600: + c.run(""" + INSERT INTO redirections + (from_prefix, to_prefix) + VALUES (%(old)s || '%%', %(new)s) + ON CONFLICT (from_prefix) DO UPDATE + SET to_prefix = excluded.to_prefix + , mtime = now() + """, prefixes) self.add_event(c, 'set_username', suggested) self.set_attributes(username=suggested) + if last_rename and self.kind == 'group': + assert isinstance(recorder, Participant) + members = self.db.all(""" + SELECT p + FROM current_takes t + JOIN participants p ON p.id = t.member + WHERE t.team = %s + """, (self.id,)) + for m in members: + if m != recorder: + m.notify( + 'team_rename', email=False, web=True, + old_name=old_username, new_name=suggested, + renamed_by=recorder.username, + ) + return suggested def update_avatar(self, src=None, cursor=None): diff --git a/tests/py/test_settings.py b/tests/py/test_settings.py index 42b7ac78a1..44b1d9065d 100644 --- a/tests/py/test_settings.py +++ b/tests/py/test_settings.py @@ -97,3 +97,21 @@ def test_too_long(self): r = self.change_username(username) assert r.code == 400 assert "The username '%s' is too long." % username in r.text, r.text + + def test_change_team_name(self): + team = self.make_participant(None, kind='group') + team.change_username('team') + alice = self.make_participant('alice') + team.add_member(alice) + bob = self.make_participant('bob') + team.add_member(bob) + r = self.client.POST('/team/settings/edit', {'username': 'Team'}, + auth_as=alice, raise_immediately=False) + assert r.code == 302 + assert r.headers[b'Location'] == b'/Team/edit' + team = team.refetch() + assert team.username == 'Team' + alice = alice.refetch() + assert alice.pending_notifs == 0 + bob = bob.refetch() + assert bob.pending_notifs == 1 diff --git a/www/%username/settings/edit.spt b/www/%username/settings/edit.spt index eb2b9291ef..4327486bb3 100644 --- a/www/%username/settings/edit.spt +++ b/www/%username/settings/edit.spt @@ -9,11 +9,11 @@ request.allow('POST') body = request.body out = {} -p = get_participant(state, restrict=True) -if user != p: - raise response.error(403) +p = get_participant(state, restrict=True, allow_member=True) if 'new-password' in body: + if not p.is_person: + raise response.error(403) if p.session_token and p.session_token.endswith('.em'): pass # user logged in via email, allow resetting password elif not p.password: @@ -28,6 +28,8 @@ if 'new-password' in body: out['msg'] = _("Your password has been changed.") elif 'privacy' in body: + if not p.is_person: + raise response.error(403) fields = body['privacy'].split() for field in fields: if field not in PRIVACY_FIELDS: @@ -44,7 +46,7 @@ elif 'privacy' in body: out['msg'] = _("Your privacy settings have been changed.") elif 'username' in body: - p.change_username(body['username']) + p.change_username(body['username'], recorder=user) response.redirect(p.path('edit')) if request.headers.get(b'X-Requested-With') != b'XMLHttpRequest':