Editing an application: saving a stale form moves the status back and logs a move nobody made #545
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#545
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo at
95348908d, by reading the edit views that route a field through a service.Checked again on 2026-10-01 against
mainat0a5cad587: still present. The code is now atapplications/views.py:960-987forInterviewUpdateView.form_valid(thereschedule_interviewcall at:985);ApplicationUpdateView.form_valid, the form and the cell editor have not moved.Since #231,
ApplicationUpdateViewhas written the status read under a lock rather than the one the form was drawn with. It then askschange_statusfor the status the form posted, and the form always posts the status it was drawn with. So a move made after the page was opened is still undone, now with a timeline entry for it.InterviewUpdateViewdoes the same with an interview's times.Where:
applications/views.py:435-472,ApplicationUpdateView.form_valid:requested_status = form.cleaned_data["status"](:460), thenchange_status(self.object, requested_status)(:470).statusis inApplicationForm.Meta.fields(applications/forms.py:318-346).applications/views.py:920-947,InterviewUpdateView.form_valid:reschedule_interview(interview, starts_at=…, ends_at=…)(:945) with the times the form was drawn with.updated_at(core/cells.py:170-195,moved_on); the full forms have no such check.What happens:
An interview moved by the API or a calendar sync is moved back in the same way when an older edit page is saved to change its location, and a "moved from … to …" entry is written.
Why it matters: the event log is meant to be the truth about the search. A stale page silently reverses a real change and records a transition the person never chose; the view's docstring describes this case as fixed.
Fix: carry what the form was drawn with (a hidden
updated_atstamp, ascore/cells.pydoes, or the drawn status and times). Callchange_statusonly when the posted status differs from the drawn one, andreschedule_interviewonly when the posted times differ from the drawn ones. When the row has moved since, either refuse with the cells'STALEmessage or keep the current value.Related to #231 (closed), whose item 11 described the race; the lock it added does not cover a stale form.
Done when
tests/test_applications.pydraws the edit form, moves the application withchange_status, posts the form with only the priority changed, and asserts the status is unchanged and no new timeline entry exists.tests/test_interviews.pydoes the same for an interview rescheduled after its edit form was drawn.