feat(responses): support safe option editing

This commit is contained in:
2026-07-20 18:20:33 +02:00
parent 65e2d1fd65
commit e0de54b756
3 changed files with 250 additions and 13 deletions

View File

@@ -732,6 +732,83 @@ def _lock_poll_for_response(session: Session, *, tenant_id: str, poll_id: str) -
return poll
def update_poll_option(
session: Session,
*,
tenant_id: str,
poll_id: str,
option_id: str,
label: str,
description: str | None,
value: dict[str, Any] | None,
metadata: dict[str, Any],
) -> PollOption:
"""Update one option and invalidate only answers bound to that option.
The Poll row is locked first, matching response submission. The owning
API commits the option and response JSON updates in the same transaction.
"""
poll = _lock_poll_for_response(
session,
tenant_id=tenant_id,
poll_id=poll_id,
)
if poll.status not in {"draft", "open"}:
raise PollError("Only draft or open poll options can be edited")
option = (
session.query(PollOption)
.filter(
PollOption.tenant_id == tenant_id,
PollOption.poll_id == poll.id,
PollOption.id == option_id,
PollOption.deleted_at.is_(None),
)
.populate_existing()
.with_for_update()
.one_or_none()
)
if option is None:
raise PollError("Unknown poll option")
normalized_value = dict(value) if value is not None else None
normalized_metadata = dict(metadata)
changed = (
option.label != label
or option.description != description
or option.value != normalized_value
or (option.metadata_ or {}) != normalized_metadata
)
if not changed:
return option
responses = (
session.query(PollResponse)
.filter(
PollResponse.tenant_id == tenant_id,
PollResponse.poll_id == poll.id,
PollResponse.deleted_at.is_(None),
)
.order_by(PollResponse.id.asc())
.populate_existing()
.with_for_update()
.all()
)
option.label = label
option.description = description
option.value = normalized_value
option.metadata_ = normalized_metadata
for response in responses:
retained_answers = [
answer
for answer in (response.answers or [])
if not isinstance(answer, dict) or answer.get("option_id") != option.id
]
if retained_answers != (response.answers or []):
response.answers = retained_answers
session.flush()
return option
def submit_poll_response(session: Session, *, tenant_id: str, poll_id: str, payload: PollSubmitResponseRequest) -> PollResponse:
poll = _lock_poll_for_response(
session,