Document export: OpenDocument and other open formats beside PDF and Word, with metadata the person can leave out or edit #480

Closed
opened 2026-10-01 10:47:00 +00:00 by tiagoagueda · 2 comments
Owner

Requested by the maintainer on 2026-10-01. Three things about what a document leaves Postulo as.

What there is today

  • Three formats. The PDF, drawn from the theme by WeasyPrint as PDF/UA-1 (documents/pdf.py). And two written from the document's Outline (headings, paragraphs and lists, with the setting taken off) through the registry in documents/formats.py (#236): plain text, and Word (documents/docx.py, a zip of XML written with the standard library). The registry's own description already expects more: "OpenDocument, or anything a portal invents next, arrives the same way."
  • Metadata is embedded, and nobody is asked. The PDF takes its title from the page's <title>, its author from <meta name="author"> (the person's name, templates/documents/themes/base_cv.html:29, base_letter.html:19) and its language from the document; the renderer adds what it adds by itself (the producing software, the date). The Word file carries the title, the author and the language in docProps/core.xml (documents/docx.py:250-261).
  • None of it can be seen, left out or changed. A person cannot tell what a recruiter's software will read from the file's properties, cannot send a file without their name in them (a recruitment that asks for anonymous applications), and cannot give the file a title, a subject or keywords of their own.

1. More open formats

  • OpenDocument Text (.odt, ISO/IEC 26300) first. It is the open standard for the job Word's format does, public bodies in several countries ask for it by name, and like .docx it is a zip of XML that the standard library can write from the same Outline. It is one more Format in the registry, with its media type and its extension, and no new dependency.
  • Candidates to weigh after it, each an open, documented format with a real use:
    • a single self-contained HTML file (the document as its theme draws it, readable anywhere, nothing fetched);
    • Markdown, for the forms and tools that take it;
    • PDF/A, the archival profile, for portals that demand it. It is a variant of the PDF rather than a new format, and has to be reconciled with PDF/UA-1, which is what makes the PDF readable by a screen reader and is not to be given up for it.
  • What a format is given stays the rule: an outline format gets the words, not the page. A themed .odt that looks like the PDF is a different and much larger thing, and is not asked for here.
  • A format a plugin wants to add should be able to register one. The registry is not on postulo.plugins.api today; putting it there is part of this, with its line in the wiki's Writing a plugin.

2. Export with or without embedded metadata

  • At export, a choice: with the document's properties, or without them. The default stays what it is today.
  • "Without" has a floor. PDF/UA requires a title and a language in the file, because that is what a screen reader announces; removing them makes the file worse for the people the standard exists for. So "without metadata" means without what identifies the person or the machine (author, dates, the producing software where the renderer allows it), and with a neutral title. Say exactly what is kept, on the page where the choice is made.
  • The same choice for every format that has properties (PDF, Word, OpenDocument). Plain text has none.
  • The record keeps up. Postulo remembers what was sent with an application. A copy exported without properties is a different file from one with them, so the choice is part of what is recorded, and the stored copy is the one that was actually sent.

3. Editing the metadata

  • Per document, the properties a file can carry, each with today's value as its default: title, author, subject, keywords, language. Shown before export, so a person sees what the file will say about itself even if they change nothing.
  • One set of fields, written to each format in its own way: the PDF through the page's <title> and <meta> elements, which is how WeasyPrint is given them (not through write_pdf's xmp_metadata argument, which documents/pdf.py leaves alone on purpose, see the comment at :80-83); docProps/core.xml for Word; meta.xml for OpenDocument.
  • Everything typed is text in somebody else's XML or PDF: escaped as docx.py already escapes the title, and bounded in length.
  • The help text should say plainly what keywords are for. Stuffing them does not help with applicant-tracking software and can count against an application.

Where it shows

The download controls on a CV and a letter, the copy made when a document is sent with an application, and the API's document calls, which take the same options so the browser extensions and the MCP server are not left with the old behaviour.

Done when

  • A CV and a letter can be downloaded as .odt, and the file opens in LibreOffice with its headings, paragraphs and lists, in the document's language.
  • An export "without properties" carries no author, and what it keeps is stated on the page; the PDF still passes the PDF/UA check the suite runs.
  • A title, subject and keywords typed for a document appear in the properties of the PDF, the Word file and the OpenDocument file, and nowhere else.
  • What was sent with an application records which choice was made.
  • Tests beside tests/test_document_formats.py (or wherever the Word format's are) read each file back and assert its properties, with and without, edited and default.

Suggested milestone: 0.6.0. It builds on the formats #236 brought in 0.5.0 and needs none of 0.5.0's other work.

Requested by the maintainer on 2026-10-01. Three things about what a document leaves Postulo as. ## What there is today - **Three formats.** The PDF, drawn from the theme by WeasyPrint as PDF/UA-1 (`documents/pdf.py`). And two written from the document's `Outline` (headings, paragraphs and lists, with the setting taken off) through the registry in `documents/formats.py` (#236): plain text, and Word (`documents/docx.py`, a zip of XML written with the standard library). The registry's own description already expects more: "OpenDocument, or anything a portal invents next, arrives the same way." - **Metadata is embedded, and nobody is asked.** The PDF takes its title from the page's `<title>`, its author from `<meta name="author">` (the person's name, `templates/documents/themes/base_cv.html:29`, `base_letter.html:19`) and its language from the document; the renderer adds what it adds by itself (the producing software, the date). The Word file carries the title, the author and the language in `docProps/core.xml` (`documents/docx.py:250-261`). - **None of it can be seen, left out or changed.** A person cannot tell what a recruiter's software will read from the file's properties, cannot send a file without their name in them (a recruitment that asks for anonymous applications), and cannot give the file a title, a subject or keywords of their own. ## 1. More open formats - **OpenDocument Text (`.odt`, ISO/IEC 26300) first.** It is the open standard for the job Word's format does, public bodies in several countries ask for it by name, and like `.docx` it is a zip of XML that the standard library can write from the same `Outline`. It is one more `Format` in the registry, with its media type and its extension, and no new dependency. - **Candidates to weigh after it**, each an open, documented format with a real use: - a single self-contained HTML file (the document as its theme draws it, readable anywhere, nothing fetched); - Markdown, for the forms and tools that take it; - PDF/A, the archival profile, for portals that demand it. It is a variant of the PDF rather than a new format, and has to be reconciled with PDF/UA-1, which is what makes the PDF readable by a screen reader and is not to be given up for it. - **What a format is given stays the rule**: an outline format gets the words, not the page. A themed `.odt` that looks like the PDF is a different and much larger thing, and is not asked for here. - A format a plugin wants to add should be able to register one. The registry is not on `postulo.plugins.api` today; putting it there is part of this, with its line in the wiki's *Writing a plugin*. ## 2. Export with or without embedded metadata - At export, a choice: **with the document's properties, or without them.** The default stays what it is today. - "Without" has a floor. PDF/UA requires a title and a language in the file, because that is what a screen reader announces; removing them makes the file worse for the people the standard exists for. So "without metadata" means without what identifies the person or the machine (author, dates, the producing software where the renderer allows it), and with a neutral title. Say exactly what is kept, on the page where the choice is made. - The same choice for every format that has properties (PDF, Word, OpenDocument). Plain text has none. - **The record keeps up.** Postulo remembers what was sent with an application. A copy exported without properties is a different file from one with them, so the choice is part of what is recorded, and the stored copy is the one that was actually sent. ## 3. Editing the metadata - Per document, the properties a file can carry, each with today's value as its default: **title, author, subject, keywords, language**. Shown before export, so a person sees what the file will say about itself even if they change nothing. - One set of fields, written to each format in its own way: the PDF through the page's `<title>` and `<meta>` elements, which is how WeasyPrint is given them (not through `write_pdf`'s `xmp_metadata` argument, which `documents/pdf.py` leaves alone on purpose, see the comment at `:80-83`); `docProps/core.xml` for Word; `meta.xml` for OpenDocument. - Everything typed is text in somebody else's XML or PDF: escaped as `docx.py` already escapes the title, and bounded in length. - The help text should say plainly what keywords are for. Stuffing them does not help with applicant-tracking software and can count against an application. ## Where it shows The download controls on a CV and a letter, the copy made when a document is sent with an application, and the API's document calls, which take the same options so the browser extensions and the MCP server are not left with the old behaviour. ## Done when - A CV and a letter can be downloaded as `.odt`, and the file opens in LibreOffice with its headings, paragraphs and lists, in the document's language. - An export "without properties" carries no author, and what it keeps is stated on the page; the PDF still passes the PDF/UA check the suite runs. - A title, subject and keywords typed for a document appear in the properties of the PDF, the Word file and the OpenDocument file, and nowhere else. - What was sent with an application records which choice was made. - Tests beside `tests/test_document_formats.py` (or wherever the Word format's are) read each file back and assert its properties, with and without, edited and default. Suggested milestone: 0.6.0. It builds on the formats #236 brought in 0.5.0 and needs none of 0.5.0's other work.
tiagoagueda added this to the 0.6.0 milestone 2026-10-01 10:47:00 +00:00
Author
Owner

The maintainer's list of 2026-10-02 states an order for document export that bears on this issue: PDF first, then ODT, then DOCX.

Checked against main at f09b6010b: PDF is written (src/postulo/documents/pdf.py) and so is DOCX (src/postulo/documents/docx.py, from #236). OpenDocument is the missing one: odt appears only as an accepted upload extension (documents/models.py:586), and the registry in documents/formats.py names it as a format that “arrives the same way” (:16). So part 1 of this issue, ODT first, is already the right order.

The candidates listed after it (HTML, Markdown, PDF/A) are outside that order. #653 writes the rule into CONTRIBUTING.md, with the line that a format further down needs a reason in its issue.

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

The maintainer's list of 2026-10-02 states an order for document export that bears on this issue: PDF first, then ODT, then DOCX. Checked against `main` at `f09b6010b`: PDF is written (`src/postulo/documents/pdf.py`) and so is DOCX (`src/postulo/documents/docx.py`, from #236). OpenDocument is the missing one: `odt` appears only as an accepted upload extension (`documents/models.py:586`), and the registry in `documents/formats.py` names it as a format that “arrives the same way” (`:16`). So part 1 of this issue, ODT first, is already the right order. The candidates listed after it (HTML, Markdown, PDF/A) are outside that order. #653 writes the rule into `CONTRIBUTING.md`, with the line that a format further down needs a reason in its issue. From the list of improvements of 2026-10-02.
Author
Owner

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

Landed on main with a green Forgejo CI run. Its entry in the CHANGELOG's Unreleased section ends in (#480).
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#480
No description provided.