mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
add /tu/{proposal_id} (get, post) routes
This commit ports the `/tu/?id={proposal_id}` PHP routes to FastAPI into two individual GET and POST routes. With this port of the single proposal view and POST logic, several things have changed. - The only parameter used is now `decision`, which must contain `Yes`, `No`, or `Abstain` as a string. When an invalid value is given, a BAD_REQUEST response is returned in plaintext: Invalid 'decision' value. - The `doVote` parameter has been removed. - The details section has been rearranged into a set of divs with specific classes that can be used for testing. CSS has been added to persist the layout with the element changes. - Several errors that can be discovered in the POST path now trigger their own non-200 HTTPStatus codes. Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
parent
83c038a42a
commit
85ba4a33a8
7 changed files with 571 additions and 2 deletions
|
@ -1,7 +1,11 @@
|
|||
import typing
|
||||
|
||||
from datetime import datetime
|
||||
from http import HTTPStatus
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi import APIRouter, Form, HTTPException, Request
|
||||
from fastapi.responses import Response
|
||||
from sqlalchemy import and_, or_
|
||||
|
||||
from aurweb import db
|
||||
|
@ -10,7 +14,7 @@ from aurweb.models.account_type import DEVELOPER, TRUSTED_USER, TRUSTED_USER_AND
|
|||
from aurweb.models.tu_vote import TUVote
|
||||
from aurweb.models.tu_voteinfo import TUVoteInfo
|
||||
from aurweb.models.user import User
|
||||
from aurweb.templates import make_context, render_template
|
||||
from aurweb.templates import make_context, make_variable_context, render_template
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
@ -95,3 +99,122 @@ async def trusted_user(request: Request,
|
|||
])
|
||||
|
||||
return render_template(request, "tu/index.html", context)
|
||||
|
||||
|
||||
def render_proposal(request: Request,
|
||||
context: dict,
|
||||
proposal: int,
|
||||
voteinfo: TUVoteInfo,
|
||||
voters: typing.Iterable[User],
|
||||
vote: TUVote,
|
||||
status_code: HTTPStatus = HTTPStatus.OK):
|
||||
""" Render a single TU proposal. """
|
||||
context["proposal"] = proposal
|
||||
context["voteinfo"] = voteinfo
|
||||
context["voters"] = voters
|
||||
|
||||
participation = voteinfo.ActiveTUs / voteinfo.total_votes() \
|
||||
if voteinfo.total_votes() else 0
|
||||
context["participation"] = participation
|
||||
|
||||
accepted = (voteinfo.Yes > voteinfo.ActiveTUs / 2) or \
|
||||
(participation > voteinfo.Quorum and voteinfo.Yes > voteinfo.No)
|
||||
context["accepted"] = accepted
|
||||
|
||||
can_vote = voters.filter(TUVote.User == request.user).first() is None
|
||||
context["can_vote"] = can_vote
|
||||
|
||||
if not voteinfo.is_running():
|
||||
context["error"] = "Voting is closed for this proposal."
|
||||
|
||||
context["vote"] = vote
|
||||
context["has_voted"] = vote is not None
|
||||
|
||||
return render_template(request, "tu/show.html", context,
|
||||
status_code=status_code)
|
||||
|
||||
|
||||
@router.get("/tu/{proposal}")
|
||||
@auth_required(True, redirect="/")
|
||||
@account_type_required(REQUIRED_TYPES)
|
||||
async def trusted_user_proposal(request: Request, proposal: int):
|
||||
context = await make_variable_context(request, "Trusted User")
|
||||
proposal = int(proposal)
|
||||
|
||||
voteinfo = db.query(TUVoteInfo, TUVoteInfo.ID == proposal).first()
|
||||
if not voteinfo:
|
||||
raise HTTPException(status_code=int(HTTPStatus.NOT_FOUND))
|
||||
|
||||
voters = db.query(User).join(TUVote).filter(TUVote.VoteID == voteinfo.ID)
|
||||
vote = db.query(TUVote, and_(TUVote.UserID == request.user.ID,
|
||||
TUVote.VoteID == voteinfo.ID)).first()
|
||||
|
||||
if not request.user.is_trusted_user():
|
||||
context["error"] = "Only Trusted Users are allowed to vote."
|
||||
elif 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)
|
||||
|
||||
|
||||
@router.post("/tu/{proposal}")
|
||||
@auth_required(True, redirect="/")
|
||||
@account_type_required(REQUIRED_TYPES)
|
||||
async def trusted_user_proposal_post(request: Request,
|
||||
proposal: int,
|
||||
decision: str = Form(...)):
|
||||
context = await make_variable_context(request, "Trusted User")
|
||||
proposal = int(proposal) # Make sure it's an int.
|
||||
|
||||
voteinfo = db.query(TUVoteInfo, TUVoteInfo.ID == proposal).first()
|
||||
if not voteinfo:
|
||||
raise HTTPException(status_code=int(HTTPStatus.NOT_FOUND))
|
||||
|
||||
voters = db.query(User).join(TUVote).filter(TUVote.VoteID == voteinfo.ID)
|
||||
|
||||
# status_code we'll use for responses later.
|
||||
status_code = HTTPStatus.OK
|
||||
|
||||
if not request.user.is_trusted_user():
|
||||
# Test: Create a proposal and view it as a "Developer". It
|
||||
# should give us this error.
|
||||
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
|
||||
|
||||
vote = db.query(TUVote, and_(TUVote.UserID == request.user.ID,
|
||||
TUVote.VoteID == voteinfo.ID)).first()
|
||||
|
||||
if status_code != HTTPStatus.OK:
|
||||
return render_proposal(request, context, proposal,
|
||||
voteinfo, voters, vote,
|
||||
status_code=status_code)
|
||||
|
||||
if vote is not None:
|
||||
context["error"] = "You've already voted for this proposal."
|
||||
status_code = HTTPStatus.BAD_REQUEST
|
||||
|
||||
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=int(HTTPStatus.BAD_REQUEST))
|
||||
|
||||
vote = db.create(TUVote, User=request.user, VoteInfo=voteinfo,
|
||||
autocommit=False)
|
||||
voteinfo.ActiveTUs += 1
|
||||
db.commit()
|
||||
|
||||
context["error"] = "You've already voted for this proposal."
|
||||
return render_proposal(request, context, proposal, voteinfo, voters, vote)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue