04ccdbf96a
Names (the genealogy standard: maiden name primary, married/alias as typed
alternates):
- Name model already supported multiple typed names; expose full CRUD —
NameCreate/Read/Update schemas, name_service (one-primary invariant,
promote-on-delete), nested /persons/{id}/names routes.
- Person page gains a Names card: add/edit/delete + "make primary", with a
curated name_type dropdown (birth/maiden, married, alias, nickname, …).
Self-person ("who am I"):
- users.self_person_id FK (use_alter for the users<->persons<->trees cycle)
+ migration; PATCH /users/me/self-person; "This is me" / "This is you"
on the person page. Soft-deleting the linked person clears it.
Deletion integrity (fixes the broken tree view):
- delete_person now soft-deletes the relationships touching the person, so no
dangling edges remain; family-chart also filters links to missing people.
- Optional cascade=true recursively deletes descendants (GEDCOM cleanup);
the person page asks "only this person" vs "with all descendants".
- DELETE returns {deleted: n}.
Family view surfaces "Not connected to anyone" so dangling people aren't lost.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
93 lines
3.3 KiB
Python
93 lines
3.3 KiB
Python
"""Multiple typed names per person: maiden (primary) + married/alias alternates."""
|
|
|
|
from tests.conftest import auth, register
|
|
|
|
|
|
async def _setup(client, email):
|
|
h = auth(await register(client, email))
|
|
tid = (await client.post("/api/v1/trees", json={"name": "T"}, headers=h)).json()["id"]
|
|
pid = (
|
|
await client.post(
|
|
f"/api/v1/trees/{tid}/persons", json={"given": "Mary", "surname": "Smith"}, headers=h
|
|
)
|
|
).json()["id"]
|
|
return h, tid, pid
|
|
|
|
|
|
async def test_create_lists_and_primary(client):
|
|
h, tid, pid = await _setup(client, "n-create@example.com")
|
|
base = f"/api/v1/trees/{tid}/persons/{pid}/names"
|
|
|
|
# The person was created with a primary birth name.
|
|
names = (await client.get(base, headers=h)).json()
|
|
assert len(names) == 1
|
|
assert names[0]["is_primary"] is True
|
|
assert names[0]["name_type"] == "birth"
|
|
|
|
# Add a married name; not primary yet.
|
|
r = await client.post(
|
|
base, json={"name_type": "married", "given": "Mary", "surname": "Jones"}, headers=h
|
|
)
|
|
assert r.status_code == 201
|
|
assert r.json()["is_primary"] is False
|
|
|
|
names = (await client.get(base, headers=h)).json()
|
|
assert len(names) == 2
|
|
# Primary first.
|
|
assert names[0]["surname"] == "Smith" and names[0]["is_primary"] is True
|
|
|
|
|
|
async def test_set_primary_demotes_others(client):
|
|
h, tid, pid = await _setup(client, "n-primary@example.com")
|
|
base = f"/api/v1/trees/{tid}/persons/{pid}/names"
|
|
married = (
|
|
await client.post(
|
|
base, json={"name_type": "married", "given": "Mary", "surname": "Jones"}, headers=h
|
|
)
|
|
).json()
|
|
|
|
r = await client.patch(f"{base}/{married['id']}", json={"is_primary": True}, headers=h)
|
|
assert r.status_code == 200 and r.json()["is_primary"] is True
|
|
|
|
names = {n["surname"]: n["is_primary"] for n in (await client.get(base, headers=h)).json()}
|
|
assert names == {"Jones": True, "Smith": False}
|
|
|
|
# The person's display name now reflects the new primary.
|
|
person = (
|
|
await client.get(f"/api/v1/trees/{tid}/persons/{pid}", headers=h)
|
|
).json()
|
|
assert person["primary_name"] == "Mary Jones"
|
|
|
|
|
|
async def test_update_fields(client):
|
|
h, tid, pid = await _setup(client, "n-update@example.com")
|
|
base = f"/api/v1/trees/{tid}/persons/{pid}/names"
|
|
nid = (
|
|
await client.post(base, json={"name_type": "alias", "given": "Polly"}, headers=h)
|
|
).json()["id"]
|
|
r = await client.patch(
|
|
f"{base}/{nid}", json={"surname": "Smith", "nickname": "Poll"}, headers=h
|
|
)
|
|
assert r.status_code == 200
|
|
assert r.json()["surname"] == "Smith" and r.json()["nickname"] == "Poll"
|
|
|
|
|
|
async def test_delete_promotes_new_primary(client):
|
|
h, tid, pid = await _setup(client, "n-delete@example.com")
|
|
base = f"/api/v1/trees/{tid}/persons/{pid}/names"
|
|
alt = (
|
|
await client.post(
|
|
base, json={"name_type": "married", "given": "Mary", "surname": "Jones"}, headers=h
|
|
)
|
|
).json()["id"]
|
|
|
|
# Delete the (primary) birth name; the married name should be promoted.
|
|
primary = next(
|
|
n for n in (await client.get(base, headers=h)).json() if n["is_primary"]
|
|
)
|
|
r = await client.delete(f"{base}/{primary['id']}", headers=h)
|
|
assert r.status_code == 204
|
|
|
|
names = (await client.get(base, headers=h)).json()
|
|
assert len(names) == 1 and names[0]["id"] == alt and names[0]["is_primary"] is True
|