Your career: publications, typed and shaped like BibTeX entries #687

Closed
opened 2026-10-03 01:59:01 +00:00 by tiagoagueda · 1 comment
Owner

A career record has nowhere to put a paper, a book chapter or a thesis, except a Link of the kind “Publication”, which holds a title and an address and nothing a reader could cite. The maintainer's list asks for it: "Ability to add publications. Kind of publications and main details mirror BibTEX. (A step closer to a potential BibTEX import/export plugin.) Suggestion for adding can be obtained using ORCID or DOI search. * Still not sure if this kind of item should be on the career or on the portfolio. *" This issue is the record and where it lives; the suggestion from a DOI or ORCID is #688.

What exists today

  • Only a link kind. LinkKind.PUBLICATION (src/postulo/resume/models.py:280) on Link (title, address, kind, description, reachability check; :291-318).
  • A portfolio is not a second store. It is a CV variant of kind portfolio that selects from the same career record: "a portfolio is a selection from the career record with its own layout", and a researcher's portfolio is "answered by the theme" and by what is selected (src/postulo/documents/models.py:174-192; wiki CVs.md:11-26). "On the portfolio" can only mean printed by a portfolio theme, or a new store, and no such store exists.
  • How a section is added, one entry in each: the model and form; SECTIONS and OVERVIEW_ORDER (resume/registry.py:28-91, read by the career page, its sidebar counts, resume/views.py:65-85, and the CV picker, documents/forms.py:386-432); the archive's RESUME_FIELDS, RESUME_MODELS and TRANSLATION_SECTIONS (core/export.py:153-162, :343-415); the candidate file's KINDS (resume/candidate.py:176-276, with names/dates saying what identifies an entry); the importer's section_models (core/importer.py:532-540); SECTION_LABELS and the plain-text and Word renderers (documents/rendering.py:41-49, :336-385); a branch per kind in base_cv.html:91-159 and base_portfolio.html:61-135, where an unknown kind falls into an else that prints item.name and item.role; and, for kinds that are not "a title and highlights", a branch in templates/resume/overview.html:74-99. Search covers five career models by hand (core/search.py:458-515) and links to anchors (#experience…) that the overview does not have (its ids are section-<slug>, resume/views.py:77-78); a new group should use the real one.
  • Translations. A credential's name "is the credential", so Certification translates nothing and a test says so (resume/translatable.py:16-35, tests/test_career_languages.py:83-86); a paper's title is the same sort of thing.
  • Formats. Archive 29 (core/export.py:80), candidate 4 (:104, fingerprint SHAPES in tests/test_candidate_file.py:372-387). The candidate reader's dates must be whole (^\d{4}-\d{2}-\d{2}$, resume/candidate.py:106).
  • No API career endpoint, and core/gdpr.py's processing record is drawn from plugins, so neither changes. ORCID is a stored, checksum-checked person identifier (#46); there is no DOI scheme. The Europass reader names PublicationHistory among sections "with nowhere to go" (tests/test_europass_candidate.py:561-577, wiki Your-career-record.md:415-418).
  • A gap not to copy. The archive writes a CV's entries by model name (core/export.py:781; a link is "link"), but the importer's name map has no link (core/importer.py:905-912), so a link on a CV is skipped on restore with "no such record" (:968-972). No test restores a CV holding one. (Tracked as #470, a bug in 0.5.0 with its own round-trip test; the fix is a line, and this issue does not need to wait for it.)

What BibTeX says (BibTeXing, O. Patashnik, btxdoc.tex on CTAN; the biblatex manual 3.22a, CTAN, 2026-09-24). BibTeX has 14 standard entry types: article, book, booklet, conference (an alias of inproceedings), inbook, incollection, inproceedings, manual, mastersthesis, misc, phdthesis, proceedings, techreport, unpublished. Each has required, optional and ignored fields: "BibTeX ignores any field that is not required or optional". author and editor are name lists "in the format described in the LaTeX book", separated by and. biblatex adds online, dataset, software, report, thesis, patent and more, renames journal to journaltitle, address to location and school to institution, changes the profile of inbook, makes date an ISO 8601-2 value and month an integer, and has no ORCID field.

Checked against main at f09b6010b.

Proposal

  1. One model, Publication, a ResumeItem (owner-scoped, order, deleted with the account): entry_type (closed list), title, authors and editors (names as typed, one per line), container_title (the journal, book or proceedings), publisher, institution, date (reduced ISO 8601 text as #679 settles it: 2024, 2024-05, 2024-05-17; year required), volume, number, pages, edition, series, chapter, location, doi, url, note, language (LanguageField), cite_key. One table with a type, as BibTeX has it.
  2. The type decides which fields the form offers, from one table recording BibTeX's required and optional lists. A field the type does not use is hidden, not refused; a missing "required" one is a hint, not an error (a person may hold only a title).
  3. Entered through the registry as in the list above ("Publications" after Projects): CV picker, overview row, archive (publications; format one more than whatever it is when the commit lands, with its history line), candidate file (identity: DOI, else title and year; candidate format one more), importer (the missing link in the CV-entry map is #470's fix, not this issue's; this issue adds its own entry and a round-trip test for the new section), renderers, both base themes, search by title, author and container, demo data.
  4. A CV prints one neutral line per entry (authors, title, container, volume and pages, year, DOI) in both base themes; the entry is on a CV when the person adds it. #308's print choice is for the profile's details and does not apply. A named citation style is out of scope.
  5. The DOI is checked for shape only, stored bare, linked as https://doi.org/<doi>, never resolved here (#688 is the one place that asks anyone). Publication joins Certification as translating nothing.
  6. Wiki: Your career record gets a Publications section and drops "publications" from what Europass holds that Postulo cannot; CVs says portfolios can pick them.

Decisions

  • Career or portfolio. (a) The career record; (b) a separate portfolio store; (c) the career record, with a portfolio theme that may lead with it. Recommended: (c), which is (a) plus what the code already does: a portfolio picks from the career record. (b) is a second list and a new document kind.
  • Which types. (a) BibTeX's 13 without the conference alias; (b) those plus biblatex's online, dataset, software; (c) all of biblatex's. Recommended: (b): a preprint, a dataset and software are what a researcher lists, and BibTeX has no home for them. A BibTeX-only exporter writes them as @misc. One table, so a type is one entry.
  • BibTeX or biblatex names. Recommended: biblatex's data model (maintained, and reads classic BibTeX), with container_title hiding the journal/journaltitle/booktitle split; the column names are Postulo's and the mapping lives with the type table.
  • Names as text or parts. Recommended: as typed, one per line; splitting into first, von, last and junior belongs to the import/export plugin. A wrong guess at a name is worse than the typed form.
  • The citation key. The documentation read does not say which characters a key may hold (not verified). Recommended: optional, unique per person, ASCII letters, digits and - _ : . + / (Postulo's own bound), generated from author, year and a title word when blank, never printed.
  • The old “Publication” link. Recommended: unchanged; a later "Turn into a publication" may be offered.
  • Fields BibTeX would carry that this does not (abstract, isbn, issn, eprint, keywords, anything imported). Recommended: none now; the plugin states what it loses. BibTeX itself ignores unknown fields.
  • Europass PublicationHistory. Recommended: its own commit once the section exists.

Prior art

  • Standards: BibTeX's types and fields as above; biblatex 3.22a (LPPL 1.3) for the data model; ISO 8601-2 for dates.
  • bibtexparser: MIT; 2.1.0, released 2026-10-02 (2.0.0 on 2026-09-08, 1.4.4 on 2026-01-29); needs pylatexenc (MIT, 2.11, 2026-07-25); no OSV advisory; not a dependency. Not needed here, since nothing reads or writes a .bib file; it is the library for the later BibTeX plugin (MIT, so compatible with AGPL-3.0), and "library first" applies there.
  • pybtex: MIT; 0.26.1, 2026-04-03, beta; needs latexcodec and pyyaml; no advisory. Same verdict.
  • citeproc-py: BSD-2-Clause-Views; 0.11.1, 2026-09-01, alpha; needs lxml, declined before (docs/PLAN.md:133); no advisory. Rejected: a named style is out of scope.
  • applications/ical.py and documents/docx.py are hand-written file formats; the precedent is not needed: this is a table.

Builds on

#46 (ORCID), #54 (Europass import), #131 (translations), #133 and documents/models.py:174-192 (a portfolio selects from the career record), #175 (career sections), #181 (candidate file), #308 (what a CV prints), #337 (BCP 47).

Needed by

#688 (suggestions from a DOI or ORCID), #697 (citations of a publication); #679 shares the date validator; a BibTeX plugin, to be recorded as an idea on #241.

Done when

  • A new tests/test_publications.py: each type offers its fields and hides the rest; a value survives a type change; DOI shape, key alphabet and partial dates are checked; tests/test_career_sections.py passes with the new anchor; tests/test_career_languages.py gains "a publication translates nothing".
  • tests/test_export.py: archive carries publications at the new format, restore brings them back, and a CV holding a publication and a link keeps both. tests/test_candidate_file.py holds the new fingerprint and review rows for a good and a refused entry; tests/security/test_candidate_file.py a hostile value.
  • Both base themes, the preview, the plain text and the Word file print the same line, and a theme with no branch does not print an empty heading. tests/security/test_isolation_sweep.py and tests/test_account_deletion.py cover another account's entries; search finds one and links to #section-publication.
  • API, core/gdpr.py and the plugin surface are unchanged (tests/test_wiki_surface.py untouched). New strings in the working catalogues (en-GB, fr-FR, pt-PT as drafts) and Weblate (#349); wiki pages and CHANGELOG (ends (#N)) in the same commit.

From the list of improvements of 2026-10-02.

A career record has nowhere to put a paper, a book chapter or a thesis, except a *Link* of the kind “Publication”, which holds a title and an address and nothing a reader could cite. The maintainer's list asks for it: "Ability to add publications. Kind of publications and main details mirror BibTEX. (A step closer to a potential BibTEX import/export plugin.) Suggestion for adding can be obtained using ORCID or DOI search. \* Still not sure if this kind of item should be on the career or on the portfolio. \*" This issue is the record and where it lives; the suggestion from a DOI or ORCID is #688. ## What exists today - **Only a link kind.** `LinkKind.PUBLICATION` (`src/postulo/resume/models.py:280`) on `Link` (title, address, kind, description, reachability check; `:291-318`). - **A portfolio is not a second store.** It is a CV variant of kind `portfolio` that selects from the same career record: "a portfolio *is* a selection from the career record with its own layout", and a researcher's portfolio is "answered by the theme" and by what is selected (`src/postulo/documents/models.py:174-192`; wiki `CVs.md:11-26`). "On the portfolio" can only mean printed by a portfolio theme, or a new store, and no such store exists. - **How a section is added**, one entry in each: the model and form; `SECTIONS` and `OVERVIEW_ORDER` (`resume/registry.py:28-91`, read by the career page, its sidebar counts, `resume/views.py:65-85`, and the CV picker, `documents/forms.py:386-432`); the archive's `RESUME_FIELDS`, `RESUME_MODELS` and `TRANSLATION_SECTIONS` (`core/export.py:153-162`, `:343-415`); the candidate file's `KINDS` (`resume/candidate.py:176-276`, with `names`/`dates` saying what identifies an entry); the importer's `section_models` (`core/importer.py:532-540`); `SECTION_LABELS` and the plain-text and Word renderers (`documents/rendering.py:41-49`, `:336-385`); a branch per kind in `base_cv.html:91-159` and `base_portfolio.html:61-135`, where an unknown kind falls into an `else` that prints `item.name` and `item.role`; and, for kinds that are not "a title and highlights", a branch in `templates/resume/overview.html:74-99`. Search covers five career models by hand (`core/search.py:458-515`) and links to anchors (`#experience`…) that the overview does not have (its ids are `section-<slug>`, `resume/views.py:77-78`); a new group should use the real one. - **Translations.** A credential's name "is the credential", so `Certification` translates nothing and a test says so (`resume/translatable.py:16-35`, `tests/test_career_languages.py:83-86`); a paper's title is the same sort of thing. - **Formats.** Archive 29 (`core/export.py:80`), candidate 4 (`:104`, fingerprint `SHAPES` in `tests/test_candidate_file.py:372-387`). The candidate reader's dates must be whole (`^\d{4}-\d{2}-\d{2}$`, `resume/candidate.py:106`). - **No API career endpoint**, and `core/gdpr.py`'s processing record is drawn from plugins, so neither changes. ORCID is a stored, checksum-checked person identifier (#46); there is no DOI scheme. The Europass reader names `PublicationHistory` among sections "with nowhere to go" (`tests/test_europass_candidate.py:561-577`, wiki `Your-career-record.md:415-418`). - **A gap not to copy.** The archive writes a CV's entries by model name (`core/export.py:781`; a link is `"link"`), but the importer's name map has no `link` (`core/importer.py:905-912`), so a link on a CV is skipped on restore with "no such record" (`:968-972`). No test restores a CV holding one. (Tracked as #470, a bug in 0.5.0 with its own round-trip test; the fix is a line, and this issue does not need to wait for it.) **What BibTeX says** (*BibTeXing*, O. Patashnik, `btxdoc.tex` on CTAN; the biblatex manual 3.22a, CTAN, 2026-09-24). BibTeX has 14 standard entry types: `article`, `book`, `booklet`, `conference` (an alias of `inproceedings`), `inbook`, `incollection`, `inproceedings`, `manual`, `mastersthesis`, `misc`, `phdthesis`, `proceedings`, `techreport`, `unpublished`. Each has required, optional and ignored fields: "BibTeX ignores any field that is not required or optional". `author` and `editor` are name lists "in the format described in the LaTeX book", separated by `and`. biblatex adds `online`, `dataset`, `software`, `report`, `thesis`, `patent` and more, renames `journal` to `journaltitle`, `address` to `location` and `school` to `institution`, changes the profile of `inbook`, makes `date` an ISO 8601-2 value and `month` an integer, and has no ORCID field. Checked against `main` at `f09b6010b`. ## Proposal 1. **One model, `Publication`**, a `ResumeItem` (owner-scoped, `order`, deleted with the account): `entry_type` (closed list), `title`, `authors` and `editors` (names as typed, one per line), `container_title` (the journal, book or proceedings), `publisher`, `institution`, `date` (reduced ISO 8601 text as #679 settles it: `2024`, `2024-05`, `2024-05-17`; year required), `volume`, `number`, `pages`, `edition`, `series`, `chapter`, `location`, `doi`, `url`, `note`, `language` (`LanguageField`), `cite_key`. One table with a type, as BibTeX has it. 2. **The type decides which fields the form offers**, from one table recording BibTeX's required and optional lists. A field the type does not use is hidden, not refused; a missing "required" one is a hint, not an error (a person may hold only a title). 3. **Entered through the registry** as in the list above ("Publications" after Projects): CV picker, overview row, archive (`publications`; format one more than whatever it is when the commit lands, with its history line), candidate file (identity: DOI, else title and year; candidate format one more), importer (the missing `link` in the CV-entry map is #470's fix, not this issue's; this issue adds its own entry and a round-trip test for the new section), renderers, both base themes, search by title, author and container, demo data. 4. **A CV prints one neutral line** per entry (authors, title, container, volume and pages, year, DOI) in both base themes; the entry is on a CV when the person adds it. #308's print choice is for the profile's details and does not apply. A named citation style is out of scope. 5. **The DOI is checked for shape only**, stored bare, linked as `https://doi.org/<doi>`, never resolved here (#688 is the one place that asks anyone). `Publication` joins `Certification` as translating nothing. 6. **Wiki**: *Your career record* gets a *Publications* section and drops "publications" from what Europass holds that Postulo cannot; *CVs* says portfolios can pick them. ## Decisions - **Career or portfolio.** (a) The career record; (b) a separate portfolio store; (c) the career record, with a portfolio theme that may lead with it. **Recommended:** (c), which is (a) plus what the code already does: a portfolio picks from the career record. (b) is a second list and a new document kind. - **Which types.** (a) BibTeX's 13 without the `conference` alias; (b) those plus biblatex's `online`, `dataset`, `software`; (c) all of biblatex's. **Recommended:** (b): a preprint, a dataset and software are what a researcher lists, and BibTeX has no home for them. A BibTeX-only exporter writes them as `@misc`. One table, so a type is one entry. - **BibTeX or biblatex names.** **Recommended:** biblatex's data model (maintained, and reads classic BibTeX), with `container_title` hiding the `journal`/`journaltitle`/`booktitle` split; the column names are Postulo's and the mapping lives with the type table. - **Names as text or parts.** **Recommended:** as typed, one per line; splitting into first, von, last and junior belongs to the import/export plugin. A wrong guess at a name is worse than the typed form. - **The citation key.** The documentation read does not say which characters a key may hold (not verified). **Recommended:** optional, unique per person, ASCII letters, digits and `- _ : . + /` (Postulo's own bound), generated from author, year and a title word when blank, never printed. - **The old “Publication” link.** **Recommended:** unchanged; a later "Turn into a publication" may be offered. - **Fields BibTeX would carry that this does not** (`abstract`, `isbn`, `issn`, `eprint`, `keywords`, anything imported). **Recommended:** none now; the plugin states what it loses. BibTeX itself ignores unknown fields. - **Europass `PublicationHistory`.** **Recommended:** its own commit once the section exists. ## Prior art - **Standards**: BibTeX's types and fields as above; biblatex 3.22a (LPPL 1.3) for the data model; ISO 8601-2 for dates. - **`bibtexparser`**: MIT; 2.1.0, released 2026-10-02 (2.0.0 on 2026-09-08, 1.4.4 on 2026-01-29); needs `pylatexenc` (MIT, 2.11, 2026-07-25); no OSV advisory; not a dependency. **Not needed here**, since nothing reads or writes a `.bib` file; it is the library for the later BibTeX plugin (MIT, so compatible with AGPL-3.0), and "library first" applies there. - **`pybtex`**: MIT; 0.26.1, 2026-04-03, beta; needs `latexcodec` and `pyyaml`; no advisory. Same verdict. - **`citeproc-py`**: BSD-2-Clause-Views; 0.11.1, 2026-09-01, alpha; needs `lxml`, declined before (`docs/PLAN.md:133`); no advisory. **Rejected**: a named style is out of scope. - `applications/ical.py` and `documents/docx.py` are hand-written file formats; the precedent is not needed: this is a table. ## Builds on #46 (ORCID), #54 (Europass import), #131 (translations), #133 and `documents/models.py:174-192` (a portfolio selects from the career record), #175 (career sections), #181 (candidate file), #308 (what a CV prints), #337 (BCP 47). ## Needed by #688 (suggestions from a DOI or ORCID), #697 (citations of a publication); #679 shares the date validator; a BibTeX plugin, to be recorded as an idea on #241. ## Done when - A new `tests/test_publications.py`: each type offers its fields and hides the rest; a value survives a type change; DOI shape, key alphabet and partial dates are checked; `tests/test_career_sections.py` passes with the new anchor; `tests/test_career_languages.py` gains "a publication translates nothing". - `tests/test_export.py`: archive carries `publications` at the new format, restore brings them back, and a CV holding a publication **and a link** keeps both. `tests/test_candidate_file.py` holds the new fingerprint and review rows for a good and a refused entry; `tests/security/test_candidate_file.py` a hostile value. - Both base themes, the preview, the plain text and the Word file print the same line, and a theme with no branch does not print an empty heading. `tests/security/test_isolation_sweep.py` and `tests/test_account_deletion.py` cover another account's entries; search finds one and links to `#section-publication`. - API, `core/gdpr.py` and the plugin surface are unchanged (`tests/test_wiki_surface.py` untouched). New strings in the working catalogues (en-GB, fr-FR, pt-PT as drafts) and Weblate (#349); wiki pages and CHANGELOG (ends `(#N)`) in the same commit. From the list of improvements of 2026-10-02.
tiagoagueda added this to the 0.6.0 milestone 2026-10-03 01:59:01 +00:00
Author
Owner

Landed on main with a green Forgejo CI run. Its entry in the CHANGELOG's Unreleased section ends in (#687).

Landed on main with a green Forgejo CI run. Its entry in the CHANGELOG's Unreleased section ends in (#687).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#687
No description provided.