Web links: the same address under two kinds crashes Your details and the contact form #457

Closed
opened 2026-10-01 10:46:50 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-09-29 in a read-only review of postulo at 95348908d, by reading the web link formsets against the model's constraints and posting the case to accounts:profile in a test.

Checked again on 2026-10-01 against main at 0a5cad587: still present. None of the cited locations has moved, and the two-block post raises the same error.

A holder's web links are unique on the address across all three kinds (web_link_unique_per_holder), but each check that guards that uniqueness sees only one kind at a time. When the same address is typed under two kinds in one save, the form is accepted, and the second insert breaks the constraint. The result is a 500 with nothing saved.

  • Where:
    • core/models.py:677-680: the web_link_unique_per_holder constraint on (content_type, object_id, url).
    • core/web_links.py:308-324 (BaseWebLinkFormSet.clean): already holds the holder's saved addresses, and seen holds only this formset's rows. Social profiles, code repositories and websites are three formsets (formsets_for, :377-389), and none of them sees what another is adding in the same POST.
    • core/web_links.py:209-242 (save_only_link, the single box used while a kind's feature is off): it looks for a hidden row of the same kind only (holder.web_links.filter(kind=kind, url=typed)) and then saves, with no check against other kinds.
    • core/mixins.py:104-114 (WebLinksMixin.web_links_invalid and save_web_links) validates and saves the formsets one by one.
  • What happens: somebody lists https://github.com/me under both Code repositories and Websites on Your details (or on a contact) and saves. Both formsets validate, and the second insert raises IntegrityError: UNIQUE constraint failed: core_weblink.content_type_id, core_weblink.object_id, core_weblink.url. The same happens with two single boxes while the features are off, and with one box and one block. With every link feature off, typing the website's address into the repository box as well is enough. Both the two-block case and the two-box case were reproduced against accounts:profile.
  • Why it matters: a plausible entry (a GitHub profile is both a code repository and a personal site) takes the whole page down, and everything else typed on it is lost.
  • Fix: decide whether one address may appear under two kinds.
    • If it may, which seems the natural reading, narrow the constraint to (content_type, object_id, kind, url), change already in BaseWebLinkFormSet.clean to holder.web_links.filter(kind=self.kind), and add a migration.
    • If it may not, collect the addresses from every bound link formset and single box in the view, in WebLinksMixin.web_links_invalid or in the form's clean, and put "This address is already listed." on the later one. save_only_link should then check other kinds before saving.

Related to #305, which reworks these rows around a chosen service but does not touch the uniqueness rule.

Done when

  • A test in tests/test_web_links.py posts the same address under repositories and websites to accounts:profile and gets either both rows saved or the form back with the error. Today it raises IntegrityError.
  • The same test, run with the three features switched off, passes for the single boxes.
Found on 2026-09-29 in a read-only review of postulo at 95348908d, by reading the web link formsets against the model's constraints and posting the case to `accounts:profile` in a test. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. None of the cited locations has moved, and the two-block post raises the same error. A holder's web links are unique on the address across all three kinds (`web_link_unique_per_holder`), but each check that guards that uniqueness sees only one kind at a time. When the same address is typed under two kinds in one save, the form is accepted, and the second insert breaks the constraint. The result is a 500 with nothing saved. - **Where:** - `core/models.py:677-680`: the `web_link_unique_per_holder` constraint on `(content_type, object_id, url)`. - `core/web_links.py:308-324` (`BaseWebLinkFormSet.clean`): `already` holds the holder's saved addresses, and `seen` holds only this formset's rows. Social profiles, code repositories and websites are three formsets (`formsets_for`, `:377-389`), and none of them sees what another is adding in the same POST. - `core/web_links.py:209-242` (`save_only_link`, the single box used while a kind's feature is off): it looks for a hidden row of the same kind only (`holder.web_links.filter(kind=kind, url=typed)`) and then saves, with no check against other kinds. - `core/mixins.py:104-114` (`WebLinksMixin.web_links_invalid` and `save_web_links`) validates and saves the formsets one by one. - **What happens:** somebody lists `https://github.com/me` under both *Code repositories* and *Websites* on *Your details* (or on a contact) and saves. Both formsets validate, and the second insert raises `IntegrityError: UNIQUE constraint failed: core_weblink.content_type_id, core_weblink.object_id, core_weblink.url`. The same happens with two single boxes while the features are off, and with one box and one block. With every link feature off, typing the website's address into the repository box as well is enough. Both the two-block case and the two-box case were reproduced against `accounts:profile`. - **Why it matters:** a plausible entry (a GitHub profile is both a code repository and a personal site) takes the whole page down, and everything else typed on it is lost. - **Fix:** decide whether one address may appear under two kinds. - If it may, which seems the natural reading, narrow the constraint to `(content_type, object_id, kind, url)`, change `already` in `BaseWebLinkFormSet.clean` to `holder.web_links.filter(kind=self.kind)`, and add a migration. - If it may not, collect the addresses from every bound link formset and single box in the view, in `WebLinksMixin.web_links_invalid` or in the form's `clean`, and put "This address is already listed." on the later one. `save_only_link` should then check other kinds before saving. Related to #305, which reworks these rows around a chosen service but does not touch the uniqueness rule. ## Done when - A test in `tests/test_web_links.py` posts the same address under `repositories` and `websites` to `accounts:profile` and gets either both rows saved or the form back with the error. Today it raises `IntegrityError`. - The same test, run with the three features switched off, passes for the single boxes.
tiagoagueda added this to the 0.5.0 milestone 2026-10-01 10:46:50 +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#457
No description provided.