Your career: honours and awards, and memberships #693
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#693
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?
The maintainer's list, under Your career, names “Honours and awards” and “Memberships” among the additional sections. Postulo has a place for neither: a prize can only be typed into a project's summary and a membership into a skill group. This issue adds the two as sections of the career record, each a small typed entry, following the shape and the checklist settled in #692.
What exists today
src/postulofor honours, awards and membership finds no model, field or string for a career: the matches are the words in other senses (“Postulo honours robots.txt”, “membership of ISO 4217”). The career's eight kinds are insrc/postulo/resume/registry.py:28-80.Certification: a name, an issuer, an issue date, an optional expiry and a credential link (src/postulo/resume/models.py:215-228). Its issuer is free text, its name is not translatable, and a CV prints it as “name, issuer” and the year (src/postulo/templates/documents/themes/base_cv.html:117-125,src/postulo/documents/rendering.py:363-371). An honour reads the same way on a CV and differs in what it is: recognition, with nothing to check and no expiry.Education.gradeis “as the institution words it” (resume/forms.py:122), so “First-class honours” and “cum laude” have a home that is not this section.Experience.organisation,resume/models.py:65-74, #131;resume/translatable.py:16-22: “Universidade de Lisboa stays that on an English CV”), and #683 is reopening that for employers. The company list is the one an application's picker draws on.DATE_FIELDSnames the field for experience, education and certifications (src/postulo/resume/ordering.py:28-32; the candidate import's_placereads it too,resume/candidate.py:1382), and the candidate file's reader refuses a date that is not a full ISO date (resume/candidate.py:106), so a year alone needs a rule of its own.NetworksAndMemberships/NetworkAndMembership(an activity withTitle,Dateas a start, an end and anOngoingflag,Description,Link,Location) andOrganizationAffiliations/OrganizationAffiliation(OrganizationName, whosevalidFromandvalidToattributes are the period, withLocationandLink). Honours have no element;HonourAwardDateexists only as a child of the activity type. The plugin reads neither (src/postulo/plugins/europass/candidate.py:136-147); a file's memberships are named in its “nowhere to go” notice, and an honour typed intoOthersarrives as a Project (tests/test_europass_candidate.py:550-577).NetworksAndMembershipsas “memberships/involvement in professional associations, societies, political parties, sports clubs, etc.” andOrganizationAffiliationsas including “membership in a union”. Article 9(1) of the GDPR lists “political opinions, religious or philosophical beliefs, or trade union membership” as special categories of personal data (text as reproduced at gdpr-info.eu).Checked against
mainatf09b6010b.Proposal
Two commits, honours first, each complete against the checklist in #692.
Honour(“Honour or award”; heading “Honours and awards”). AResumeItemwithtitle(required, 200),awarded_by(200, optional),awarded_on(a date, optional),summary(a line or two, optional) andurl(optional). Section slughonour, placed inOVERVIEW_ORDERwhere #692 puts it. A CV prints “title, awarded by” and the year, as it prints a certification, in both base themes, the plain text and the Word file. A new entry is placed byawarded_on(ordering.DATE_FIELDS).Membership(heading “Memberships”).organisation(required, 200),role(optional, 200, labelled “your role” as a project's is),start_dateandend_date(both optional; an empty end with a start means “still”),summaryandurl. Slugmembership. A CV prints “role, organisation” and “since 2015” or “2015–2018” (the years only, as a certification's date is), with the summary under it when there is one. A new entry is placed bystart_date.summaryand nothing else: a prize's name and the body that gave it are theirs, by the rule that keepsCertificationuntranslatable (resume/translatable.py:16-26). A membership translatesroleandsummary, notorganisation, as an experience does.honoursandmembershipsinRESUME_FIELDS,RESUME_MODELSandTRANSLATION_SECTIONS(src/postulo/core/export.py:153-162,:343-415); the archive importer'ssection_modelsanddate_fields(core/importer.py:532-541) and its CV-entry map (:905-912);Kinds inresume/candidate.pythat say what makes two entries the same one: an honour is its title, its giver and its date, a membership its organisation, its role and its dates (ongoing=True). Both formats go up by one from whatever they are when each commit lands.NetworkAndMembershipandOrganizationAffiliationare read intoRecordas memberships:TitleorOrganizationNamebecomes the organisation as written (a title like “Member of the University's Film-Making Society”, which is Europass's own example, is not split into a role and a body, because that would be a guess),Descriptionthe summary,Dateor the name'svalidFromandvalidTothe dates,Linkthe address. Nothing is read as an honour. The review page lists them before anything is saved, as it lists every row.#section-honourand#section-membership. Demo data gains one of each.docs/THREAT-MODEL.md: a membership can reveal what Article 9(1) lists, so it has the same standing as the home address there: owned by one account, never in a list, a log or an error message, and printed only on a CV the person put it on.company(SET_NULL) beside its text, offered and added as g1 does it (a company the form creates is marked from the career and given no industry, as #686's issuers are; only #685's education form assigns one), with a file never creating a company, a company merge moving the links to the kept company and the company's delete dialog counting them (jobs/merging.py,jobs/views.py:574-610: aSET_NULLlink is not seen by the merge's own check, so it is cleared unless moved); both formats go up by one again, each with its line.Decisions
These are recommendations the maintainer has not confirmed.
organisation,urlandsummary; (c) one model with a type of “honour” or “membership”. Recommended: (a). An honour has one date and a title; a membership has a period and a role; the shared part is three fields, and a base for three fields is more indirection than copying them. (c) is the “one table with a kind” #692 turns down.Certification.issueris today, so nothing here waits on g1; then, in a second commit once g1 has landed, g1's optional link to one of the owner's companies, the text staying the CV's wording. How g1's “from the career” marker applies: a company the career form creates for a club or a prize body carries the marker, so it is left out of the posting, contact and agency pickers, the companies table and the map until a posting, a contact or an application is attached; being in the career never makes it somewhere to apply. #671's registry gets a role “membership organisation” (NACE division 94, “Activities of membership organisations”, section T insrc/postulo/jobs/data/nace-2.1.json), offered first and never required, since an association named for a sport or a trade may sit in another division. For honours, #686's “awarding body” role orders the list the same way and restricts nothing: universities, foundations, firms and governments all award things.HonourAwardDateas an honour is an inference nobody has checked against a real file (#318 is the open check). Recommended: read none for now, and add the reading if a real download shows the portal using it.pgettextcontext is needed for these two messages; “your role” already exists.Prior art
No dependency. The standards are the Europass formats in #692's table (read in full for the elements named above; whether the portal fills them for a membership a person adds is not verified) and Article 9(1) of Regulation (EU) 2016/679, quoted as reproduced at gdpr-info.eu (the EUR-Lex text was not fetched).
Builds on
Issues #131 (career entries in other languages; free-text organisations), #181 (the candidate file), #203 (the order of entries), #244 and #318 (the Europass reader and its real-download check), #308 (what a CV prints). New issues of this set: #692 (shape and checklist), #683 (organisation as a company, “history only”), #671 (roles and divisions), #679 (the reduced-date rule).
Needed by
Nothing waits on it. #669's link from a document to the entry it proves may later offer an honour; it is not part of this issue.
Done when
tests/test_honours_and_memberships.py: each section's create, edit, delete and move through the registry; the CV prints “title, awarded by, year” and “role, organisation, since …” in the HTML themes, the plain text and the Word file, and a portfolio prints them without an empty heading; an empty end date means “since”; a new entry lands by its date; the form help carries the rules above.tests/test_career_sections.py(the #692 guard) passes with both sections;tests/test_career_languages.pygains “an honour translates only its summary” and “a membership translates role and summary, not organisation” beside:83-86;tests/test_career_ordering.pycovers the newDATE_FIELDS.core/export.pyFORMAT_VERSION, 29 atf09b6010b; one more than whatever it is when each commit lands),tests/test_export.pyround-trips both blocks and a CV that holds one entry of each; the candidate format (4) goes up by one with its line inSHAPES(tests/test_candidate_file.py:372-387), review rows for a good entry and a refused one, andtests/security/test_candidate_file.pyfor hostile or over-long values;tests/test_europass_candidate.pyreads the two membership elements and reads no honour; the API is unchanged (there is none for the career); search finds both (tests/test_search.py); CV print choice (#308) and the processing record (core/gdpr.py) do not apply;tests/test_wiki_surface.pyis untouched unlessRecordis changed on the surface, in which case its wiki line andWriting a pluginfollow;tests/test_page_coverage.pyneeds no entry;tests/security/test_isolation_sweep.pyand a per-section test show another account's entry is a 404; account deletion removes both (tests/test_account_deletion.py); strings are in the working catalogues (en-GB, fr-FR, pt-PT as drafts) and Weblate (#349).tests/test_merging.pymoves the links of both sections,tests/test_career_companies.pygains the honour and membership cases, and a file never creates a company.docs/THREAT-MODEL.mdnamed above and the CHANGELOG line (ends(#N)) are in the same commits.From the list of improvements of 2026-10-02.
Landed on main with a green Forgejo CI run. Its entry in the CHANGELOG's Unreleased section ends in (#693).