fix(account): one form and one Save for your account; the same for an account's page in Users
CI / lint (push) Successful in 2s
CI / integration (push) Failing after 1m8s
CI / unit (push) Successful in 1m36s
CI / publish (push) Skipped

Operator review of the Account page: two sections with two different
save buttons, one stuck to the next card and one beside a squeezed hint.
- Account: time zone and Email are one form; POST /account/ saves both,
  nothing unless all of it is valid. /account/email is gone.
- Users › account: Name and Email are one form; the rename route also
  saves the email switches. /settings/users/<id>/email is gone.
- The Email section is a plain section now (email_section); its hint sits
  under the switches, names the zone as the picker does, and links
  Notifications only for an admin.
- apply_email_form leaves a switch the form doesn't carry as it is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 16:05:41 -04:00
co-authored by Claude Opus 5.5
parent 31e32c173c
commit beae8a33f3
8 changed files with 102 additions and 106 deletions
+22 -29
View File
@@ -1,5 +1,6 @@
"""The signed-in user's own account (milestone 490): the time zone every
page writes its times in. Any role reaches it, for themselves only."""
page writes its times in, and their email (milestone 495). Any role reaches
it, for themselves only."""
from __future__ import annotations
from quart import Blueprint, abort, current_app, redirect, render_template, request, session
@@ -26,48 +27,34 @@ async def _me(db) -> User:
@account_bp.get("/")
@require_role(UserRole.viewer)
async def account(error: str | None = None, status: int = 200, email_error: str | None = None):
async def account(error: str | None = None, status: int = 200):
from steward.core.mail import role_defaults
from steward.core.settings import get_all_settings
async with current_app.db_sessionmaker() as db:
user = await _me(db)
defaults = role_defaults(user, await get_all_settings(db))
tz = formatting.zone(user.time_zone)
zone_name = user.time_zone or "UTC"
return await render_template(
"account/index.html", user=user, source=_SOURCES.get(user.auth_source, user.auth_source),
zone_name=user.time_zone or "UTC", zone_label=formatting.tz_label(tz),
zones=formatting.zones_by_region(), defaults=defaults, error=error,
email_error=email_error), status
zone_name=zone_name, zone_title=zone_name.split("/", 1)[-1].replace("_", " "),
zone_label=formatting.tz_label(tz), zones=formatting.zones_by_region(),
defaults=defaults, error=error), status
@account_bp.post("/")
@require_role(UserRole.viewer)
async def account_save():
from steward.core.audit import log_audit
name = ((await request.form).get("time_zone") or "").strip()
if name not in formatting.zone_names():
return await account(f"{name or 'That'} is not a time zone Steward knows.", 400)
async with current_app.db_sessionmaker() as db:
async with db.begin():
user = await _me(db)
before = user.time_zone
user.time_zone = name
session["tz"] = name
if before != name:
await log_audit(current_app, user.id, user.username, "account.time_zone",
detail={"time_zone": name, "was": before or "UTC"})
return redirect("/account/")
@account_bp.post("/email")
@require_role(UserRole.viewer)
async def account_email():
"""Your address and whether you're emailed alerts and the weekly report
(milestone 495)."""
"""The page's one form: your time zone, your address and whether you're
emailed alerts and the weekly report (milestone 495). Nothing is saved
unless all of it is valid."""
from sqlalchemy.exc import IntegrityError
from steward.core.audit import log_audit
from steward.core.mail import apply_email_form
form = await request.form
name = (form.get("time_zone") or "").strip()
if name not in formatting.zone_names():
return await account(f"{name or 'That'} is not a time zone Steward knows.", 400)
try:
async with current_app.db_sessionmaker() as db:
async with db.begin():
@@ -75,13 +62,19 @@ async def account_email():
changes, error = apply_email_form(user, form)
if error:
raise ValueError(error)
before = user.time_zone
user.time_zone = name
except ValueError as exc:
return await account(status=400, email_error=str(exc))
return await account(str(exc), 400)
except IntegrityError:
return await account(status=400, email_error="Another account already uses that address.")
return await account("Another account already uses that address.", 400)
session["tz"] = name
if before != name:
await log_audit(current_app, user.id, user.username, "account.time_zone",
detail={"time_zone": name, "was": before or "UTC"})
if changes:
await log_audit(current_app, user.id, user.username, "account.email", detail=changes)
return redirect("/account/#email")
return redirect("/account/")
@account_bp.post("/time-zone/adopt")