feat(poll): reorder options without invalidating answers
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user