Build M6 Edit personal info for the mobile app

The last of the seven Member-mode screens. A real editable form for the
fields Member actually has (first/last name, date of birth, email,
phone, emergency phone) -- the design mock's national-register-number,
address, allergies/notes and consent-toggle rows have no backing field
and are omitted rather than added as new schema for a screen-building
pass. Two mock rows do have real data and are shown read-only instead:
every guardian via Member.guardians ("Emergency contact"), and any open
onboarding requirement for the person's current-season membership (via
club.services.onboarding.checklist_for) as a banner -- no upload/complete
action, that stays staff-only elsewhere in the platform.

Authorization: the target Member (from the URL) must be one of the
signed-in account's managed_people, checked on both GET and POST -- an
unmanaged id 404s, same as another club's Event/News already does
elsewhere in this app.

This completes M1-M7 (Home, Event detail, Calendar, News article, Me &
my people, Edit personal info, Notifications). Coach mode (C1-C6) is a
separate, later phase per the design doc and is intentionally not
started here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
2026-08-21 14:11:56 +02:00
parent 19e1acb93a
commit 2cdba6a571
6 changed files with 364 additions and 5 deletions

39
mobile/forms.py Normal file
View File

@@ -0,0 +1,39 @@
from django import forms
from members.models import Member
#: Shared by every text-ish field below -- mobile has no equivalent of
#: management/controlpanel's templatetags/field.html (which builds widget
#: classes at render time for their daisyUI-shaped inputs), so this app's one
#: form so far just bakes its own classes straight into the widget.
_INPUT_CLASSES = "h-11 w-full rounded-lg border border-stroke bg-paper px-3 text-[15px] text-ink placeholder:text-dim focus:border-ink focus:outline-none"
class MemberProfileForm(forms.ModelForm):
"""M6 -- "Edit personal info" (design_handoff_rosterchief_platform/README.md).
Covers exactly the fields ``members.models.Member`` actually has. The
design mock also shows a "National register no.", an "Address", an
"Allergies / notes" field and two "Consent" toggles -- none of those have
a backing field on ``Member`` (see EditProfileView's own docstring), so
they're simply not part of this form rather than being invented here.
Same field list and date widget as ``management.forms.MemberForm`` (the
staff-side equivalent editing the same model) -- diverging widget
conventions across the platform for identical fields would be its own bug.
"""
class Meta:
model = Member
fields = ["first_name", "last_name", "date_of_birth", "email", "phone", "emergency_phone"]
# Same date widget as management.forms.MemberForm. phone/emergency_phone
# deliberately keep django-phonenumber-field's own RegionalPhoneNumberWidget
# (national-format display, region-aware parsing) rather than being
# swapped for a plain TextInput here -- __init__ below only adds a CSS
# class to whatever widget each field already has, never replaces it.
widgets = {"date_of_birth": forms.DateInput(attrs={"type": "date"})}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs["class"] = _INPUT_CLASSES