Several postal addresses per account, one primary — but not unique across the instance #92
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Reference
Postulo/postulo#92
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Observation
Companion to #90, which does the same for telephone numbers.
What exists
There is no postal address in Postulo at all. What looks like one is not:
One free-text line, 120 characters, and its help text says exactly what it is for: the line
a CV header prints.
CV.show_contact_detailsdescribes it as "Your name, email andlocation, taken from your profile". It is a display string, not an address, and it was
right to be one.
Postulo already throws a real address away
tests/data/europass.json, which the importer added in #69 reads today:and
resume/europass.pykeeps this much of it:The street and the postcode are discarded on import. Somebody exports a Europass CV,
imports it here, and the two lines that make an address an address are silently gone. That
is the concrete cost of not having somewhere to put them, and it is happening now.
What this asks for
A
PostalAddressmodel owned by a person: several per account, exactly one primary, theinvariant enforced by a database constraint rather than by a form.
Where it deliberately differs from #90
No uniqueness across the instance, and this is the important difference. #90 asks for
telephone numbers to be unique platform-wide, which is defensible because a mobile number
belongs to one person. A postal address does not. Spouses share one. Flatmates share one.
An adult child living at home shares one. Two siblings on a family instance share one, and a
family instance is exactly the kind of small self-hosted deployment this project is built
for.
A uniqueness constraint here would refuse the second member of a household their own
address, and the refusal would also disclose that somebody else on the server lives there --
the same leak #90 has to reason about, but with no compensating reason to accept it.
So: unique per owner, so one person cannot list the same address twice, and freely shared
between accounts.
"Valid" cannot mean "verified". Deciding whether an address exists needs a per-country
reference database or a paid lookup service, which is a network dependency, a cost and a
stream of updates.
phones.pyalready refuses the equivalent for telephone numbers and sayswhy: "Postulo has no use for the answer: it is not going to dial anything." Nor is it going
to post anything. Valid here can only mean well-formed enough to be used, and an address
somebody types oddly must still be saved exactly as typed.
The shape of the model
Europass gives the fields to copy, and they are the ones every format agrees on:
address line, postcode, municipality, region, country code. Beyond that, address formats
differ by country -- where the postcode goes, whether a region is named at all -- so the
model stores the parts and the rendering decides the order, per country, or falls back to
the order they were entered in.
What the primary is for, and what it is not for
The primary address is what a form or a letter uses when one is needed. A formal cover
letter carries the sender's address; a job application form asks for one.
It is not for the CV header. Guidance in most of Europe is that a CV should carry a city
and country and not a street -- partly because it is irrelevant and partly because a precise
address invites the reader to draw conclusions about somebody from where they live. So
Profile.locationstays as it is: its own overridable line, defaulting from the primaryaddress's municipality and country, never printing the street. Anything else would quietly
put people's home addresses on documents they send to strangers.
What else has to move
core/export.py--PROFILE_FIELDSlistslocation; the new table has to be in thearchive, or taking your data out stops being complete.
tests/security/-- a new table holding home addresses gets the ownership sweep, likeevery table that holds a personal detail.
docs/THREAT-MODEL.md-- a home address is among the most sensitive things thisapplication would hold, and the document should say so rather than have it appear quietly.
resume/europass.py-- import stops discarding the street and the postcode, which iswhat makes this worth doing rather than merely tidy.
Classification
Enhancement. Not breaking:
Profile.locationkeeps working and keeps its meaning; this addssomewhere for the parts of an address that currently have nowhere to go.
Depends on nothing, but sits beside #90 (telephone numbers): same shape, deliberately different uniqueness rule. Whichever is built first should establish the add / remove / make-primary pattern in Settings → Your details, and the other should follow it rather than invent a second one.
#146 proposes one contract for every kind of contact detail — how it is shaped, whether it can be proved, and which connector proves it — with email, telephone and postal addresses as the three instances.
Postal is the one that makes the contract honest: it can be validated and cannot be confirmed, short of posting something to it. That is a legitimate answer rather than a missing feature, and this issue is the reason the contract has to allow it. Nothing here is blocked by it — this issue can be built first and described by the contract afterwards — but the not unique across the instance decision in the title is a second axis the contract should carry too, since telephone numbers went the other way.
Two things arrive on top of this issue, and neither changes what is written above.
It is an internal plugin, like telephone numbers (#90). That brings the plugin rules with it: #126 for what it may import, #127 for its own catalogues, #128 for what happens to a table of home addresses when the plugin goes, and #129 for the packaging. Note that #128 matters more here than for any plugin so far — this table holds the most sensitive thing Postulo would store, and uninstall and keep the table and uninstall and delete it are very different promises to make about somebody's home address.
Per-country validation is #147, which takes the paragraph here about formats differing by country and makes it the subject: required fields, postcode shapes, what each field is called, and where the data comes from. The not unique across the instance decision in this issue's title stands and is not revisited there.
PostalAddress, owned, several per account, exactly one primary — with the invariant in adatabase constraint rather than in whichever form saved last. On a person and on a contact,
through the same generic relation the telephone numbers use.
The difference from #90 is the whole point, and it is enforced rather than intended.
Unique per owner; freely shared between accounts. Two accounts holding one address is a
test rather than an accident:
Both halves are covered — the second person at one address succeeds, and one person listing
their own home twice is refused.
Valid does not mean verified, for the reason
phones.pyalready gives about dialling:Postulo is not going to post anything, so it has no way to learn whether an address exists
and no use for the answer. An address typed oddly is saved exactly as typed; only the
comparison folds case and interior spacing, and
Rua do Exemplo 1Ais a different addressfrom
Rua do Exemplo 1. Nothing here carries a verification, so nothing here can become away back into an account — asserted on the field list rather than on behaviour.
Postulo has stopped throwing real addresses away
The part the issue said makes this worth doing rather than tidy. The Europass importer kept
the town and the country and discarded the street and the postcode, because there was nowhere
to put them. They land now, filling blanks only — an import never argues with an address
somebody typed.
One thing surfaced doing it:
tests/data/europass.xmlhad no street at all, while the JSONfixture beside it did, and the two are meant to be one person. Invisible while both were
discarded; the XML now carries the same address.
Everything else the issue listed
core/export.py— format 9,postal_addresseson a profile and on a contact, andthe importer reads them back. No skipping on import, because an archive cannot collide with
somebody else's address — only with another row in the same file.
PostalAddressis anOwnedModel, so the sweep that walks everyone of them covers it by construction.
docs/THREAT-MODEL.md— a new section saying a home address is the sharpest thing thisapplication holds, with the three rules that follow: it is owner-scoped, it is deliberately
not unique, and a CV header prints a town and a country and never a street.
Profile.locationis untouched and still its own overridable line.postal.location_line()is only what it can default from, and a test asserts the streetnever appears in it.
What is not here
Per-country rules — which fields are required, what a postcode looks like, what each field is
called, what order they print in. That is #147, which extends this, and the neutral labels
on the form today are what it replaces. The form structure will not change; the words will.
17 tests, 18 strings in all 39 European catalogues. Shipped in
5e05e74on0.3.0, withmainkept level.