The record of what was sent can be destroyed or rewritten: cascading deletes, uploads replaced in place, files left on disk #217

Closed
opened 2026-09-15 21:23:20 +00:00 by tiagoagueda · 0 comments
Owner

applications/models.py:10 says "Nothing here deletes history", and PLAN §5 and the wiki (Files and what you sent: "the record of what you sent has to stay true") promise that sent snapshots are kept unchanged. Three paths break that. Found in the 2026-09-15 code audit.

1. Deleting a company, listing or application silently deletes applications and their sent documents

The chain is all CASCADE:

  • JobPosting.company (jobs/models.py:617)
  • Application.posting (applications/models.py:195)
  • ApplicationEvent.application and Interview.application
  • RenderedDocument.application (documents/models.py:486-492)

What the person sees:

  • The confirmation page says only "Anything belonging to it goes too", with no counts (templates/partials/confirm_delete.html).
  • The success message says "Company deleted, along with its postings" and never mentions applications.
  • Deleting the CV itself is deliberately handled so the PDF survives (documents/signals.py:31-48). Deleting the application is not.
  • Deleting an upload silently removes it from every application's sent_uploads.

Proposal:

  • PROTECT on Application.posting, or a confirmation page that lists how many applications, timeline entries, interviews and sent documents will go.
  • SET_NULL on RenderedDocument.application, keeping company and role as text on the render.
  • Warn before deleting an upload that applications reference.

2. Editing an uploaded file replaces it in place

  • UploadedDocumentForm includes file (documents/forms.py:220), and UploadUpdateView (documents/views.py:318) uses the same form.
  • An application's sent_uploads points at the row, so it now shows a file it never sent.
  • UploadedDocument has no checksum, so the change cannot be detected.
  • signals.py:24 acts only on creation, so external stores keep the old copy marked archived.

Proposal: make file read-only on edit, so a new file means a new version through replaces. Add a checksum to uploads and pass it in metadata_for, which today sends an empty checksum.

3. Deleted documents' files stay on disk

  • accounts/deletion.py:109-117 is the only code that removes a document file. UploadDeleteView, render rows removed with an application, and files replaced as in 2 all leave their bytes behind.
  • No command cleans them up, and backup copies them.
  • These files hold home addresses and whole careers; "deleted" should mean deleted.

Proposal:

  • On post_delete of UploadedDocument and RenderedDocument, remove the file after commit when no other row uses the name.
  • Add manage.py prune_media --dry-run, which lists files under documents/<owner>/ with no row.
  • Optionally, show a storage total per person.

Tests

  • Deleting a company with an application refuses, or lists counts and keeps the snapshot.
  • Editing an upload cannot change its file.
  • Deleting an upload or render removes the file only when nothing else references it.
`applications/models.py:10` says "Nothing here deletes history", and PLAN §5 and the wiki (*Files and what you sent*: "the record of what you sent has to stay true") promise that sent snapshots are kept unchanged. Three paths break that. Found in the 2026-09-15 code audit. ## 1. Deleting a company, listing or application silently deletes applications and their sent documents The chain is all `CASCADE`: - `JobPosting.company` (`jobs/models.py:617`) - `Application.posting` (`applications/models.py:195`) - `ApplicationEvent.application` and `Interview.application` - `RenderedDocument.application` (`documents/models.py:486-492`) What the person sees: - The confirmation page says only "Anything belonging to it goes too", with no counts (`templates/partials/confirm_delete.html`). - The success message says "Company deleted, along with its postings" and never mentions applications. - Deleting the CV itself is deliberately handled so the PDF survives (`documents/signals.py:31-48`). Deleting the application is not. - Deleting an upload silently removes it from every application's `sent_uploads`. **Proposal:** - `PROTECT` on `Application.posting`, or a confirmation page that lists how many applications, timeline entries, interviews and sent documents will go. - `SET_NULL` on `RenderedDocument.application`, keeping company and role as text on the render. - Warn before deleting an upload that applications reference. ## 2. Editing an uploaded file replaces it in place - `UploadedDocumentForm` includes `file` (`documents/forms.py:220`), and `UploadUpdateView` (`documents/views.py:318`) uses the same form. - An application's `sent_uploads` points at the row, so it now shows a file it never sent. - `UploadedDocument` has no checksum, so the change cannot be detected. - `signals.py:24` acts only on creation, so external stores keep the old copy marked archived. **Proposal:** make `file` read-only on edit, so a new file means a new version through `replaces`. Add a `checksum` to uploads and pass it in `metadata_for`, which today sends an empty checksum. ## 3. Deleted documents' files stay on disk - `accounts/deletion.py:109-117` is the only code that removes a document file. `UploadDeleteView`, render rows removed with an application, and files replaced as in 2 all leave their bytes behind. - No command cleans them up, and `backup` copies them. - These files hold home addresses and whole careers; "deleted" should mean deleted. **Proposal:** - On `post_delete` of `UploadedDocument` and `RenderedDocument`, remove the file after commit when no other row uses the name. - Add `manage.py prune_media --dry-run`, which lists files under `documents/<owner>/` with no row. - Optionally, show a storage total per person. ## Tests - Deleting a company with an application refuses, or lists counts and keeps the snapshot. - Editing an upload cannot change its file. - Deleting an upload or render removes the file only when nothing else references it.
tiagoagueda added this to the 0.3.0 milestone 2026-09-15 21:33:18 +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#217
No description provided.