Listings bulk actions: every ticked row counts as changed, and re-shortlisting resets the decision date #508

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

Found on 2026-09-29 in a read-only review of postulo at 95348908d, comparing the three bulk views with the helper they share.

Related to #134.

Checked again on 2026-10-01 against main at 0a5cad587: still present. The code is now at jobs/models.py:1022-1039 (JobPosting.shortlist, discard, restore); the other locations have not moved.

bulk.changed promises to say how many rows did change, and the applications bulk view keeps that promise. The listings bulk view does not: it counts every ticked row, and calls the model method on rows already in the target state, which overwrites their decision date.

  • Where: jobs/listing_views.py:183-193 (ListingBulkView.post); jobs/models.py:1019-1036 (JobPosting.shortlist, discard, restore); the rule it drifts from at core/bulk.py:102-108 and applications/views.py:318-329 (ApplicationBulkView._status, which skips rows change_status reports as unchanged).
  • What happens: a person ticks 40 listings on Listings (for example on the All tab), 35 of them already shortlisted, and presses Shortlist. The loop calls listing.shortlist() on all 40 and adds one to changed each time, so the message says "40 listings changed." shortlist() sets decided_at = timezone.now() and saves unconditionally, so the 35 lose the date they were really shortlisted on and get a new updated_at. Restore on listings that are already new and Discard on listings already discarded for the same reason behave the same way.
  • Why it matters: the count is wrong, which the docstring of bulk.changed exists to prevent, and the decision date — exposed by the API (api/schemas.py:648) and written into the export (core/export.py:185) — is rewritten for listings nobody changed. The two copies of "count only what changed" have drifted, and the next bulk view will copy whichever it finds first.
  • Fix: have shortlist, discard and restore return whether they changed anything (no-op when the state, and for discard the reason, are already the target), as change_status does by returning None; count only the rows that report a change in ListingBulkView.post. The single-row views and the API (jobs/listing_views.py:245-254, api/routers/listings.py:228-243) then stop re-stamping as well.

Done when

  • A test in tests/test_listings_table.py ticks one new and one already-shortlisted listing, presses Shortlist, and finds "1 listing changed." and the shortlisted one's decided_at unchanged.
Found on 2026-09-29 in a read-only review of postulo at 95348908d, comparing the three bulk views with the helper they share. Related to #134. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. The code is now at `jobs/models.py:1022-1039` (`JobPosting.shortlist`, `discard`, `restore`); the other locations have not moved. `bulk.changed` promises to say how many rows *did* change, and the applications bulk view keeps that promise. The listings bulk view does not: it counts every ticked row, and calls the model method on rows already in the target state, which overwrites their decision date. - **Where:** `jobs/listing_views.py:183-193` (`ListingBulkView.post`); `jobs/models.py:1019-1036` (`JobPosting.shortlist`, `discard`, `restore`); the rule it drifts from at `core/bulk.py:102-108` and `applications/views.py:318-329` (`ApplicationBulkView._status`, which skips rows `change_status` reports as unchanged). - **What happens:** a person ticks 40 listings on *Listings* (for example on the *All* tab), 35 of them already shortlisted, and presses *Shortlist*. The loop calls `listing.shortlist()` on all 40 and adds one to `changed` each time, so the message says "40 listings changed." `shortlist()` sets `decided_at = timezone.now()` and saves unconditionally, so the 35 lose the date they were really shortlisted on and get a new `updated_at`. *Restore* on listings that are already new and *Discard* on listings already discarded for the same reason behave the same way. - **Why it matters:** the count is wrong, which the docstring of `bulk.changed` exists to prevent, and the decision date — exposed by the API (`api/schemas.py:648`) and written into the export (`core/export.py:185`) — is rewritten for listings nobody changed. The two copies of "count only what changed" have drifted, and the next bulk view will copy whichever it finds first. - **Fix:** have `shortlist`, `discard` and `restore` return whether they changed anything (no-op when the state, and for discard the reason, are already the target), as `change_status` does by returning `None`; count only the rows that report a change in `ListingBulkView.post`. The single-row views and the API (`jobs/listing_views.py:245-254`, `api/routers/listings.py:228-243`) then stop re-stamping as well. ## Done when - A test in `tests/test_listings_table.py` ticks one new and one already-shortlisted listing, presses *Shortlist*, and finds "1 listing changed." and the shortlisted one's `decided_at` unchanged.
tiagoagueda added this to the 0.6.0 milestone 2026-10-01 10:47:11 +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#508
No description provided.