Capture review: saving a capture already saved makes a second listing, and a saved one can still be discarded #336

Closed
opened 2026-10-01 06:31:29 +00:00 by tiagoagueda · 1 comment
Owner

Found on 2026-09-30, reading src/postulo/jobs/capture_views.py at 0a5cad587 for postulo/postulo-chromium#7. Read from the code; not replayed in a browser.

Of the answers a capture can be given, only add to a listing's history checks that the capture is still waiting. Saving it as a listing and discarding it act on a capture in any state.

  • Where:
    • jobs/capture_views.py:221-233: CaptureReviewView.get_queryset returns every capture the person owns, whatever its status. get() (:321-324) draws the form for one already saved or discarded.
    • :326-381 (post): calls create_listing, and apply_to_listing when I have already applied is ticked, then sets ACCEPTED. No check of the status, no lock.
    • :447-452 (CaptureDiscardView.post): sets DISCARDED whatever the capture was.
    • For comparison, bind_capture (src/postulo/jobs/history.py:218-224) locks the row and raises AlreadyDecided unless it is pending, and the bulk discard (capture_views.py:479) filters on pending.
  • What happens:
    • A review form submitted twice (a double click, the back button, two tabs) makes two listings for one posting, and two applications when the box is ticked.
    • An old link does the same later: the link in a notification, or the Review in Postulo link the browser extension keeps for a capture it sent. postulo/postulo-chromium#7 would list captures from a copy that can be minutes old, which makes a stale link ordinary.
    • Discarding a capture that was saved marks it discarded while it still points at the listing it became.
  • Who meets it: anybody reviewing captures. No other account is involved.
  • Fix: saving and discarding decide under the same rule as binding: lock the row, refuse one that is no longer pending with the sentence AlreadyDecided already carries, and send the person to what the capture became. get() for a decided capture shows that (the listing or the application, or that it was discarded) instead of the form.

Done when

  • A test posts the review form twice for one capture: one listing, one application, and the second answer says it was already saved and links to it.
  • A test discards a saved capture: it stays saved.
  • Opening the review page of a decided capture shows what it became, with no form.

Found while filing the extension and DAV plugin improvements of 2026-09-30.

Found on 2026-09-30, reading `src/postulo/jobs/capture_views.py` at `0a5cad587` for postulo/postulo-chromium#7. Read from the code; not replayed in a browser. Of the answers a capture can be given, only *add to a listing's history* checks that the capture is still waiting. Saving it as a listing and discarding it act on a capture in any state. - **Where:** - `jobs/capture_views.py:221-233`: `CaptureReviewView.get_queryset` returns every capture the person owns, whatever its status. `get()` (`:321-324`) draws the form for one already saved or discarded. - `:326-381` (`post`): calls `create_listing`, and `apply_to_listing` when *I have already applied* is ticked, then sets `ACCEPTED`. No check of the status, no lock. - `:447-452` (`CaptureDiscardView.post`): sets `DISCARDED` whatever the capture was. - For comparison, `bind_capture` (`src/postulo/jobs/history.py:218-224`) locks the row and raises `AlreadyDecided` unless it is pending, and the bulk discard (`capture_views.py:479`) filters on pending. - **What happens:** - A review form submitted twice (a double click, the back button, two tabs) makes two listings for one posting, and two applications when the box is ticked. - An old link does the same later: the link in a notification, or the *Review in Postulo* link the browser extension keeps for a capture it sent. postulo/postulo-chromium#7 would list captures from a copy that can be minutes old, which makes a stale link ordinary. - Discarding a capture that was saved marks it discarded while it still points at the listing it became. - **Who meets it:** anybody reviewing captures. No other account is involved. - **Fix:** saving and discarding decide under the same rule as binding: lock the row, refuse one that is no longer pending with the sentence `AlreadyDecided` already carries, and send the person to what the capture became. `get()` for a decided capture shows that (the listing or the application, or that it was discarded) instead of the form. ## Done when - A test posts the review form twice for one capture: one listing, one application, and the second answer says it was already saved and links to it. - A test discards a saved capture: it stays saved. - Opening the review page of a decided capture shows what it became, with no form. Found while filing the extension and DAV plugin improvements of 2026-09-30.
tiagoagueda added this to the 0.5.0 milestone 2026-10-01 06:31:29 +00:00
Author
Owner

From the read-only review of 2026-09-29 (postulo at 95348908d, checked again at 0a5cad587). Three reviewers found this independently; it was not filed again. Two things they add to what is above:

  • A consequence not named here: the kept page is lost. CaptureDiscardView.post (jobs/capture_views.py) sets DISCARDED on an accepted capture and leaves posting set. expire_unconfirmed (jobs/pages.py) keeps a page only for capture__status=ACCEPTED, so the copy of the advert kept with that capture (#256) is deleted POSTULO_CAPTURE_PAGE_KEEP_DAYS after capture, although the listing it belongs to still exists and the docstring promises a saved capture keeps its page for as long as it exists.
  • The lock. bind_capture (jobs/history.py) re-reads the capture under select_for_update() before raising AlreadyDecided. The two views should do the same inside the request's transaction, or two tabs submitting at once still both pass a plain status check.

A test for the first: save a capture that has a kept page, post a discard, run expire_unconfirmed past the keep period, and assert the page is still there.

**From the read-only review of 2026-09-29** (postulo at `95348908d`, checked again at `0a5cad587`). Three reviewers found this independently; it was not filed again. Two things they add to what is above: - **A consequence not named here: the kept page is lost.** `CaptureDiscardView.post` (`jobs/capture_views.py`) sets `DISCARDED` on an accepted capture and leaves `posting` set. `expire_unconfirmed` (`jobs/pages.py`) keeps a page only for `capture__status=ACCEPTED`, so the copy of the advert kept with that capture (#256) is deleted `POSTULO_CAPTURE_PAGE_KEEP_DAYS` after capture, although the listing it belongs to still exists and the docstring promises a saved capture keeps its page for as long as it exists. - **The lock.** `bind_capture` (`jobs/history.py`) re-reads the capture under `select_for_update()` before raising `AlreadyDecided`. The two views should do the same inside the request's transaction, or two tabs submitting at once still both pass a plain status check. A test for the first: save a capture that has a kept page, post a discard, run `expire_unconfirmed` past the keep period, and assert the page is still there.
tiagoagueda referenced this issue from a commit 2026-10-04 10:05:00 +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#336
No description provided.