Phone numbers and web links: removing a row and adding the same value back in one save is refused #461
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#461
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, reading the two formsets'cleanmethods and binding them with a deleted row and a new row holding the same value.Related to #303.
Checked again on 2026-10-01 against
mainat0a5cad587: still present. The code is now atcore/phone_numbers.py:310(BasePhoneNumberFormSet.clean, with the call totaken_elsewhereat line 326),core/phone_numbers.py:123(taken_elsewhere) andcore/phone_numbers.py:149(collision_message);core/web_links.pyhas not moved, and both formsets give the same refusals.Both formsets compare what is typed against the rows still in the database, including the rows the same submission is deleting. For telephone numbers the refusal is the instance-wide sentence, which tells the person the number "may belong to somebody else's records" when the only holder is the row they are removing, and each refusal spends one of the account's collision answers.
core/phone_numbers.py:333(BasePhoneNumberFormSet.clean), which callstaken_elsewhere(typed, exclude_pk=form.instance.pk)at line 349;taken_elsewhereatcore/phone_numbers.py:146excludes only the row's own key;collision_messageatcore/phone_numbers.py:172callscollision_noticed, which spends the allowance.core/web_links.py:308(BaseWebLinkFormSet.clean), wherealreadyat line 313 holds every stored address of the holder, including those marked for deletion.+351912345678and adds the same number as a Mobile row in one save. The new row is refused with "That number is already recorded on this instance. Telephone numbers are kept unique across every account here, so it may belong to somebody else's records." The same move in a web link block is refused with "This address is already listed." (Both reproduced by binding the formsets.) Swapping the values of two rows is refused the same way.cleanmethods, first collect the primary keys of the forms markedDELETE. Pass them totaken_elsewhere(anexclude_pksargument alongsideexclude_pk) and drop their stored values fromalready.BaseModelFormSet.savedeletes existing rows before it inserts new ones, so the per-instance unique constraint onPhoneNumberstill holds at save.Done when
tests/test_phone_numbers.pyandtests/test_web_links.pyeach bind their formset with a stored row markedDELETEand a new row with the same value, and asserts that the formset is valid, that saving leaves one row, and that no collision answer was spent.