diff --git a/aurweb/models/tu_vote.py b/aurweb/models/tu_vote.py index cd486b4d..22fefedb 100644 --- a/aurweb/models/tu_vote.py +++ b/aurweb/models/tu_vote.py @@ -54,3 +54,6 @@ class TUVote(Base): "aurweb.models.tu_vote.DECISIONS"), orig="TU_Votes.Decision", params=(self.Decision,)) + + def display(self) -> str: + return DECISIONS.get(self.Decision) diff --git a/aurweb/routers/trusted_user.py b/aurweb/routers/trusted_user.py index 53bcecb7..02d3a5b7 100644 --- a/aurweb/routers/trusted_user.py +++ b/aurweb/routers/trusted_user.py @@ -2,6 +2,7 @@ import html import typing from http import HTTPStatus +from typing import Any, Dict, Optional, Tuple from fastapi import APIRouter, Form, HTTPException, Request from fastapi.responses import RedirectResponse, Response @@ -10,8 +11,9 @@ from sqlalchemy import and_, func, or_ from aurweb import db, l10n, logging, models, time from aurweb.auth import creds, requires_auth from aurweb.exceptions import handle_form_exceptions -from aurweb.models import User +from aurweb.models import TUVote, TUVoteInfo, User from aurweb.models.account_type import TRUSTED_USER_AND_DEV_ID, TRUSTED_USER_ID +from aurweb.models.tu_vote import DECISION_IDS, DECISIONS from aurweb.templates import make_context, make_variable_context, render_template router = APIRouter() @@ -152,8 +154,10 @@ async def trusted_user_proposal(request: Request, proposal: int): context = await make_variable_context(request, "Trusted User") proposal = int(proposal) - voteinfo = db.query(models.TUVoteInfo).filter( - models.TUVoteInfo.ID == proposal).first() + with db.begin(): + voteinfo = db.query(models.TUVoteInfo).filter( + models.TUVoteInfo.ID == proposal).first() + if not voteinfo: raise HTTPException(status_code=HTTPStatus.NOT_FOUND) @@ -166,13 +170,43 @@ async def trusted_user_proposal(request: Request, proposal: int): context["error"] = "Only Trusted Users are allowed to vote." if voteinfo.User == request.user.Username: context["error"] = "You cannot vote in an proposal about you." - elif vote is not None: - context["error"] = "You've already voted for this proposal." - context["vote"] = vote return render_proposal(request, context, proposal, voteinfo, voters, vote) +def judge_decision(request: Request, context: Dict[str, Any], + voteinfo: TUVoteInfo, vote: Optional[TUVote], + decision_id: int) -> Tuple[HTTPStatus, str]: + """ Decide if a given decision by request is valid. + + A non-HTTPStatus.OK status_code value indicates that judge_decision + ran into an error. context's error key is set when this happens. + + :param request: FastAPI Request + :param context: FastAPI template context + :param voteinfo: TUVoteInfo instance + :param vote: TUVote instance + :param decision_id: YES_ID, NO_ID, or ABSTAIN_ID + :return: (status_code, old_decision) tuple + """ + old_decision = None + status_code = HTTPStatus.OK + if not request.user.has_credential(creds.TU_VOTE): + context["error"] = "Only Trusted Users are allowed to vote." + status_code = HTTPStatus.UNAUTHORIZED + elif not voteinfo.is_running(): + context["error"] = "Voting is closed for this proposal." + status_code = HTTPStatus.BAD_REQUEST + elif voteinfo.User == request.user.Username: + context["error"] = "You cannot vote in an proposal about you." + status_code = HTTPStatus.BAD_REQUEST + elif vote is not None: + if vote.Decision is not None and vote.Decision != decision_id: + old_decision = DECISIONS.get(vote.Decision) + + return (status_code, old_decision) + + @router.post("/tu/{proposal}") @handle_form_exceptions @requires_auth @@ -189,39 +223,40 @@ async def trusted_user_proposal_post(request: Request, proposal: int, if not voteinfo: raise HTTPException(status_code=HTTPStatus.NOT_FOUND) + if decision not in DECISION_IDS: + return Response("Invalid 'decision' value.", + status_code=HTTPStatus.BAD_REQUEST) + decision_id = DECISION_IDS.get(decision) + voters = db.query(models.User).join(models.TUVote).filter( models.TUVote.VoteID == voteinfo.ID) vote = db.query(models.TUVote).filter( and_(models.TUVote.UserID == request.user.ID, models.TUVote.VoteID == voteinfo.ID)).first() - status_code = HTTPStatus.OK - if not request.user.has_credential(creds.TU_VOTE): - context["error"] = "Only Trusted Users are allowed to vote." - status_code = HTTPStatus.UNAUTHORIZED - elif voteinfo.User == request.user.Username: - context["error"] = "You cannot vote in an proposal about you." - status_code = HTTPStatus.BAD_REQUEST - elif vote is not None: - context["error"] = "You've already voted for this proposal." - status_code = HTTPStatus.BAD_REQUEST - + status_code, old_decision = judge_decision( + request, context, voteinfo, vote, decision_id) if status_code != HTTPStatus.OK: return render_proposal(request, context, proposal, voteinfo, voters, vote, status_code=status_code) - if decision in {"Yes", "No", "Abstain"}: - # Increment whichever decision was given to us. - setattr(voteinfo, decision, getattr(voteinfo, decision) + 1) - else: - return Response("Invalid 'decision' value.", - status_code=HTTPStatus.BAD_REQUEST) - with db.begin(): - vote = db.create(models.TUVote, User=request.user, VoteInfo=voteinfo) + # If the decision was changed, decrement the old decision. + if old_decision is not None: + setattr(voteinfo, old_decision, + getattr(voteinfo, old_decision) - 1) + + # In all cases, increment the new decision. + setattr(voteinfo, decision, getattr(voteinfo, decision) + 1) + + # Create the vote if doesn't exist yet. + if not vote: + vote = db.create(models.TUVote, User=request.user, + VoteInfo=voteinfo, Decision=decision_id) + else: + vote.Decision = decision_id - context["error"] = "You've already voted for this proposal." return render_proposal(request, context, proposal, voteinfo, voters, vote) diff --git a/po/aurweb.pot b/po/aurweb.pot index bec1b672..aa9f4eb2 100644 --- a/po/aurweb.pot +++ b/po/aurweb.pot @@ -2334,3 +2334,11 @@ msgid "This action will close any pending package requests " "related to it. If %sComments%s are omitted, a closure " "comment will be autogenerated." msgstr "" + +#: templates/partials/tu/proposal/details.html +msgid "Your vote" +msgstr "" + +#: templates/partials/tu/proposal/details.html +msgid "You can change your vote while the proposal is still running." +msgstr "" diff --git a/templates/partials/tu/proposal/details.html b/templates/partials/tu/proposal/details.html index f7a55148..e4b64629 100644 --- a/templates/partials/tu/proposal/details.html +++ b/templates/partials/tu/proposal/details.html @@ -2,7 +2,11 @@ {% if voteinfo.is_running() %}
- {% trans %}This vote is still running.{% endtrans %}
+ {% trans %}This vote is still running.{% endtrans %}
+ {% if vote %}
+ {{ "You've already voted for this proposal." | tr }}
+ {{ "You can change your vote while the proposal is still running." | tr }}
+ {% endif %}