Your details: gender, in your own words, optional, and never printed or inferred unless you say so #681
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#681
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 gender field. The maintainer's list asks for one ("Add gender"). It sits beside the form of address and the pronouns that #309 added, and follows the same promises: the person gives it in their own words or not at all, nothing is ever worked out from a name, an address or any other field, and no document prints it until a CV is set to.
What exists today
src/postulo/accounts/models.py:265-269).accounts/addressing.pysays "neither is ever worked out from a name", keeps each list per language as data and not as translations (FORMS_OF_ADDRESS,PRONOUNS: English, French, European and Brazilian Portuguese), stores "the text itself" and not a key into a list, and gives a language with no list only Other (accounts/addressing.py:1-105, #309).data-if-other), so it behaves the same with scripts off; the menu wins unless it says Other…; the options carry their language'slangand the boxesdir="auto"(accounts/forms.py:ProfileFormat:380, its_offerat:516;templates/accounts/profile.html:87-145). The blank choice reads “Not stated”.src. The wiki,docs/and the README do not mention gender (searched).PROFILE_FIELDSandCANDIDATE_PROFILE_FIELDS(core/export.py:111,:143),FORMAT_VERSION(:80; 29 atf09b6010b) andCANDIDATE_FORMAT(:104; 4),ProfileOutandProfilePatchwith the 40-character_ADDRESSINGbound (api/schemas.py:888-960), theshow_*switches onCVandprinting.SWITCHES(documents/printing.py:156;documents/models.py:316-326), and thecontactdict (documents/rendering.py:218-229)._detailscleans any string column through its own form field (resume/candidate.py:750-790).GENDER, whose value is a sex component (M,F,O,NorU, or empty) and, after a semicolon, free text for the identity: the grammar issex [";" text]withsexallowed to be empty (RFC 6350 section 6.2.7). The Europass Candidate XML the importer is tested with carriesGenderCodedirectly underPerson, a value of HR-Open Standards'GenderEnumType(the sample holds “Male”; rule BR-CV-10 in its comments;tests/data/europass-candidate-sample.xml:98-100); the reader here takes none (plugins/europass/candidate.py:345-398).sex("gender identity, free-form text"), which WCAG 2.1 SC 1.3.5 lists among the input purposes a field may identify itself with.Checked against
mainatf09b6010b.Proposal
gendercolumn onProfile: optional text of at most 40 characters, the person's own words, exactly as the form of address is stored. Blank means nothing is said; nothing is assumed.addressing.forms_of_addresspicks), and Other… with its box, using the machinery ofProfileForm._offerand thedata-if-otherrule, so it works with scripts off. A short list per language, inaccounts/addressing.py: English “Woman”, “Man”, “Non-binary”; French and the two Portuguese equivalents; each list with its source named in the module docstring, as the others are, and a language with no list offers Other alone.autocomplete="sex"on the field.show_genderonCV, off, inprinting.SWITCHES, so the form, the API and the importer pick it up; thecontactdict gainsgender; the base themes, the plain text and the Word file print it as one detail (“Gender: …”) among the personal ones, and a plugin theme sees a key it may ignore.PROFILE_FIELDSandCANDIDATE_PROFILE_FIELDS; each format is one more than it is when the commit lands, with its history line. A candidate file's value is read "filled where blank, kept where not", like the form of address.ProfileOutandProfilePatchtake it with the page's rules (stripped, held to 40, a NUL is a 422 naming the field);CVPrintsOut/CVPrintsIngain the boolean.GENDERor to Europass. They belong with #660, and the mapping rule is recorded below so it is not decided by accident.Decisions
docs/TRANSLATING.md).M/F/O/N/U) has nothing to take straight from a stored word.GENDER:;Non-binary); never fill the sex component from the word; and when reading a card, never set the field fromGENDERwithout the person confirming on a review page. Decided here so #660 does not coerce a word intoMorF. For a Europass writer, a word that is not one ofGenderEnumType's values (the list was not checked here) is left out rather than forced.readandwrite, and never in a list endpoint, a webhook, a log, a search result or an error message.docs/THREAT-MODEL.md.Builds on
#679 (the card, the switches, the validator pattern, the format bumps), #309 (form of address and pronouns: separate, never inferred), #308 (a CV chooses what it prints), #181 (the candidate file), #301 (the native select stays the scripts-off path). #660 for the vCard side.
Done when
tests/test_gender.py(or cases intests/test_form_of_address.py, beside the two it sits with): the field saves a listed value, an Other text and a clear; a value outside the list is kept as text (the menu accepts what it is sent, as #309 does); the length bound and NUL refusal; setting it changes neither the form of address nor the pronouns, and the reverse.tests/test_cv_prints.pyandtests/security/test_cv_prints.pycovershow_gender(default off, chosen on, the archive carrying it); nothing is printed by default in the preview, the plain text or the Word file;tests/test_export.pyand the archive round trip carry the column at the new format;tests/test_candidate_file.pyholds the new fingerprint and a review row; the archive importer reads it through the column's rule (tests/test_importers.py).tests/test_api.py:GETandPATCH /profilecarrygenderwith the page's bounds; the CV'sprintscarries the boolean. Search is unaffected: nothing here is indexed (core/search.pyhas no profile group), and a test says so for the new fields.core/gdpr.pyneeds no line, because the record of processing is drawn from plugins and connections.sexautocomplete token, a described-by sentence, and the options carry theirlang; axe is clean in both themes (tests/e2e/test_accessibility.py,tests/e2e/test_form_of_address.pyextended).docs/THREAT-MODEL.mdchange in the same commit.From the list of improvements of 2026-10-02.