This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-release.git
The following commit(s) were added to refs/heads/main by this push:
new 2abe180 Improve form validation in candidate release creation
2abe180 is described below
commit 2abe180c0fce9a5867088fba2a93c3bce8d358e5
Author: Sean B. Palmer <[email protected]>
AuthorDate: Fri Mar 14 21:02:35 2025 +0200
Improve form validation in candidate release creation
---
atr/routes/candidate.py | 49 ++++++++++++++++++++------
atr/routes/vote_policy.py | 11 +++---
atr/static/css/atr.css | 29 ++++++++-------
atr/static/css/bootstrap.custom.css | 10 ++++++
atr/templates/candidate-create.html | 70 ++++++++++++++++++-------------------
bootstrap/custom.scss | 10 ++++++
6 files changed, 114 insertions(+), 65 deletions(-)
diff --git a/atr/routes/candidate.py b/atr/routes/candidate.py
index d426f47..c5ee23e 100644
--- a/atr/routes/candidate.py
+++ b/atr/routes/candidate.py
@@ -22,6 +22,7 @@ import secrets
import quart
import werkzeug.wrappers.response as response
+import wtforms
import asfquart
import asfquart.auth as auth
@@ -30,11 +31,23 @@ import asfquart.session as session
import atr.db as db
import atr.db.models as models
import atr.routes as routes
+import atr.util as util
if asfquart.APP is ...:
raise RuntimeError("APP is not set")
+class ReleaseAddForm(util.QuartFormTyped):
+ committee_name = wtforms.StringField(
+ "Committee", validators=[wtforms.validators.InputRequired("Committee
name is required")]
+ )
+ version = wtforms.StringField("Version",
validators=[wtforms.validators.InputRequired("Version is required")])
+ project_name = wtforms.StringField(
+ "Project name", validators=[wtforms.validators.InputRequired("Project
name is required")]
+ )
+ submit = wtforms.SubmitField("Create release candidate")
+
+
def format_artifact_name(project_name: str, version: str, is_podling: bool =
False) -> str:
"""Format an artifact name according to Apache naming conventions.
@@ -51,21 +64,33 @@ def format_artifact_name(project_name: str, version: str,
is_podling: bool = Fal
# Release functions
-async def release_add_post(session: session.ClientSession, request:
quart.Request) -> response.Response:
+async def release_add_post(session: session.ClientSession, request:
quart.Request) -> str | response.Response:
"""Handle POST request for creating a new release."""
- form = await routes.get_form(request)
- committee_name = form.get("committee_name")
- if not committee_name:
- raise base.ASFQuartException("Committee name is required",
errorcode=400)
+ def not_none(value: str | None) -> str:
+ if value is None:
+ raise ValueError("This field is required")
+ return value
- version = form.get("version")
- if not version:
- raise base.ASFQuartException("Version is required", errorcode=400)
+ form = await ReleaseAddForm.create_form(data=await request.form)
+
+ if not await form.validate():
+ # Get PMC objects for all projects the user is a member of
+ async with db.session() as data:
+ project_list = session.committees + session.projects
+ user_committees = await data.committee(name_in=project_list).all()
+
+ # Return the form with validation errors
+ return await quart.render_template(
+ "candidate-create.html",
+ asf_id=session.uid,
+ user_committees=user_committees,
+ form=form,
+ )
- project_name = form.get("project_name")
- if not project_name:
- raise base.ASFQuartException("Project name is required", errorcode=400)
+ committee_name = form.committee_name.data
+ version = not_none(form.version.data)
+ project_name = not_none(form.project_name.data)
# TODO: Forbid creating a release with an existing project and version
# Create the release record in the database
@@ -134,10 +159,12 @@ async def root_candidate_create() -> response.Response |
str:
user_committees = await data.committee(name_in=project_list).all()
# For GET requests, show the form
+ form = await ReleaseAddForm.create_form()
return await quart.render_template(
"candidate-create.html",
asf_id=web_session.uid,
user_committees=user_committees,
+ form=form,
)
diff --git a/atr/routes/vote_policy.py b/atr/routes/vote_policy.py
index 889f8c9..f447235 100644
--- a/atr/routes/vote_policy.py
+++ b/atr/routes/vote_policy.py
@@ -18,18 +18,17 @@
"""vote_policy.py"""
import quart
-import quart_wtf
import werkzeug.wrappers.response as response
import wtforms
+import asfquart.base as base
import asfquart.session as session
import atr.db as db
import atr.routes as routes
-from asfquart import base
-from asfquart.base import ASFQuartException
+import atr.util as util
-class VotePolicyForm(quart_wtf.QuartForm):
+class VotePolicyForm(util.QuartFormTyped):
project_name = wtforms.HiddenField("project_name")
mailto_addresses = wtforms.StringField(
"Email",
@@ -56,7 +55,7 @@ async def root_vote_policy_edit(vote_policy_id: str) ->
response.Response | str:
async with db.session() as data:
vote_policy = await data.vote_policy(id=int(vote_policy_id)).demand(
- ASFQuartException("Vote policy not found", 404)
+ base.ASFQuartException("Vote policy not found", 404)
)
form = await VotePolicyForm.create_form()
@@ -65,8 +64,8 @@ async def root_vote_policy_edit(vote_policy_id: str) ->
response.Response | str:
form.process(obj=vote_policy)
if await form.validate_on_submit():
+ # return await add_voting_policy(web_session, form)
return ""
- # return await add_voting_policy(web_session, form) # pyright: ignore
[reportArgumentType]
# For GET requests, show the form
return await quart.render_template(
diff --git a/atr/static/css/atr.css b/atr/static/css/atr.css
index 59a2373..6288cb3 100644
--- a/atr/static/css/atr.css
+++ b/atr/static/css/atr.css
@@ -28,8 +28,6 @@ body {
}
input, textarea, select, option {
- border-width: 2px !important;
- border-color: #cccccc !important;
font-size: 17px !important;
font-weight: 425 !important;
}
@@ -49,10 +47,26 @@ label[for] {
cursor: pointer;
}
+input,
+textarea {
+ font-family: monospace;
+ padding: 0.5rem;
+}
+
+textarea {
+ width: 100%;
+ min-height: 200px;
+}
+
select, input[type="file"] {
padding: 6px 12px;
}
+input:not([type="submit"]), textarea, select, option {
+ border-width: 2px !important;
+ border-color: #cccccc !important;
+}
+
a {
font-weight: 450;
}
@@ -153,17 +167,6 @@ footer p {
margin-bottom: 0;
}
-input,
-textarea {
- font-family: monospace;
- padding: 0.5rem;
-}
-
-textarea {
- width: 100%;
- min-height: 200px;
-}
-
summary {
cursor: pointer;
}
diff --git a/atr/static/css/bootstrap.custom.css
b/atr/static/css/bootstrap.custom.css
index 295092b..9a0aada 100644
--- a/atr/static/css/bootstrap.custom.css
+++ b/atr/static/css/bootstrap.custom.css
@@ -11455,6 +11455,16 @@ th {
font-weight: 475;
}
+.btn:disabled {
+ background-color: #cccccc;
+ border-color: #cccccc;
+}
+
+.btn-primary:disabled {
+ background-color: #004477;
+ border-color: #004477;
+}
+
.btn-primary {
background-color: #004477;
border-color: #004477;
diff --git a/atr/templates/candidate-create.html
b/atr/templates/candidate-create.html
index 106d30c..bcfa835 100644
--- a/atr/templates/candidate-create.html
+++ b/atr/templates/candidate-create.html
@@ -23,11 +23,14 @@
<form method="post"
enctype="multipart/form-data"
class="striking py-4 px-5">
+ <input type="hidden" name="form_type" value="single" />
+ {{ form.hidden_tag() }}
<div class="mb-3 pb-3 row border-bottom">
- <label for="committee_name" class="col-sm-3 col-form-label
text-sm-end">Committee:</label>
+ <label for="{{ form.committee_name.id }}"
+ class="col-sm-3 col-form-label text-sm-end">{{
form.committee_name.label.text }}:</label>
<div class="col-sm-8">
- <select id="committee_name"
- name="committee_name"
+ <select id="{{ form.committee_name.id }}"
+ name="{{ form.committee_name.name }}"
class="mb-2 form-select"
required>
<option value="">Select a committee...</option>
@@ -35,39 +38,36 @@
<option value="{{ committee.name }}">{{ committee.display_name
}}</option>
{% endfor %}
</select>
- {% if not user_committees %}
- <p class="text-danger">You must be a (P)PMC member or committer to
submit a release candidate.</p>
- {% endif %}
+ {% if form.committee_name.errors -%}<span class="error-message">{{
form.committee_name.errors[0] }}</span>{%- endif %}
+ {% if not user_committees %}
+ <p class="text-danger">You must be a (P)PMC member or committer to
submit a release candidate.</p>
+ {% endif %}
+ </div>
</div>
- </div>
- <div class="mb-3 pb-3 row border-bottom">
- <label for="version" class="col-sm-3 col-form-label
text-sm-end">Version:</label>
- <div class="col-sm-8">
- <input type="text" id="version" name="version" class="form-control"
required />
- </div>
- </div>
+ <div class="mb-3 pb-3 row border-bottom">
+ <label for="{{ form.version.id }}"
+ class="col-sm-3 col-form-label text-sm-end">{{
form.version.label.text }}:</label>
+ <div class="col-sm-8">
+ {{ form.version(class_="form-control") }}
+ {% if form.version.errors -%}<span class="error-message">{{
form.version.errors[0] }}</span>{%- endif %}
+ </div>
+ </div>
- <div class="mb-3 pb-3 row border-bottom">
- <label for="project_name" class="col-sm-3 col-form-label
text-sm-end">Project name:</label>
- <div class="col-sm-8">
- <!-- TODO: Add a dropdown for the project name, plus "add new project"
-->
- <input type="text"
- id="project_name"
- name="project_name"
- class="form-control"
- required />
- <!-- TODO: Add a subproject checkbox -->
- <small class="text-muted">This can be the same as the committee name,
but may be different if e.g. this is a subproject.</small>
- </div>
- </div>
+ <div class="mb-3 pb-3 row border-bottom">
+ <label for="{{ form.project_name.id }}"
+ class="col-sm-3 col-form-label text-sm-end">{{
form.project_name.label.text }}:</label>
+ <div class="col-sm-8">
+ <!-- TODO: Add a dropdown for the project name, plus "add new
project" -->
+ {{ form.project_name(class_="form-control") }}
+ {% if form.project_name.errors -%}<span class="error-message">{{
form.project_name.errors[0] }}</span>{%- endif %}
+ <!-- TODO: Add a subproject checkbox -->
+ <small class="text-muted">This can be the same as the committee
name, but may be different if e.g. this is a subproject.</small>
+ </div>
+ </div>
- <div class="row">
- <div class="col-sm-9 offset-sm-3">
- <button type="submit"
- class="btn btn-primary mt-2"
- {% if not user_committees %}disabled{% endif %}>Create
release</button>
- </div>
- </div>
- </form>
-{% endblock content %}
+ <div class="row">
+ <div class="col-sm-9 offset-sm-3">{{ form.submit(class_="btn
btn-primary mt-3") }}</div>
+ </div>
+ </form>
+ {% endblock content %}
diff --git a/bootstrap/custom.scss b/bootstrap/custom.scss
index 5c076f4..84e00fe 100644
--- a/bootstrap/custom.scss
+++ b/bootstrap/custom.scss
@@ -76,6 +76,16 @@ th {
font-weight: 475;
}
+.btn:disabled {
+ background-color: #cccccc;
+ border-color: #cccccc;
+}
+
+.btn-primary:disabled {
+ background-color: #004477;
+ border-color: #004477;
+}
+
.btn-primary {
background-color: #004477;
border-color: #004477;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]