Let Settings → Export set or strip the metadata of exported files (#878) #1061

Merged
tiagoagueda merged 1 commit from 878-export-metadata into main 2026-10-28 17:21:31 +00:00
Owner

A new Settings → Export page says what every exported PDF, Word and
OpenDocument file carries. For each of the title, the author, the subject,
the keywords and the language a person chooses from the document (what
an export always wrote, and the default), always this with a value of
their own, or leave it out. One switch, Strip all metadata, leaves out
everything but the language. The language stays unless it is turned off
as well, and the page says why: a screen reader reads a file in the
language it declares, and PDF/UA requires one.

The choice is eleven new profile fields (accounts migration 0048).
documents.properties.Policy reads them and resolve is the one place
the layers meet: the document's values, then the default, then whatever
was typed for one file in the export dialog (#480), which still goes
over the default, and strip all last, as a promise about every file.
The Word and OpenDocument writers, the PDF's page and the snapshot title
all go through it. The export dialog starts from the default, and while
everything is stripped it says so, with a link, instead of showing fields
it would ignore. A file whose title is left out is still downloaded under
the neutral title, and a stripped snapshot is recorded as sent without
its properties.

A language left out leaves the properties, not the text: the Word and
OpenDocument styles still set the paragraphs in it, and the PDF's page
keeps its lang, since WeasyPrint hyphenates and picks fonts by it.

WeasyPrint has no option for leaving its name out: it writes /Producer
into the information dictionary and pdf:Producer into the XMP packet
regardless. It does take a finisher, called before a byte is written, so
pdf.finish takes out /Producer, /Creator and the dates (and their XMP
counterparts) when everything is stripped, and /Lang when the language is
left out. The page asks for that in a <meta name="postulo-scrub">,
which WeasyPrint reads as custom metadata and writes nowhere. The
Chromium backend is not changed, and still names itself.

base_portfolio.html read the contact's name straight into
<meta name="author">, so a portfolio exported without its properties
named its author all the same; it now writes the properties as base_cv
does.

The setting is part of the account archive: format 76, written and read
back through each column like the other preferences.

The default lives in Settings only. A per-document choice of from the
document
, always this or leave it out in the export dialog is left
for later.

Closes #878

Notes for review

  • Precedence: the document's values, then the Settings default, then a value typed for one file in the #480 dialog, then Strip all, which wins over everything.
  • Leaving the language out removes dc:language and the PDF's /Lang only. The page's lang and the DOCX/ODT styles keep it, because hyphenation and font choice depend on it.
  • WeasyPrint has no option to drop its producer, so stripping uses its finisher hook (pdf.finish). It removes /Producer, /Creator, both dates and their XMP counterparts.
  • Not changed: the Chromium backend still names itself.
  • Open question: with Strip all on, a PDF has no title, so it is no longer strictly PDF/UA-conformant, though it is still marked pdf/ua-1. The decision kept only the language.
  • Also fixed: base_portfolio.html wrote the contact as author even when exported without properties.
  • Archive format 76: the 11 new Profile fields are in PROFILE_FIELDS. Migration accounts 0048_export_metadata.
  • Skipped locally: the 4 real-WeasyPrint read-back tests need Pango, so they run only in CI.

🤖 Generated with Claude Code

A new Settings → Export page says what every exported PDF, Word and OpenDocument file carries. For each of the title, the author, the subject, the keywords and the language a person chooses *from the document* (what an export always wrote, and the default), *always this* with a value of their own, or *leave it out*. One switch, *Strip all metadata*, leaves out everything but the language. The language stays unless it is turned off as well, and the page says why: a screen reader reads a file in the language it declares, and PDF/UA requires one. The choice is eleven new profile fields (accounts migration 0048). `documents.properties.Policy` reads them and `resolve` is the one place the layers meet: the document's values, then the default, then whatever was typed for one file in the export dialog (#480), which still goes over the default, and *strip all* last, as a promise about every file. The Word and OpenDocument writers, the PDF's page and the snapshot title all go through it. The export dialog starts from the default, and while everything is stripped it says so, with a link, instead of showing fields it would ignore. A file whose title is left out is still downloaded under the neutral title, and a stripped snapshot is recorded as sent without its properties. A language left out leaves the properties, not the text: the Word and OpenDocument styles still set the paragraphs in it, and the PDF's page keeps its `lang`, since WeasyPrint hyphenates and picks fonts by it. WeasyPrint has no option for leaving its name out: it writes /Producer into the information dictionary and pdf:Producer into the XMP packet regardless. It does take a finisher, called before a byte is written, so `pdf.finish` takes out /Producer, /Creator and the dates (and their XMP counterparts) when everything is stripped, and /Lang when the language is left out. The page asks for that in a `<meta name="postulo-scrub">`, which WeasyPrint reads as custom metadata and writes nowhere. The Chromium backend is not changed, and still names itself. base_portfolio.html read the contact's name straight into `<meta name="author">`, so a portfolio exported without its properties named its author all the same; it now writes the properties as base_cv does. The setting is part of the account archive: format 76, written and read back through each column like the other preferences. The default lives in Settings only. A per-document choice of *from the document*, *always this* or *leave it out* in the export dialog is left for later. Closes #878 ## Notes for review - **Precedence:** the document's values, then the Settings default, then a value typed for one file in the #480 dialog, then *Strip all*, which wins over everything. - **Leaving the language out** removes `dc:language` and the PDF's `/Lang` only. The page's `lang` and the DOCX/ODT styles keep it, because hyphenation and font choice depend on it. - **WeasyPrint has no option to drop its producer,** so stripping uses its `finisher` hook (`pdf.finish`). It removes `/Producer`, `/Creator`, both dates and their XMP counterparts. - **Not changed:** the Chromium backend still names itself. - **Open question:** with *Strip all* on, a PDF has no title, so it is no longer strictly PDF/UA-conformant, though it is still marked `pdf/ua-1`. The decision kept only the language. - **Also fixed:** `base_portfolio.html` wrote the contact as author even when exported without properties. - **Archive format 76:** the 11 new Profile fields are in `PROFILE_FIELDS`. Migration `accounts 0048_export_metadata`. - **Skipped locally:** the 4 real-WeasyPrint read-back tests need Pango, so they run only in CI. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Let Settings → Export set or strip the metadata of exported files (#878)
Some checks failed
CI / Database tests on PostgreSQL (push) Failing after 2m34s
CI / Unit tests and coverage (Python 3.14) (push) Failing after 11m46s
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 2m40s
CI / Security audit (push) Successful in 51s
CI / Browser tests (Chromium) (push) Successful in 30m55s
Every Python / Unit tests (Python 3.12) (push) Failing after 12m4s
Every Python / Unit tests (Python 3.13) (push) Failing after 12m1s
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
6692e75a20
A new Settings → Export page says what every exported PDF, Word and
OpenDocument file carries. For each of the title, the author, the subject,
the keywords and the language a person chooses *from the document* (what
an export always wrote, and the default), *always this* with a value of
their own, or *leave it out*. One switch, *Strip all metadata*, leaves out
everything but the language. The language stays unless it is turned off
as well, and the page says why: a screen reader reads a file in the
language it declares, and PDF/UA requires one.

The choice is eleven new profile fields (accounts migration 0048).
`documents.properties.Policy` reads them and `resolve` is the one place
the layers meet: the document's values, then the default, then whatever
was typed for one file in the export dialog (#480), which still goes
over the default, and *strip all* last, as a promise about every file.
The Word and OpenDocument writers, the PDF's page and the snapshot title
all go through it. The export dialog starts from the default, and while
everything is stripped it says so, with a link, instead of showing fields
it would ignore. A file whose title is left out is still downloaded under
the neutral title, and a stripped snapshot is recorded as sent without
its properties.

A language left out leaves the properties, not the text: the Word and
OpenDocument styles still set the paragraphs in it, and the PDF's page
keeps its `lang`, since WeasyPrint hyphenates and picks fonts by it.

WeasyPrint has no option for leaving its name out: it writes /Producer
into the information dictionary and pdf:Producer into the XMP packet
regardless. It does take a finisher, called before a byte is written, so
`pdf.finish` takes out /Producer, /Creator and the dates (and their XMP
counterparts) when everything is stripped, and /Lang when the language is
left out. The page asks for that in a `<meta name="postulo-scrub">`,
which WeasyPrint reads as custom metadata and writes nowhere. The
Chromium backend is not changed, and still names itself.

base_portfolio.html read the contact's name straight into
`<meta name="author">`, so a portfolio exported without its properties
named its author all the same; it now writes the properties as base_cv
does.

The setting is part of the account archive: format 76, written and read
back through each column like the other preferences.

The default lives in Settings only. A per-document choice of *from the
document*, *always this* or *leave it out* in the export dialog is left
for later.

Closes #878

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tiagoagueda force-pushed 878-export-metadata from 6692e75a20
Some checks failed
CI / Database tests on PostgreSQL (push) Failing after 2m34s
CI / Unit tests and coverage (Python 3.14) (push) Failing after 11m46s
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 2m40s
CI / Security audit (push) Successful in 51s
CI / Browser tests (Chromium) (push) Successful in 30m55s
Every Python / Unit tests (Python 3.12) (push) Failing after 12m4s
Every Python / Unit tests (Python 3.13) (push) Failing after 12m1s
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
to f23f34e29a
Some checks failed
CI / Browser tests (Chromium) (push) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (push) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (push) Has been cancelled
CI / Database tests on PostgreSQL (push) Has been cancelled
CI / Security audit (push) Has been cancelled
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
Every Python / Unit tests (Python 3.12) (push) Has been cancelled
Every Python / Unit tests (Python 3.13) (push) Has been cancelled
2026-10-27 19:26:57 +00:00
Compare
tiagoagueda force-pushed 878-export-metadata from f23f34e29a
Some checks failed
CI / Browser tests (Chromium) (push) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (push) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (push) Has been cancelled
CI / Database tests on PostgreSQL (push) Has been cancelled
CI / Security audit (push) Has been cancelled
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
Every Python / Unit tests (Python 3.12) (push) Has been cancelled
Every Python / Unit tests (Python 3.13) (push) Has been cancelled
to f8878c8f61
Some checks failed
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
Every Python / Unit tests (Python 3.12) (push) Has been cancelled
Every Python / Unit tests (Python 3.13) (push) Has been cancelled
CI / Browser tests (Chromium) (push) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (push) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (push) Has been cancelled
CI / Database tests on PostgreSQL (push) Has been cancelled
CI / Security audit (push) Has been cancelled
2026-10-27 22:49:05 +00:00
Compare
tiagoagueda force-pushed 878-export-metadata from f8878c8f61
Some checks failed
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
Every Python / Unit tests (Python 3.12) (push) Has been cancelled
Every Python / Unit tests (Python 3.13) (push) Has been cancelled
CI / Browser tests (Chromium) (push) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (push) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (push) Has been cancelled
CI / Database tests on PostgreSQL (push) Has been cancelled
CI / Security audit (push) Has been cancelled
to f5836719bf
Some checks failed
Every Python / Unit tests (Python 3.12) (push) Has been cancelled
Every Python / Unit tests (Python 3.13) (push) Has been cancelled
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
CI / Browser tests (Chromium) (push) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (push) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (push) Has been cancelled
CI / Database tests on PostgreSQL (push) Has been cancelled
CI / Security audit (push) Has been cancelled
2026-10-27 22:49:30 +00:00
Compare
tiagoagueda force-pushed 878-export-metadata from f5836719bf
Some checks failed
Every Python / Unit tests (Python 3.12) (push) Has been cancelled
Every Python / Unit tests (Python 3.13) (push) Has been cancelled
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
CI / Browser tests (Chromium) (push) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (push) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (push) Has been cancelled
CI / Database tests on PostgreSQL (push) Has been cancelled
CI / Security audit (push) Has been cancelled
to a58db02dd7
Some checks failed
CI / Database tests on PostgreSQL (push) Successful in 4m15s
CI / Unit tests and coverage (Python 3.14) (push) Failing after 12m31s
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 2m54s
CI / Security audit (push) Successful in 1m5s
Every Python / Unit tests (Python 3.12) (push) Failing after 12m40s
CI / Browser tests (Chromium) (push) Failing after 45m0s
Every Python / Unit tests (Python 3.13) (push) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
CI / Browser tests (Chromium) (pull_request) Has been cancelled
2026-10-28 02:45:06 +00:00
Compare
tiagoagueda force-pushed 878-export-metadata from a58db02dd7
Some checks failed
CI / Database tests on PostgreSQL (push) Successful in 4m15s
CI / Unit tests and coverage (Python 3.14) (push) Failing after 12m31s
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 2m54s
CI / Security audit (push) Successful in 1m5s
Every Python / Unit tests (Python 3.12) (push) Failing after 12m40s
CI / Browser tests (Chromium) (push) Failing after 45m0s
Every Python / Unit tests (Python 3.13) (push) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
CI / Browser tests (Chromium) (pull_request) Has been cancelled
to ecd2a497a5
Some checks failed
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 3m6s
CI / Security audit (push) Successful in 1m7s
Every Python / Unit tests (Python 3.12) (push) Successful in 13m35s
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (push) Successful in 4m21s
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (push) Successful in 13m32s
CI / Browser tests (Chromium) (push) Successful in 33m15s
Every Python / Unit tests (Python 3.13) (push) Has been cancelled
2026-10-28 13:42:53 +00:00
Compare
tiagoagueda force-pushed 878-export-metadata from ecd2a497a5
Some checks failed
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 3m6s
CI / Security audit (push) Successful in 1m7s
Every Python / Unit tests (Python 3.12) (push) Successful in 13m35s
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (push) Successful in 4m21s
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (push) Successful in 13m32s
CI / Browser tests (Chromium) (push) Successful in 33m15s
Every Python / Unit tests (Python 3.13) (push) Has been cancelled
to 6f95f7c9c9
All checks were successful
CI / Unit tests and coverage (Python 3.14) (push) Successful in 13m21s
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 3m23s
CI / Database tests on PostgreSQL (push) Successful in 4m15s
CI / Security audit (push) Successful in 1m11s
CI / Browser tests (Chromium) (push) Successful in 32m20s
Every Python / Unit tests (Python 3.12) (push) Successful in 13m57s
Every Python / Unit tests (Python 3.13) (push) Successful in 13m51s
CI / Unit tests and coverage (Python 3.14) (pull_request) Successful in 13m12s
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Successful in 3m17s
CI / Database tests on PostgreSQL (pull_request) Successful in 4m10s
CI / Security audit (pull_request) Successful in 1m3s
CI / Browser tests (Chromium) (pull_request) Successful in 32m7s
2026-10-28 17:19:17 +00:00
Compare
tiagoagueda deleted branch 878-export-metadata 2026-10-28 17:21:36 +00:00
Sign in to join this conversation.
No reviewers
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!1061
No description provided.