You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by sb...@apache.org on 2025/06/11 19:01:24 UTC
(tooling-trusted-release) branch main updated: Make the code more aware of dormant and retired projects
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 21a5f9a Make the code more aware of dormant and retired projects
21a5f9a is described below
commit 21a5f9a03cdae03ebc7f7c87c0ccf1d96ce0234c
Author: Sean B. Palmer <se...@miscoranda.com>
AuthorDate: Wed Jun 11 19:57:35 2025 +0100
Make the code more aware of dormant and retired projects
- Expands Project.is_retired to encompass dormancy
- Documents that Project.is_retired status is not permanent
- Sets Project.is_retired based on wider criteria
- Reflects dormant and retired projects through the interface
- Creates a list of committees which cannot have releases
- Disallows release creation for committees which cannot have releases
---
atr/blueprints/admin/admin.py | 22 ++-
atr/construct.py | 4 +-
atr/db/__init__.py | 5 +-
atr/db/models.py | 1 +
atr/routes/committees.py | 6 +-
atr/routes/preview.py | 2 +-
atr/routes/projects.py | 8 +-
atr/routes/release.py | 4 +-
atr/routes/start.py | 4 +-
atr/ssh.py | 4 +-
atr/templates/committee-directory.html | 2 +-
atr/templates/project-view.html | 270 +++++++++++++++++----------------
atr/user.py | 4 +-
atr/util.py | 10 ++
14 files changed, 191 insertions(+), 155 deletions(-)
diff --git a/atr/blueprints/admin/admin.py b/atr/blueprints/admin/admin.py
index ebb79f8..608e3dd 100644
--- a/atr/blueprints/admin/admin.py
+++ b/atr/blueprints/admin/admin.py
@@ -677,6 +677,18 @@ async def _process_undiscovered(data: db.Session) -> tuple[int, int]:
return added_count, updated_count
+def _project_is_retired(pmc: models.Committee, project_name: str, project_status: apache.ProjectStatus) -> bool:
+ if util.committee_without_releases(pmc.name):
+ return True
+ elif ("_dormant_" in project_name) or project_status.name.endswith("(Dormant)"):
+ # TODO: Dormant is not quite the same thing as retired
+ # But we can act on the assumption that projects can be unretired
+ # The Project.is_retired setting is not permanent
+ # We have to check the names because projects.json does not have a dormant property
+ return True
+ return False
+
+
def _session_data(
ldap_data: dict[str, Any],
new_uid: str,
@@ -862,11 +874,14 @@ async def _update_projects(data: db.Session, projects: apache.ProjectsData) -> t
continue
project_model = await data.project(name=project_name).get()
+ # Check whether the project is retired, whether temporarily or otherwise
+ is_retired = _project_is_retired(pmc, project_name, project_status)
if not project_model:
- project_model = models.Project(name=project_name, committee=pmc)
+ project_model = models.Project(name=project_name, committee=pmc, is_retired=is_retired)
data.add(project_model)
added_count += 1
else:
+ project_model.is_retired = is_retired
updated_count += 1
project_model.full_name = project_status.name
@@ -874,11 +889,6 @@ async def _update_projects(data: db.Session, projects: apache.ProjectsData) -> t
project_model.description = project_status.description
project_model.programming_languages = project_status.programming_language
- # TODO: find a better way to declare a project retired
- # right now we assume that a project is retired if its assigned to the attic PMC
- # maybe make that information configurable
- project_model.is_retired = pmc.name == "attic"
-
return added_count, updated_count
diff --git a/atr/construct.py b/atr/construct.py
index 413773c..929a047 100644
--- a/atr/construct.py
+++ b/atr/construct.py
@@ -83,7 +83,7 @@ async def announce_release_body(body: str, options: AnnounceReleaseOptions) -> s
async def announce_release_default(project_name: str) -> str:
async with db.session() as data:
- project = await data.project(name=project_name, _release_policy=True).demand(
+ project = await data.project(name=project_name, is_retired=False, _release_policy=True).demand(
RuntimeError(f"Project {project_name} not found")
)
@@ -137,7 +137,7 @@ async def start_vote_body(body: str, options: StartVoteOptions) -> str:
async def start_vote_default(project_name: str) -> str:
async with db.session() as data:
- project = await data.project(name=project_name, _release_policy=True).demand(
+ project = await data.project(name=project_name, is_retired=False, _release_policy=True).demand(
RuntimeError(f"Project {project_name} not found")
)
diff --git a/atr/db/__init__.py b/atr/db/__init__.py
index c1cfd31..a199e94 100644
--- a/atr/db/__init__.py
+++ b/atr/db/__init__.py
@@ -272,6 +272,7 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
full_name: Opt[str] = NOT_SET,
committee_name: Opt[str] = NOT_SET,
release_policy_id: Opt[int] = NOT_SET,
+ is_retired: Opt[bool] = NOT_SET,
_committee: bool = True,
_releases: bool = False,
_distribution_channels: bool = False,
@@ -289,6 +290,8 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
query = query.where(models.Project.committee_name == committee_name)
if is_defined(release_policy_id):
query = query.where(models.Project.release_policy_id == release_policy_id)
+ if is_defined(is_retired):
+ query = query.where(models.Project.is_retired == is_retired)
if _committee:
query = query.options(select_in_load(models.Project.committee))
@@ -581,7 +584,7 @@ async def create_async_engine(app_config: type[config.AppConfig]) -> sqlalchemy.
async def get_project_release_policy(data: Session, project_name: str) -> models.ReleasePolicy | None:
"""Fetch the ReleasePolicy for a project."""
- project = await data.project(name=project_name, _release_policy=True).demand(
+ project = await data.project(name=project_name, is_retired=False, _release_policy=True).demand(
RuntimeError(f"Project {project_name} not found")
)
return project.release_policy
diff --git a/atr/db/models.py b/atr/db/models.py
index 6aec0f2..1483951 100644
--- a/atr/db/models.py
+++ b/atr/db/models.py
@@ -170,6 +170,7 @@ class Project(sqlmodel.SQLModel, table=True):
# We always include "Apache" in the full_name
full_name: str | None = sqlmodel.Field(default=None)
+ # The is_retired flag is not permanent, and is also used for dormant projects
is_retired: bool = sqlmodel.Field(default=False)
super_project_name: str | None = sqlmodel.Field(default=None, foreign_key="project.name")
# NOTE: Neither "Project" | None nor "Project | None" works
diff --git a/atr/routes/committees.py b/atr/routes/committees.py
index 4570282..9066f10 100644
--- a/atr/routes/committees.py
+++ b/atr/routes/committees.py
@@ -32,7 +32,11 @@ async def directory() -> str:
"""Main committee directory page."""
async with db.session() as data:
committees = await data.committee(_projects=True).order_by(models.Committee.name).all()
- return await template.render("committee-directory.html", committees=committees)
+ return await template.render(
+ "committee-directory.html",
+ committees=committees,
+ committee_without_releases=util.committee_without_releases,
+ )
@routes.public("/committees/<name>")
diff --git a/atr/routes/preview.py b/atr/routes/preview.py
index d33193c..11865e7 100644
--- a/atr/routes/preview.py
+++ b/atr/routes/preview.py
@@ -111,7 +111,7 @@ async def delete(session: routes.CommitterSession) -> response.Response:
# Check that the user has access to the project
async with db.session() as data:
- project = await data.project(name=project_name).get()
+ project = await data.project(name=project_name, is_retired=False).get()
if not project or not any(
(
(c.name == project.committee_name)
diff --git a/atr/routes/projects.py b/atr/routes/projects.py
index 42fb06e..06ae117 100644
--- a/atr/routes/projects.py
+++ b/atr/routes/projects.py
@@ -159,7 +159,9 @@ async def delete(session: routes.CommitterSession) -> response.Response:
return await session.redirect(projects, error="Missing project name for deletion.")
async with db.session() as data:
- project = await data.project(name=project_name, _releases=True, _distribution_channels=True).get()
+ project = await data.project(
+ name=project_name, is_retired=False, _releases=True, _distribution_channels=True
+ ).get()
if not project:
return await session.redirect(projects, error=f"Project '{project_name}' not found.")
@@ -205,7 +207,7 @@ async def select(session: routes.CommitterSession) -> str:
if session.uid:
async with db.session() as data:
# TODO: Move this filtering logic somewhere else
- all_projects = await data.project(_committee=True).all()
+ all_projects = await data.project(is_retired=False, _committee=True).all()
user_projects = [
p
for p in all_projects
@@ -410,6 +412,8 @@ async def _project_add(form: AddFormProtocol, asf_id: str) -> response.Response:
super_project = None
async with db.session() as data:
# Get the base project to derive from
+ # We're allowing derivation from a retired project here
+ # TODO: Should we disallow this instead?
committee_projects = await data.project(committee_name=committee_name, _committee=True).all()
for committee_project in committee_projects:
if label.startswith(committee_project.name + "-"):
diff --git a/atr/routes/release.py b/atr/routes/release.py
index 655f796..0cbff53 100644
--- a/atr/routes/release.py
+++ b/atr/routes/release.py
@@ -75,7 +75,7 @@ async def bulk_status(session: routes.CommitterSession, task_id: int) -> str | r
async def finished(project_name: str) -> str:
"""View all finished releases for a project."""
async with db.session() as data:
- project = await data.project(name=project_name).demand(
+ project = await data.project(name=project_name, is_retired=False).demand(
base.ASFQuartException(f"Project {project_name} not found", errorcode=404)
)
@@ -126,7 +126,7 @@ async def select(session: routes.CommitterSession, project_name: str) -> str:
await session.check_access(project_name)
async with db.session() as data:
- project = await data.project(name=project_name, _releases=True).demand(
+ project = await data.project(name=project_name, is_retired=False, _releases=True).demand(
base.ASFQuartException(f"Project {project_name} not found", errorcode=404)
)
releases = await project.releases_in_progress
diff --git a/atr/routes/start.py b/atr/routes/start.py
index e1e684f..58397fd 100644
--- a/atr/routes/start.py
+++ b/atr/routes/start.py
@@ -50,7 +50,7 @@ async def create_release_draft(project_name: str, version: str, asf_uid: str) ->
# Get the project from the project name
async with db.session() as data:
async with data.begin():
- project = await data.project(name=project_name, _committee=True).get()
+ project = await data.project(name=project_name, is_retired=False, _committee=True).get()
if not project:
raise routes.FlashError(f"Project {project_name} not found")
@@ -104,7 +104,7 @@ async def selected(session: routes.CommitterSession, project_name: str) -> respo
await session.check_access(project_name)
async with db.session() as data:
- project = await data.project(name=project_name).demand(
+ project = await data.project(name=project_name, is_retired=False).demand(
base.ASFQuartException(f"Project {project_name} not found", errorcode=404)
)
diff --git a/atr/ssh.py b/atr/ssh.py
index 45f5987..a09fcbf 100644
--- a/atr/ssh.py
+++ b/atr/ssh.py
@@ -284,7 +284,7 @@ async def _step_04_command_validate(
ssh_uid = process.get_extra_info("username")
async with db.session() as data:
- project = await data.project(name=path_project, _committee=True).get()
+ project = await data.project(name=path_project, is_retired=False, _committee=True).get()
if project is None:
# Projects are public, so existence information is public
return _fail(process, f"Project '{path_project}' does not exist", None)
@@ -540,7 +540,7 @@ async def _step_07c_ensure_release_object_for_write(
name=models.release_name(project_name, version_name), _committee=True
).get()
if release is None:
- project = await data.project(name=project_name, _committee=True).demand(
+ project = await data.project(name=project_name, is_retired=False, _committee=True).demand(
RuntimeError("Project not found after validation")
)
if version_name_error := util.version_name_error(version_name):
diff --git a/atr/templates/committee-directory.html b/atr/templates/committee-directory.html
index 7401509..ad69221 100644
--- a/atr/templates/committee-directory.html
+++ b/atr/templates/committee-directory.html
@@ -152,7 +152,7 @@
{# Add an else clause here if we decide to show an alternative to an empty card #}
{% endif %}
</div>
- {% if current_user and is_part %}
+ {% if current_user and is_part and (not committee_without_releases(committee.name)) %}
<a href="{{ as_url(routes.projects.add_project, committee_name=committee.name) }}"
title="Create a project for {{ committee.display_name }}"
class="text-decoration-none d-block mt-4 mb-3">
diff --git a/atr/templates/project-view.html b/atr/templates/project-view.html
index f89862f..d3bc960 100644
--- a/atr/templates/project-view.html
+++ b/atr/templates/project-view.html
@@ -65,159 +65,161 @@
</div>
</div>
- <div class="card mb-4">
- <div class="card-header bg-light d-flex justify-content-between align-items-center">
- <h3 class="mb-0">Release policy</h3>
- </div>
- <div class="card-body">
- {% if can_edit and policy_form %}
- <form method="post"
- action="{{ as_url(routes.projects.view, name=project.name) }}"
- class="atr-canary py-4 px-5"
- novalidate>
- {{ policy_form.hidden_tag() if policy_form.hidden_tag }}
- {{ policy_form.project_name(value=project.name) }}
- {{ policy_form.default_start_vote_template_hash() }}
- {{ policy_form.default_announce_release_template_hash() }}
- {{ policy_form.default_min_hours_value_at_render() }}
+ {% if not project.is_retired %}
+ <div class="card mb-4">
+ <div class="card-header bg-light d-flex justify-content-between align-items-center">
+ <h3 class="mb-0">Release policy</h3>
+ </div>
+ <div class="card-body">
+ {% if can_edit and policy_form %}
+ <form method="post"
+ action="{{ as_url(routes.projects.view, name=project.name) }}"
+ class="atr-canary py-4 px-5"
+ novalidate>
+ {{ policy_form.hidden_tag() if policy_form.hidden_tag }}
+ {{ policy_form.project_name(value=project.name) }}
+ {{ policy_form.default_start_vote_template_hash() }}
+ {{ policy_form.default_announce_release_template_hash() }}
+ {{ policy_form.default_min_hours_value_at_render() }}
- <div class="mb-3 pb-3 row border-bottom">
- {{ forms.label(policy_form.mailto_addresses.entries[0], col="md3") }}
- <div class="col-sm-8">
- {{ forms.widget(policy_form.mailto_addresses.entries[0]) }}
- {{ forms.errors(policy_form.mailto_addresses.entries[0]) }}
- {{ forms.description(policy_form.mailto_addresses.entries[0]) }}
+ <div class="mb-3 pb-3 row border-bottom">
+ {{ forms.label(policy_form.mailto_addresses.entries[0], col="md3") }}
+ <div class="col-sm-8">
+ {{ forms.widget(policy_form.mailto_addresses.entries[0]) }}
+ {{ forms.errors(policy_form.mailto_addresses.entries[0]) }}
+ {{ forms.description(policy_form.mailto_addresses.entries[0]) }}
+ </div>
</div>
- </div>
- <div class="mb-3 pb-3 row border-bottom">
- {{ forms.label(policy_form.manual_vote, col="md3-high") }}
- <div class="col-sm-8">
- <div class="form-check">
- {{ forms.widget(policy_form.manual_vote, classes="form-check-input", boolean_label="Enable") }}
- {{ forms.errors(policy_form.manual_vote, classes="invalid-feedback d-block") }}
+ <div class="mb-3 pb-3 row border-bottom">
+ {{ forms.label(policy_form.manual_vote, col="md3-high") }}
+ <div class="col-sm-8">
+ <div class="form-check">
+ {{ forms.widget(policy_form.manual_vote, classes="form-check-input", boolean_label="Enable") }}
+ {{ forms.errors(policy_form.manual_vote, classes="invalid-feedback d-block") }}
+ </div>
+ {{ forms.description(policy_form.manual_vote) }}
</div>
- {{ forms.description(policy_form.manual_vote) }}
</div>
- </div>
- <div class="mb-3 pb-3 row border-bottom">
- {{ forms.label(policy_form.min_hours, col="md3") }}
- <div class="col-sm-8">
- {{ forms.widget(policy_form.min_hours) }}
- {{ forms.errors(policy_form.min_hours) }}
- {{ forms.description(policy_form.min_hours) }}
+ <div class="mb-3 pb-3 row border-bottom">
+ {{ forms.label(policy_form.min_hours, col="md3") }}
+ <div class="col-sm-8">
+ {{ forms.widget(policy_form.min_hours) }}
+ {{ forms.errors(policy_form.min_hours) }}
+ {{ forms.description(policy_form.min_hours) }}
+ </div>
</div>
- </div>
- <div class="mb-3 pb-3 row border-bottom">
- {{ forms.label(policy_form.release_checklist, col="md3") }}
- <div class="col-sm-8">
- {{ forms.widget(policy_form.release_checklist, rows="10", classes="form-control font-monospace") }}
- {{ forms.errors(policy_form.release_checklist) }}
- {{ forms.description(policy_form.release_checklist) }}
+ <div class="mb-3 pb-3 row border-bottom">
+ {{ forms.label(policy_form.release_checklist, col="md3") }}
+ <div class="col-sm-8">
+ {{ forms.widget(policy_form.release_checklist, rows="10", classes="form-control font-monospace") }}
+ {{ forms.errors(policy_form.release_checklist) }}
+ {{ forms.description(policy_form.release_checklist) }}
+ </div>
</div>
- </div>
- <div class="mb-3 pb-3 row border-bottom">
- {{ forms.label(policy_form.start_vote_template, col="md3") }}
- <div class="col-sm-8">
- {{ forms.widget(policy_form.start_vote_template, rows="10", classes="form-control font-monospace") }}
- {{ forms.errors(policy_form.start_vote_template) }}
- {{ forms.description(policy_form.start_vote_template) }}
+ <div class="mb-3 pb-3 row border-bottom">
+ {{ forms.label(policy_form.start_vote_template, col="md3") }}
+ <div class="col-sm-8">
+ {{ forms.widget(policy_form.start_vote_template, rows="10", classes="form-control font-monospace") }}
+ {{ forms.errors(policy_form.start_vote_template) }}
+ {{ forms.description(policy_form.start_vote_template) }}
+ </div>
</div>
- </div>
- <div class="mb-3 pb-3 row border-bottom">
- {{ forms.label(policy_form.announce_release_template, col="md3") }}
- <div class="col-sm-8">
- {{ forms.widget(policy_form.announce_release_template, rows="10", classes="form-control font-monospace") }}
- {{ forms.errors(policy_form.announce_release_template) }}
- {{ forms.description(policy_form.announce_release_template) }}
+ <div class="mb-3 pb-3 row border-bottom">
+ {{ forms.label(policy_form.announce_release_template, col="md3") }}
+ <div class="col-sm-8">
+ {{ forms.widget(policy_form.announce_release_template, rows="10", classes="form-control font-monospace") }}
+ {{ forms.errors(policy_form.announce_release_template) }}
+ {{ forms.description(policy_form.announce_release_template) }}
+ </div>
</div>
- </div>
- <div class="mb-3 pb-3 row border-bottom">
- {{ forms.label(policy_form.pause_for_rm, col="md3-high") }}
- <div class="col-sm-8">
- <div class="form-check">
- {{ forms.widget(policy_form.pause_for_rm, classes="form-check-input", boolean_label="Enable") }}
- {{ forms.errors(policy_form.pause_for_rm, classes="invalid-feedback d-block") }}
+ <div class="mb-3 pb-3 row border-bottom">
+ {{ forms.label(policy_form.pause_for_rm, col="md3-high") }}
+ <div class="col-sm-8">
+ <div class="form-check">
+ {{ forms.widget(policy_form.pause_for_rm, classes="form-check-input", boolean_label="Enable") }}
+ {{ forms.errors(policy_form.pause_for_rm, classes="invalid-feedback d-block") }}
+ </div>
+ {{ forms.description(policy_form.pause_for_rm) }}
</div>
- {{ forms.description(policy_form.pause_for_rm) }}
</div>
- </div>
- <div class="row">
- <div class="col-sm-9 offset-sm-3">{{ policy_form.submit_policy(class_="btn btn-primary mt-2") }}</div>
- </div>
- </form>
- {% elif project.release_policy or project.name %}
- <div class="card h-100 border">
- <div class="card-body">
- <table class="table mb-0">
- <tbody>
- <tr>
- <th class="border-0 w-25">Email</th>
- <td class="text-break border-0">
- {% if project.policy_mailto_addresses %}
- <a href="mailto:{{ project.policy_mailto_addresses[0] }}">{{ project.policy_mailto_addresses[0] }}</a>
- {% else %}
- Not set
- {% endif %}
- </td>
- </tr>
- <tr>
- <th class="border-0">Manual vote process</th>
- <td class="text-break border-0">{{ project.policy_manual_vote }}</td>
- </tr>
- <tr>
- <th class="border-0">Minimum voting period</th>
- <td class="text-break border-0">{{ project.policy_min_hours }}h</td>
- </tr>
- <tr>
- <th class="border-0">Release checklist</th>
- <td class="text-break border-0">
- {% if project.policy_release_checklist|length > 0 %}
- <textarea readonly class="form-control font-monospace" rows="10">{{ project.policy_release_checklist }}</textarea>
- {% else %}
- None
- {% endif %}
- </td>
- </tr>
- <tr>
- <th class="border-0">Start vote template</th>
- <td class="text-break border-0">
- {% if project.policy_start_vote_template|length > 0 %}
- <textarea readonly class="form-control font-monospace" rows="10">{{ project.policy_start_vote_template }}</textarea>
- {% else %}
- None (System default will be used)
- {% endif %}
- </td>
- </tr>
- <tr>
- <th class="border-0">Announce release template</th>
- <td class="text-break border-0">
- {% if project.policy_announce_release_template|length > 0 %}
- <textarea readonly class="form-control font-monospace" rows="10">{{ project.policy_announce_release_template }}</textarea>
- {% else %}
- None (System default will be used)
- {% endif %}
- </td>
- </tr>
- <tr>
- <th class="border-0">Pause for RM</th>
- <td class="text-break border-0">{{ project.policy_pause_for_rm }}</td>
- </tr>
- </tbody>
- </table>
+ <div class="row">
+ <div class="col-sm-9 offset-sm-3">{{ policy_form.submit_policy(class_="btn btn-primary mt-2") }}</div>
+ </div>
+ </form>
+ {% elif project.release_policy or project.name %}
+ <div class="card h-100 border">
+ <div class="card-body">
+ <table class="table mb-0">
+ <tbody>
+ <tr>
+ <th class="border-0 w-25">Email</th>
+ <td class="text-break border-0">
+ {% if project.policy_mailto_addresses %}
+ <a href="mailto:{{ project.policy_mailto_addresses[0] }}">{{ project.policy_mailto_addresses[0] }}</a>
+ {% else %}
+ Not set
+ {% endif %}
+ </td>
+ </tr>
+ <tr>
+ <th class="border-0">Manual vote process</th>
+ <td class="text-break border-0">{{ project.policy_manual_vote }}</td>
+ </tr>
+ <tr>
+ <th class="border-0">Minimum voting period</th>
+ <td class="text-break border-0">{{ project.policy_min_hours }}h</td>
+ </tr>
+ <tr>
+ <th class="border-0">Release checklist</th>
+ <td class="text-break border-0">
+ {% if project.policy_release_checklist|length > 0 %}
+ <textarea readonly class="form-control font-monospace" rows="10">{{ project.policy_release_checklist }}</textarea>
+ {% else %}
+ None
+ {% endif %}
+ </td>
+ </tr>
+ <tr>
+ <th class="border-0">Start vote template</th>
+ <td class="text-break border-0">
+ {% if project.policy_start_vote_template|length > 0 %}
+ <textarea readonly class="form-control font-monospace" rows="10">{{ project.policy_start_vote_template }}</textarea>
+ {% else %}
+ None (System default will be used)
+ {% endif %}
+ </td>
+ </tr>
+ <tr>
+ <th class="border-0">Announce release template</th>
+ <td class="text-break border-0">
+ {% if project.policy_announce_release_template|length > 0 %}
+ <textarea readonly class="form-control font-monospace" rows="10">{{ project.policy_announce_release_template }}</textarea>
+ {% else %}
+ None (System default will be used)
+ {% endif %}
+ </td>
+ </tr>
+ <tr>
+ <th class="border-0">Pause for RM</th>
+ <td class="text-break border-0">{{ project.policy_pause_for_rm }}</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
</div>
- </div>
- {% else %}
- <div>None defined.</div>
- {% endif %}
+ {% else %}
+ <div>None defined.</div>
+ {% endif %}
+ </div>
</div>
- </div>
+ {% endif %}
{% if (is_committee_member or is_admin) %}
{% if candidate_drafts|length > 0 %}
diff --git a/atr/user.py b/atr/user.py
index 16b37bc..4039b83 100644
--- a/atr/user.py
+++ b/atr/user.py
@@ -62,7 +62,9 @@ async def projects(uid: str, committee_only: bool = False, super_project: bool =
user_projects: list[models.Project] = []
async with db.session() as data:
# Must have releases, because this is used in candidate_drafts
- projects = await data.project(_committee=True, _releases=True, _super_project=super_project).all()
+ projects = await data.project(
+ is_retired=False, _committee=True, _releases=True, _super_project=super_project
+ ).all()
for p in projects:
if p.committee is None:
continue
diff --git a/atr/util.py b/atr/util.py
index 25aec42..250c60d 100644
--- a/atr/util.py
+++ b/atr/util.py
@@ -52,6 +52,12 @@ import atr.user as user
F = TypeVar("F", bound="QuartFormTyped")
T = TypeVar("T")
+_COMMITTEES_WITHOUT_RELEASES: Final[set[str]] = {
+ "attic",
+ "comdev",
+ "logodev",
+ "whimsy",
+}
_LOGGER: Final = logging.getLogger(__name__)
@@ -154,6 +160,10 @@ def chmod_directories(path: pathlib.Path, permissions: int = 0o755) -> None:
os.chmod(dir_path, permissions)
+def committee_without_releases(committee_name: str) -> bool:
+ return committee_name in _COMMITTEES_WITHOUT_RELEASES
+
+
def compute_sha3_256(file_data: bytes) -> str:
"""Compute SHA3-256 hash of file data."""
return hashlib.sha3_256(file_data).hexdigest()
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org