mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
feat(FastAPI): add /requests/{id}/close (get, post)
Changes from PHP: - If a user submits a POST request with an invalid reason, they are returned back to the closure form with a BAD_REQUEST status. - Now, users which created a PackageRequest have the ability to close their own. - Form action has been changed to `/requests/{id}/close`. Closes https://gitlab.archlinux.org/archlinux/aurweb/-/issues/20 Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
parent
1c031638c6
commit
f6141ff177
3 changed files with 196 additions and 2 deletions
|
@ -22,7 +22,7 @@ from aurweb.models.package_dependency import PackageDependency
|
||||||
from aurweb.models.package_license import PackageLicense
|
from aurweb.models.package_license import PackageLicense
|
||||||
from aurweb.models.package_notification import PackageNotification
|
from aurweb.models.package_notification import PackageNotification
|
||||||
from aurweb.models.package_relation import PackageRelation
|
from aurweb.models.package_relation import PackageRelation
|
||||||
from aurweb.models.package_request import PENDING_ID, PackageRequest
|
from aurweb.models.package_request import ACCEPTED_ID, PENDING_ID, REJECTED_ID, PackageRequest
|
||||||
from aurweb.models.package_source import PackageSource
|
from aurweb.models.package_source import PackageSource
|
||||||
from aurweb.models.package_vote import PackageVote
|
from aurweb.models.package_vote import PackageVote
|
||||||
from aurweb.models.relation_type import CONFLICTS_ID
|
from aurweb.models.relation_type import CONFLICTS_ID
|
||||||
|
@ -651,3 +651,53 @@ async def pkgbase_request_post(request: Request, name: str,
|
||||||
# Redirect the submitting user to /packages.
|
# Redirect the submitting user to /packages.
|
||||||
return RedirectResponse("/packages",
|
return RedirectResponse("/packages",
|
||||||
status_code=int(HTTPStatus.SEE_OTHER))
|
status_code=int(HTTPStatus.SEE_OTHER))
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/requests/{id}/close")
|
||||||
|
@auth_required(True)
|
||||||
|
async def requests_close(request: Request, id: int):
|
||||||
|
pkgreq = db.query(PackageRequest).filter(PackageRequest.ID == id).first()
|
||||||
|
if not request.user.is_elevated() and request.user != pkgreq.User:
|
||||||
|
# Request user doesn't have permission here: redirect to '/'.
|
||||||
|
return RedirectResponse("/", status_code=int(HTTPStatus.SEE_OTHER))
|
||||||
|
|
||||||
|
context = make_context(request, "Close Request")
|
||||||
|
context["pkgreq"] = pkgreq
|
||||||
|
return render_template(request, "requests/close.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/requests/{id}/close")
|
||||||
|
@auth_required(True)
|
||||||
|
async def requests_close_post(request: Request, id: int,
|
||||||
|
reason: int = Form(default=0),
|
||||||
|
comments: str = Form(default=str())):
|
||||||
|
pkgreq = db.query(PackageRequest).filter(PackageRequest.ID == id).first()
|
||||||
|
if not request.user.is_elevated() and request.user != pkgreq.User:
|
||||||
|
# Request user doesn't have permission here: redirect to '/'.
|
||||||
|
return RedirectResponse("/", status_code=int(HTTPStatus.SEE_OTHER))
|
||||||
|
|
||||||
|
context = make_context(request, "Close Request")
|
||||||
|
context["pkgreq"] = pkgreq
|
||||||
|
|
||||||
|
if reason not in {ACCEPTED_ID, REJECTED_ID}:
|
||||||
|
# If the provided reason is not valid, send the user back to
|
||||||
|
# the closure form with a BAD_REQUEST status.
|
||||||
|
return render_template(request, "requests/close.html", context,
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST)
|
||||||
|
|
||||||
|
if not request.user.is_elevated():
|
||||||
|
# If we're closing the request as the user who created it,
|
||||||
|
# the reason should just be a REJECTION.
|
||||||
|
reason = REJECTED_ID
|
||||||
|
|
||||||
|
with db.begin():
|
||||||
|
pkgreq.Closer = request.user
|
||||||
|
pkgreq.Status = reason
|
||||||
|
pkgreq.ClosureComment = comments
|
||||||
|
|
||||||
|
conn = db.ConnectionExecutor(db.get_engine().raw_connection())
|
||||||
|
notify_ = notify.RequestCloseNotification(
|
||||||
|
conn, request.user.ID, pkgreq.ID, pkgreq.status_display())
|
||||||
|
notify_.send()
|
||||||
|
|
||||||
|
return RedirectResponse("/requests", status_code=int(HTTPStatus.SEE_OTHER))
|
||||||
|
|
60
templates/requests/close.html
Normal file
60
templates/requests/close.html
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
{% extends "partials/layout.html" %}
|
||||||
|
|
||||||
|
{% block pageContent %}
|
||||||
|
<div class="box">
|
||||||
|
<h2>{{ "Close Request" | tr }}: {{ pkgreq.PackageBaseName }}</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{{
|
||||||
|
"Use this form to close the request for package base %s%s%s."
|
||||||
|
| tr | format("<strong>", pkgreq.PackageBaseName, "</strong>")
|
||||||
|
| safe
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<em>{{ "Note" | tr }}:</em>
|
||||||
|
{{
|
||||||
|
"The comments field can be left empty. However, it is highly "
|
||||||
|
"recommended to add a comment when rejecting a request."
|
||||||
|
| tr
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form action="/requests/{{ pkgreq.ID }}/close" method="post">
|
||||||
|
<fieldset>
|
||||||
|
<p>
|
||||||
|
<label for="id_reason">{{ "Reason" | tr }}:</label>
|
||||||
|
<select id="id_reason" name="reason">
|
||||||
|
{# Value 2 == "Accepted" status.
|
||||||
|
See aurweb.models.package_request. #}
|
||||||
|
{% if request.user.is_elevated() %}
|
||||||
|
<option value="2">
|
||||||
|
{{ "Accepted" | tr }}
|
||||||
|
</option>
|
||||||
|
{% endif %}
|
||||||
|
{# Value 3 == "Rejected" status.
|
||||||
|
See aurweb.models.package_request. #}
|
||||||
|
<option value="3">
|
||||||
|
{{ "Rejected" | tr }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="id_comments">{{ "Comments" | tr }}:</label>
|
||||||
|
<textarea id="id_comments" name="comments"
|
||||||
|
rows="5" cols="50"></textarea>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<button class="button" type="submit">
|
||||||
|
{{ "Close Request" | tr }}
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -20,7 +20,7 @@ from aurweb.models.package_dependency import PackageDependency
|
||||||
from aurweb.models.package_keyword import PackageKeyword
|
from aurweb.models.package_keyword import PackageKeyword
|
||||||
from aurweb.models.package_notification import PackageNotification
|
from aurweb.models.package_notification import PackageNotification
|
||||||
from aurweb.models.package_relation import PackageRelation
|
from aurweb.models.package_relation import PackageRelation
|
||||||
from aurweb.models.package_request import PackageRequest
|
from aurweb.models.package_request import ACCEPTED_ID, REJECTED_ID, PackageRequest
|
||||||
from aurweb.models.package_vote import PackageVote
|
from aurweb.models.package_vote import PackageVote
|
||||||
from aurweb.models.relation_type import PROVIDES_ID, RelationType
|
from aurweb.models.relation_type import PROVIDES_ID, RelationType
|
||||||
from aurweb.models.request_type import DELETION_ID, RequestType
|
from aurweb.models.request_type import DELETION_ID, RequestType
|
||||||
|
@ -1581,3 +1581,87 @@ def test_pkgbase_request_post_merge_self_error(client: TestClient, user: User,
|
||||||
error = root.xpath('//ul[@class="errorlist"]/li')[0]
|
error = root.xpath('//ul[@class="errorlist"]/li')[0]
|
||||||
expected = "You cannot merge a package base into itself."
|
expected = "You cannot merge a package base into itself."
|
||||||
assert error.text.strip() == expected
|
assert error.text.strip() == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def pkgreq(user: User, package: Package) -> PackageRequest:
|
||||||
|
reqtype = db.query(RequestType).filter(
|
||||||
|
RequestType.ID == DELETION_ID
|
||||||
|
).first()
|
||||||
|
with db.begin():
|
||||||
|
pkgreq = db.create(PackageRequest,
|
||||||
|
RequestType=reqtype,
|
||||||
|
User=user,
|
||||||
|
PackageBase=package.PackageBase,
|
||||||
|
PackageBaseName=package.PackageBase.Name,
|
||||||
|
Comments=str(),
|
||||||
|
ClosureComment=str())
|
||||||
|
yield pkgreq
|
||||||
|
|
||||||
|
|
||||||
|
def test_requests_close(client: TestClient, user: User,
|
||||||
|
pkgreq: PackageRequest):
|
||||||
|
cookies = {"AURSID": user.login(Request(), "testPassword")}
|
||||||
|
with client as request:
|
||||||
|
resp = request.get(f"/requests/{pkgreq.ID}/close", cookies=cookies,
|
||||||
|
allow_redirects=False)
|
||||||
|
assert resp.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
|
||||||
|
def test_requests_close_unauthorized(client: TestClient, maintainer: User,
|
||||||
|
pkgreq: PackageRequest):
|
||||||
|
cookies = {"AURSID": maintainer.login(Request(), "testPassword")}
|
||||||
|
with client as request:
|
||||||
|
resp = request.get(f"/requests/{pkgreq.ID}/close", cookies=cookies,
|
||||||
|
allow_redirects=False)
|
||||||
|
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
|
||||||
|
assert resp.headers.get("location") == "/"
|
||||||
|
|
||||||
|
|
||||||
|
def test_requests_close_post_invalid_reason(client: TestClient, user: User,
|
||||||
|
pkgreq: PackageRequest):
|
||||||
|
cookies = {"AURSID": user.login(Request(), "testPassword")}
|
||||||
|
with client as request:
|
||||||
|
resp = request.post(f"/requests/{pkgreq.ID}/close", data={
|
||||||
|
"reason": 0
|
||||||
|
}, cookies=cookies, allow_redirects=False)
|
||||||
|
assert resp.status_code == int(HTTPStatus.BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
def test_requests_close_post_unauthorized(client: TestClient, maintainer: User,
|
||||||
|
pkgreq: PackageRequest):
|
||||||
|
cookies = {"AURSID": maintainer.login(Request(), "testPassword")}
|
||||||
|
with client as request:
|
||||||
|
resp = request.post(f"/requests/{pkgreq.ID}/close", data={
|
||||||
|
"reason": ACCEPTED_ID
|
||||||
|
}, cookies=cookies, allow_redirects=False)
|
||||||
|
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
|
||||||
|
assert resp.headers.get("location") == "/"
|
||||||
|
|
||||||
|
|
||||||
|
def test_requests_close_post(client: TestClient, user: User,
|
||||||
|
pkgreq: PackageRequest):
|
||||||
|
cookies = {"AURSID": user.login(Request(), "testPassword")}
|
||||||
|
with client as request:
|
||||||
|
resp = request.post(f"/requests/{pkgreq.ID}/close", data={
|
||||||
|
"reason": REJECTED_ID
|
||||||
|
}, cookies=cookies, allow_redirects=False)
|
||||||
|
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
|
||||||
|
|
||||||
|
assert pkgreq.Status == REJECTED_ID
|
||||||
|
assert pkgreq.Closer == user
|
||||||
|
assert pkgreq.ClosureComment == str()
|
||||||
|
|
||||||
|
|
||||||
|
def test_requests_close_post_rejected(client: TestClient, user: User,
|
||||||
|
pkgreq: PackageRequest):
|
||||||
|
cookies = {"AURSID": user.login(Request(), "testPassword")}
|
||||||
|
with client as request:
|
||||||
|
resp = request.post(f"/requests/{pkgreq.ID}/close", data={
|
||||||
|
"reason": REJECTED_ID
|
||||||
|
}, cookies=cookies, allow_redirects=False)
|
||||||
|
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
|
||||||
|
|
||||||
|
assert pkgreq.Status == REJECTED_ID
|
||||||
|
assert pkgreq.Closer == user
|
||||||
|
assert pkgreq.ClosureComment == str()
|
||||||
|
|
Loading…
Add table
Reference in a new issue