feat(poll): reorder options without invalidating answers

This commit is contained in:
2026-07-22 03:22:04 +02:00
parent 38ecff60a5
commit 8292c9d709
4 changed files with 151 additions and 2 deletions

View File

@@ -1382,6 +1382,61 @@ def update_poll_option(
return option
def reorder_poll_options(
session: Session,
*,
tenant_id: str,
poll_id: str,
option_ids: tuple[str, ...],
mutation_owner: PollMutationOwner | None = None,
) -> Poll:
"""Atomically replace the complete active option order.
Position is presentation state, so existing answers remain attached to
their stable option identities. The Poll row is locked first to keep the
lock order consistent with response and option mutations.
"""
poll = _lock_poll_for_response(
session,
tenant_id=tenant_id,
poll_id=poll_id,
)
_assert_poll_mutation_owner(poll, mutation_owner=mutation_owner)
requested_ids = tuple(option_ids)
if len(requested_ids) != len(set(requested_ids)):
raise PollError("Poll option order cannot contain duplicate options")
options = (
session.query(PollOption)
.filter(
PollOption.tenant_id == tenant_id,
PollOption.poll_id == poll.id,
PollOption.deleted_at.is_(None),
)
.order_by(PollOption.id.asc())
.populate_existing()
.with_for_update()
.all()
)
by_id = {option.id: option for option in options}
if set(requested_ids) != set(by_id):
raise PollError(
"Poll option order must contain every active option exactly once"
)
current_ids = tuple(
option.id
for option in sorted(options, key=lambda item: (item.position, item.id))
)
if requested_ids == current_ids:
return poll
if poll.status not in {"draft", "open"}:
raise PollError("Only draft or open poll options can be reordered")
for position, option_id in enumerate(requested_ids):
by_id[option_id].position = position
session.flush()
return poll
def _locked_active_poll_responses(session: Session, *, poll: Poll) -> list[PollResponse]:
return (
session.query(PollResponse)