Your details: nationalities, one or several, or only whether you are a citizen of the EU, the EEA or Switzerland #680
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#680
Loading…
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?
Your details has no nationality. The maintainer's list asks for it, with more than one allowed, and with a way to say less: "Add nationality option, including multiple nationality. Add the possibility to just list as European/non-European citizen." It is optional, never worked out from a name, an address, a language or a phone number, and printed on a CV only where a CV is set to print it (#308, #309).
What exists today
Profileor anywhere else insrc(src/postulo/accounts/models.py:265-295holds the name's companions, headline and location only).core/phones.pyCOUNTRIES: 241 rows of ISO 3166-1 alpha-2 code, dialling prefix and English name, including territories (Aruba, Guam, Åland, Kosovo, Taiwan), andphones.BY_CODE,country_choices()andcountry_name()read it (core/phones.py:89,:333,:399-419). The address form's country menu and the flag tag use it (core/postal.py:355;core/flags.py,{% flag %}). Names are in English by design and are not translated.accounts/forms.py:380,accounts/views.py_section_nav),PROFILE_FIELDSandCANDIDATE_PROFILE_FIELDS(core/export.py:111,:143),FORMAT_VERSION(:80; 29 atf09b6010b) andCANDIDATE_FORMAT(:104; 4),ProfileOutandProfilePatch(api/schemas.py:891,:940),printing.SWITCHESand theshow_*columns ofCV(documents/printing.py:156,documents/models.py:316-326), and thecontactdict handed to every renderer (documents/rendering.py:218-229).hidden_nav_items,nav_orderanddashboard_widgetsare JSON lists of keys, each read back through a function that believes only what it can check (core/navigation.pyknown_keys). A person's rows that need their own table are the phone numbers, addresses, links and identifiers, each with a label, a kind and a primary, which a nationality has none of.resume/candidate.py:750-790,_details), and a value that is not text is a refused row (_not_text()); a list needs its own reading.core/importer.py:478-482); see #679 for the consequence.NationalityCodeper nationality directly underPerson, ISO 3166-1 alpha-2, “multiple entries of this code list are allowed” (BR-COM-43;tests/data/europass-candidate-sample.xml:90-94), which is the shape proposed here; the reader takes none (plugins/europass/candidate.py:345-398). vCard 4 has no nationality property, so the person's own card (#660) has nothing to write it to.core/languages.py, #118) and for NACE (jobs/industries.py).Checked against
mainatf09b6010b.Proposal
Profile: a JSON list of two-letter codes fromphones.COUNTRIES, in the order the person gave them, each once, up to ten; a code that is not on the list is refused by the form and the API, and dropped with a note by a file. The form draws a menu for each nationality held and one empty one, as the row blocks drawextra=1, each a Basecoat select with the flag beside the country (#301), the native select being the scripts-off path (saving adds the next empty one); it sits in the card #679 makes.nationality_scope, with three values: not said (blank), “Citizen of an EU or EEA country, or of Switzerland”, and “Citizen of another country”. It is the answer only while the list is empty; with countries listed the scope is derived from them and the stored value is cleared on save, so the two can never disagree. The EU, EEA and Switzerland set is one constant in a module of its own (27 EU members, Iceland, Liechtenstein and Norway, and Switzerland), with where each part comes from in the comment, and a test that every code in it is inphones.COUNTRIES.show_nationalityswitch onCVinprinting.SWITCHES, so the form, the API and the importer pick it up; thecontactdict gainsnationalities(names) andnationality_scope; the base themes, the plain text and the Word file print one line, “Nationality: Portugal, Brazil”, or the scope's wording when no country is listed.PROFILE_FIELDSandCANDIDATE_PROFILE_FIELDSgain both columns; each bumps its format by one from whatever it is when the commit lands, with its line in the history comment. A candidate file's list is read code by code: each is validated, each becomes a review row (add, or already there), and nothing a person has is replaced.ProfileOutandProfilePatchtakenationalities(a list of codes) andnationality_scope, with the page's rules and a 422 naming the field;CVPrintsOut/CVPrintsIngain the boolean.Decisions
OwnedModelrows would be a model, a migration, a formset, a removal dialog (#303) and an isolation test for a list of codes. Recommended: a JSON list on the profile, validated at every door by one function, as the navigation lists are.phones.COUNTRIESwas made for dialling prefixes. It has territories, which are not nationalities, and no code for stateless people or refugees (ISO 3166 has none; ICAO's travel-document list usesXXA,XXB,XXCfor these, not verified here). Recommended: reuse it; it already drives the address menu and the flags, and one list is one thing to keep. A stateless person leaves the list empty and may choose the scope; if the maintainer wants the case named it is a later, separate row in the menu with its own wording.location_linealready does (#309 recorded “München, Germany” on a French CV as the same limitation), under a translated label; revisit with #689.docs/THREAT-MODEL.md.Builds on
#679 (the card, the switches, the format bumps, the validator pattern), #309 and #308 (optional, never inferred, off by default), #181 (the candidate file), #301 (Basecoat's select with a flag beside each country), #306 (postal addresses and the country list). #689 for names in the document's language.
Done when
tests/test_nationalities.py: the validator (known codes, case folded, duplicates removed, order kept, an unknown code and more than ten refused, a non-list refused); the scope is cleared when countries are listed and derived as “EU, EEA or Switzerland” forPT,NOandCHand not forGBorBR; every code of the set is inphones.COUNTRIES; the form saves, adds a second and clears; nothing prints by default and the switch prints the line in the preview, the plain text and the Word file.tests/test_cv_prints.pyandtests/security/test_cv_prints.pycover the switch; the archive round trip andtests/test_export.pycarry both columns at the new format;tests/test_candidate_file.pyholds the new fingerprint and a review row per listed code, andtests/security/test_candidate_file.pya list that is the wrong type, too long, or full of unknown codes; the archive importer drops a bad list with a report line (tests/test_importers.py).tests/test_api.pycovers the two fields onGETandPATCH /profile, including the 422 for an unknown code;tests/test_profile_sections.pyis unchanged because the card is #679's. Search is unaffected: nothing here is indexed (core/search.pyhas no profile group), and a test says so for the new fields.tests/test_account_deletion.py,tests/security/test_isolation_sweep.py). The record of processing needs no line (core/gdpr.pydraws it from plugins and connections), as in #679.docs/THREAT-MODEL.mdchange in the same commit.From the list of improvements of 2026-10-02.