Sending documents: sent_to is not cut to its 250-character column, so a long title and employer fail on PostgreSQL #513

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

Found on 2026-09-29 in a read-only review of postulo at 95348908d, by comparing the length of what sent_to() writes with the column it writes into.

Checked again on 2026-10-01 against main at 0a5cad587: still present. The code is now at documents/rendering.py:536-549 (sent_to), called from snapshot_cv (rendering.py:646) and snapshot_letter (rendering.py:709), with _file_and_save at rendering.py:518-533; the column is at documents/models.py:644, JobPosting.title at jobs/models.py:877 and Company.name at jobs/models.py:248. The backfill migration has not moved.

sent_to() writes "%(role)s at %(company)s" without cutting it to length. A posting's title may be 250 characters and a company's name 200, but RenderedDocument.sent_to is a CharField(max_length=250). The backfill migration that first filled the column cuts to 250; the live path does not. On PostgreSQL the insert raises DataError; SQLite does not enforce the length, so the default setup is unaffected.

  • Where:
    • documents/rendering.py:562-575, sent_to: gettext("%(role)s at %(company)s") % {...} with no truncation; called from snapshot_cv (rendering.py:672) and snapshot_letter (rendering.py:735).
    • documents/models.py:616: sent_to = models.CharField(_("sent to"), max_length=250, ...).
    • jobs/models.py:874 (JobPosting.title, 250) and jobs/models.py:245 (Company.name, 200).
    • documents/migrations/0009_backfill_sent_to_and_upload_checksums.py:30: the backfill does [:250].
    • documents/rendering.py:544-559, _file_and_save: the PDF is written before the row is saved.
  • What happens: on a PostgreSQL instance, somebody sends a CV or letter with an application whose posting title and employer name together, with the words between them, exceed 250 characters. The sent_documents errand ends in "Something went wrong. The server log has the details.", nothing is attached, and each retry fails the same way while leaving another PDF on disk that no row points at.
  • Why it matters: a captured title can be long (some boards put the whole team and location into it), and the failure is permanent for that application with no sentence that says why.
  • Fix: cut the result to RenderedDocument._meta.get_field("sent_to").max_length in sent_to(), with Truncator(...).chars() so it ends with an ellipsis, as the backfill already cuts. RenderedDocument.title (250) deserves the same guard, since document_title() starts from a full name that can reach 301 characters.

Done when

  • A test in tests/test_document_record.py builds an application with a 250-character title and a 200-character company, calls rendering.sent_to(), and asserts that the result fits the column; it fails today.
Found on 2026-09-29 in a read-only review of postulo at 95348908d, by comparing the length of what `sent_to()` writes with the column it writes into. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. The code is now at `documents/rendering.py:536-549` (`sent_to`), called from `snapshot_cv` (`rendering.py:646`) and `snapshot_letter` (`rendering.py:709`), with `_file_and_save` at `rendering.py:518-533`; the column is at `documents/models.py:644`, `JobPosting.title` at `jobs/models.py:877` and `Company.name` at `jobs/models.py:248`. The backfill migration has not moved. `sent_to()` writes "%(role)s at %(company)s" without cutting it to length. A posting's title may be 250 characters and a company's name 200, but `RenderedDocument.sent_to` is a `CharField(max_length=250)`. The backfill migration that first filled the column cuts to 250; the live path does not. On PostgreSQL the insert raises `DataError`; SQLite does not enforce the length, so the default setup is unaffected. - **Where:** - `documents/rendering.py:562-575`, `sent_to`: `gettext("%(role)s at %(company)s") % {...}` with no truncation; called from `snapshot_cv` (`rendering.py:672`) and `snapshot_letter` (`rendering.py:735`). - `documents/models.py:616`: `sent_to = models.CharField(_("sent to"), max_length=250, ...)`. - `jobs/models.py:874` (`JobPosting.title`, 250) and `jobs/models.py:245` (`Company.name`, 200). - `documents/migrations/0009_backfill_sent_to_and_upload_checksums.py:30`: the backfill does `[:250]`. - `documents/rendering.py:544-559`, `_file_and_save`: the PDF is written before the row is saved. - **What happens:** on a PostgreSQL instance, somebody sends a CV or letter with an application whose posting title and employer name together, with the words between them, exceed 250 characters. The `sent_documents` errand ends in "Something went wrong. The server log has the details.", nothing is attached, and each retry fails the same way while leaving another PDF on disk that no row points at. - **Why it matters:** a captured title can be long (some boards put the whole team and location into it), and the failure is permanent for that application with no sentence that says why. - **Fix:** cut the result to `RenderedDocument._meta.get_field("sent_to").max_length` in `sent_to()`, with `Truncator(...).chars()` so it ends with an ellipsis, as the backfill already cuts. `RenderedDocument.title` (250) deserves the same guard, since `document_title()` starts from a full name that can reach 301 characters. ## Done when - A test in `tests/test_document_record.py` builds an application with a 250-character title and a 200-character company, calls `rendering.sent_to()`, and asserts that the result fits the column; it fails today.
tiagoagueda added this to the 0.6.0 milestone 2026-10-01 10:47:13 +00:00
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#513
No description provided.