diff --git a/tests/unit/manage/test_views.py b/tests/unit/manage/test_views.py index fb16a5a325ec..e8857f1e2d36 100644 --- a/tests/unit/manage/test_views.py +++ b/tests/unit/manage/test_views.py @@ -63,6 +63,7 @@ from warehouse.packaging.models import ( File, JournalEntry, + LifecycleStatus, Project, Release, Role, @@ -96,7 +97,6 @@ class TestManageUnverifiedAccount: - def test_manage_account(self, monkeypatch): user_service = pretend.stub() name = pretend.stub() @@ -2603,11 +2603,12 @@ class TestManageProjectSettings: @pytest.mark.parametrize("enabled", [False, True]) def test_manage_project_settings(self, enabled, monkeypatch): request = pretend.stub(organization_access=enabled) - project = pretend.stub(organization=None) + project = pretend.stub(organization=None, lifecycle_status=None) view = views.ManageProjectSettingsViews(project, request) form = pretend.stub() view.transfer_organization_project_form_class = lambda *a, **kw: form view.add_alternate_repository_form_class = lambda *a, **kw: form + view.set_project_status_form_class = lambda *a, **kw: form user_organizations = pretend.call_recorder( lambda *a, **kw: { @@ -2622,21 +2623,91 @@ def test_manage_project_settings(self, enabled, monkeypatch): "project": project, "MAX_FILESIZE": MAX_FILESIZE, "MAX_PROJECT_SIZE": MAX_PROJECT_SIZE, + "set_project_status_form": form, "transfer_organization_project_form": form, "add_alternate_repository_form_class": form, } + def test_archive_project(self, db_request): + project = ProjectFactory.create(name="foo") + + db_request.POST = MultiDict({"project_status": "archived"}) + db_request.route_path = pretend.call_recorder(lambda *a, **kw: "/the-redirect") + db_request.session = pretend.stub( + flash=pretend.call_recorder(lambda *a, **kw: None) + ) + + settings_views = views.ManageProjectSettingsViews(project, db_request) + result = settings_views.set_project_status() + + assert isinstance(result, HTTPSeeOther) + assert result.headers["Location"] == "/the-redirect" + assert db_request.session.flash.calls == [ + pretend.call("Set project status to 'archived'", queue="success") + ] + assert db_request.route_path.calls == [ + pretend.call("manage.project.settings", project_name="foo") + ] + assert project.lifecycle_status == LifecycleStatus.Archived + + def test_unarchive_project(self, db_request): + project = ProjectFactory.create( + name="foo", lifecycle_status=LifecycleStatus.Archived + ) + + db_request.POST = MultiDict({"project_status": ""}) + db_request.route_path = pretend.call_recorder(lambda *a, **kw: "/the-redirect") + db_request.session = pretend.stub( + flash=pretend.call_recorder(lambda *a, **kw: None) + ) + + settings_views = views.ManageProjectSettingsViews(project, db_request) + result = settings_views.set_project_status() + + assert isinstance(result, HTTPSeeOther) + assert result.headers["Location"] == "/the-redirect" + assert db_request.session.flash.calls == [ + pretend.call("Set project status to 'None'", queue="success") + ] + assert db_request.route_path.calls == [ + pretend.call("manage.project.settings", project_name="foo") + ] + assert project.lifecycle_status is None + + def test_disallowed_lifecycle_status_change(self, db_request): + project = ProjectFactory.create(name="foo", lifecycle_status="quarantine-enter") + + db_request.POST = MultiDict({"project_status": "quarantine-exit"}) + db_request.route_path = pretend.call_recorder(lambda *a, **kw: "/the-redirect") + db_request.session = pretend.stub( + flash=pretend.call_recorder(lambda *a, **kw: None) + ) + + settings_views = views.ManageProjectSettingsViews(project, db_request) + result = settings_views.set_project_status() + + assert isinstance(result, HTTPSeeOther) + assert result.headers["Location"] == "/the-redirect" + assert db_request.session.flash.calls == [ + pretend.call("Invalid project status", queue="error") + ] + assert db_request.route_path.calls == [ + pretend.call("manage.project.settings", project_name="foo") + ] + assert project.lifecycle_status == "quarantine-enter" + def test_manage_project_settings_in_organization_managed(self, monkeypatch): request = pretend.stub(organization_access=True) organization_managed = pretend.stub(name="managed-org", is_active=True) organization_owned = pretend.stub(name="owned-org", is_active=True) - project = pretend.stub(organization=organization_managed) + project = pretend.stub(organization=organization_managed, lifecycle_status=None) view = views.ManageProjectSettingsViews(project, request) form = pretend.stub() view.transfer_organization_project_form_class = pretend.call_recorder( lambda *a, **kw: form ) view.add_alternate_repository_form_class = lambda *a, **kw: form + view.set_project_status_form_class = lambda *a, **kw: form user_organizations = pretend.call_recorder( lambda *a, **kw: { @@ -2651,6 +2722,7 @@ def test_manage_project_settings_in_organization_managed(self, monkeypatch): "project": project, "MAX_FILESIZE": MAX_FILESIZE, "MAX_PROJECT_SIZE": MAX_PROJECT_SIZE, + "set_project_status_form": form, "transfer_organization_project_form": form, "add_alternate_repository_form_class": form, } @@ -2662,13 +2734,14 @@ def test_manage_project_settings_in_organization_owned(self, monkeypatch): request = pretend.stub(organization_access=True) organization_managed = pretend.stub(name="managed-org", is_active=True) organization_owned = pretend.stub(name="owned-org", is_active=True) - project = pretend.stub(organization=organization_owned) + project = pretend.stub(organization=organization_owned, lifecycle_status=None) view = views.ManageProjectSettingsViews(project, request) form = pretend.stub() view.transfer_organization_project_form_class = pretend.call_recorder( lambda *a, **kw: form ) view.add_alternate_repository_form_class = lambda *a, **kw: form + view.set_project_status_form_class = lambda *a, **kw: form user_organizations = pretend.call_recorder( lambda *a, **kw: { @@ -2683,6 +2756,7 @@ def test_manage_project_settings_in_organization_owned(self, monkeypatch): "project": project, "MAX_FILESIZE": MAX_FILESIZE, "MAX_PROJECT_SIZE": MAX_PROJECT_SIZE, + "set_project_status_form": form, "transfer_organization_project_form": form, "add_alternate_repository_form_class": form, } diff --git a/warehouse/locale/messages.pot b/warehouse/locale/messages.pot index 4d8141199bbe..a0581b98916b 100644 --- a/warehouse/locale/messages.pot +++ b/warehouse/locale/messages.pot @@ -90,8 +90,8 @@ msgid "" "different email." msgstr "" -#: warehouse/accounts/forms.py:410 warehouse/manage/forms.py:139 -#: warehouse/manage/forms.py:730 +#: warehouse/accounts/forms.py:410 warehouse/manage/forms.py:140 +#: warehouse/manage/forms.py:746 msgid "The name is too long. Choose a name with 100 characters or less." msgstr "" @@ -152,7 +152,7 @@ msgstr "" msgid "Successful WebAuthn assertion" msgstr "" -#: warehouse/accounts/views.py:569 warehouse/manage/views/__init__.py:873 +#: warehouse/accounts/views.py:569 warehouse/manage/views/__init__.py:874 msgid "Recovery code accepted. The supplied code cannot be used again." msgstr "" @@ -286,7 +286,7 @@ msgid "You are now ${role} of the '${project_name}' project." msgstr "" #: warehouse/accounts/views.py:1548 warehouse/accounts/views.py:1791 -#: warehouse/manage/views/__init__.py:1417 +#: warehouse/manage/views/__init__.py:1462 msgid "" "Trusted publishing is temporarily disabled. See https://pypi.org/help" "#admin-intervention for details." @@ -306,19 +306,19 @@ msgstr "" msgid "You can't register more than 3 pending trusted publishers at once." msgstr "" -#: warehouse/accounts/views.py:1614 warehouse/manage/views/__init__.py:1472 -#: warehouse/manage/views/__init__.py:1585 -#: warehouse/manage/views/__init__.py:1697 -#: warehouse/manage/views/__init__.py:1807 +#: warehouse/accounts/views.py:1614 warehouse/manage/views/__init__.py:1517 +#: warehouse/manage/views/__init__.py:1630 +#: warehouse/manage/views/__init__.py:1742 +#: warehouse/manage/views/__init__.py:1852 msgid "" "There have been too many attempted trusted publisher registrations. Try " "again later." msgstr "" -#: warehouse/accounts/views.py:1625 warehouse/manage/views/__init__.py:1486 -#: warehouse/manage/views/__init__.py:1599 -#: warehouse/manage/views/__init__.py:1711 -#: warehouse/manage/views/__init__.py:1821 +#: warehouse/accounts/views.py:1625 warehouse/manage/views/__init__.py:1531 +#: warehouse/manage/views/__init__.py:1644 +#: warehouse/manage/views/__init__.py:1756 +#: warehouse/manage/views/__init__.py:1866 msgid "The trusted publisher could not be registered" msgstr "" @@ -345,11 +345,11 @@ msgstr "" msgid "Banner Preview" msgstr "" -#: warehouse/manage/forms.py:408 +#: warehouse/manage/forms.py:409 msgid "Choose an organization account name with 50 characters or less." msgstr "" -#: warehouse/manage/forms.py:416 +#: warehouse/manage/forms.py:417 msgid "" "The organization account name is invalid. Organization account names must" " be composed of letters, numbers, dots, hyphens and underscores. And must" @@ -357,246 +357,254 @@ msgid "" "organization account name." msgstr "" -#: warehouse/manage/forms.py:439 +#: warehouse/manage/forms.py:440 msgid "" "This organization account name has already been used. Choose a different " "organization account name." msgstr "" -#: warehouse/manage/forms.py:454 +#: warehouse/manage/forms.py:455 msgid "" "You have already submitted an application for that name. Choose a " "different organization account name." msgstr "" -#: warehouse/manage/forms.py:490 +#: warehouse/manage/forms.py:491 msgid "Select project" msgstr "" -#: warehouse/manage/forms.py:495 warehouse/oidc/forms/_core.py:22 +#: warehouse/manage/forms.py:496 warehouse/oidc/forms/_core.py:22 #: warehouse/oidc/forms/gitlab.py:43 msgid "Specify project name" msgstr "" -#: warehouse/manage/forms.py:498 +#: warehouse/manage/forms.py:499 msgid "" "Start and end with a letter or numeral containing only ASCII numeric and " "'.', '_' and '-'." msgstr "" -#: warehouse/manage/forms.py:505 +#: warehouse/manage/forms.py:506 msgid "This project name has already been used. Choose a different project name." msgstr "" -#: warehouse/manage/forms.py:578 +#: warehouse/manage/forms.py:579 msgid "" "The organization name is too long. Choose a organization name with 100 " "characters or less." msgstr "" -#: warehouse/manage/forms.py:590 +#: warehouse/manage/forms.py:591 msgid "" "The organization URL is too long. Choose a organization URL with 400 " "characters or less." msgstr "" -#: warehouse/manage/forms.py:597 +#: warehouse/manage/forms.py:598 msgid "The organization URL must start with http:// or https://" msgstr "" -#: warehouse/manage/forms.py:608 +#: warehouse/manage/forms.py:609 msgid "" "The organization description is too long. Choose a organization " "description with 400 characters or less." msgstr "" -#: warehouse/manage/forms.py:643 +#: warehouse/manage/forms.py:644 msgid "You have already submitted the maximum number of " msgstr "" -#: warehouse/manage/forms.py:673 +#: warehouse/manage/forms.py:674 msgid "Choose a team name with 50 characters or less." msgstr "" -#: warehouse/manage/forms.py:679 +#: warehouse/manage/forms.py:680 msgid "" "The team name is invalid. Team names cannot start or end with a space, " "period, underscore, hyphen, or slash. Choose a different team name." msgstr "" -#: warehouse/manage/forms.py:707 +#: warehouse/manage/forms.py:708 msgid "This team name has already been used. Choose a different team name." msgstr "" -#: warehouse/manage/forms.py:726 +#: warehouse/manage/forms.py:742 msgid "Specify your alternate repository name" msgstr "" -#: warehouse/manage/forms.py:740 +#: warehouse/manage/forms.py:756 msgid "Specify your alternate repository URL" msgstr "" -#: warehouse/manage/forms.py:744 +#: warehouse/manage/forms.py:760 msgid "The URL is too long. Choose a URL with 400 characters or less." msgstr "" -#: warehouse/manage/forms.py:758 +#: warehouse/manage/forms.py:774 msgid "" "The description is too long. Choose a description with 400 characters or " "less." msgstr "" -#: warehouse/manage/views/__init__.py:285 +#: warehouse/manage/views/__init__.py:286 msgid "Account details updated" msgstr "" -#: warehouse/manage/views/__init__.py:315 +#: warehouse/manage/views/__init__.py:316 msgid "Email ${email_address} added - check your email for a verification link" msgstr "" -#: warehouse/manage/views/__init__.py:821 +#: warehouse/manage/views/__init__.py:822 msgid "Recovery codes already generated" msgstr "" -#: warehouse/manage/views/__init__.py:822 +#: warehouse/manage/views/__init__.py:823 msgid "Generating new recovery codes will invalidate your existing codes." msgstr "" -#: warehouse/manage/views/__init__.py:931 +#: warehouse/manage/views/__init__.py:932 msgid "Verify your email to create an API token." msgstr "" -#: warehouse/manage/views/__init__.py:1031 +#: warehouse/manage/views/__init__.py:1032 msgid "API Token does not exist." msgstr "" -#: warehouse/manage/views/__init__.py:1063 +#: warehouse/manage/views/__init__.py:1064 msgid "Invalid credentials. Try again" msgstr "" -#: warehouse/manage/views/__init__.py:1182 +#: warehouse/manage/views/__init__.py:1188 +msgid "Invalid project status" +msgstr "" + +#: warehouse/manage/views/__init__.py:1201 +msgid "Set project status to '${status_name}'" +msgstr "" + +#: warehouse/manage/views/__init__.py:1227 msgid "Invalid alternate repository location details" msgstr "" -#: warehouse/manage/views/__init__.py:1219 +#: warehouse/manage/views/__init__.py:1264 msgid "Added alternate repository '${name}'" msgstr "" -#: warehouse/manage/views/__init__.py:1253 -#: warehouse/manage/views/__init__.py:2154 -#: warehouse/manage/views/__init__.py:2239 -#: warehouse/manage/views/__init__.py:2340 -#: warehouse/manage/views/__init__.py:2440 +#: warehouse/manage/views/__init__.py:1298 +#: warehouse/manage/views/__init__.py:2199 +#: warehouse/manage/views/__init__.py:2284 +#: warehouse/manage/views/__init__.py:2385 +#: warehouse/manage/views/__init__.py:2485 msgid "Confirm the request" msgstr "" -#: warehouse/manage/views/__init__.py:1265 +#: warehouse/manage/views/__init__.py:1310 msgid "Invalid alternate repository id" msgstr "" -#: warehouse/manage/views/__init__.py:1276 +#: warehouse/manage/views/__init__.py:1321 msgid "Invalid alternate repository for project" msgstr "" -#: warehouse/manage/views/__init__.py:1284 +#: warehouse/manage/views/__init__.py:1329 msgid "" "Could not delete alternate repository - ${confirm} is not the same as " "${alt_repo_name}" msgstr "" -#: warehouse/manage/views/__init__.py:1322 +#: warehouse/manage/views/__init__.py:1367 msgid "Deleted alternate repository '${name}'" msgstr "" -#: warehouse/manage/views/__init__.py:1453 +#: warehouse/manage/views/__init__.py:1498 msgid "" "GitHub-based trusted publishing is temporarily disabled. See " "https://pypi.org/help#admin-intervention for details." msgstr "" -#: warehouse/manage/views/__init__.py:1566 +#: warehouse/manage/views/__init__.py:1611 msgid "" "GitLab-based trusted publishing is temporarily disabled. See " "https://pypi.org/help#admin-intervention for details." msgstr "" -#: warehouse/manage/views/__init__.py:1678 +#: warehouse/manage/views/__init__.py:1723 msgid "" "Google-based trusted publishing is temporarily disabled. See " "https://pypi.org/help#admin-intervention for details." msgstr "" -#: warehouse/manage/views/__init__.py:1787 +#: warehouse/manage/views/__init__.py:1832 msgid "" "ActiveState-based trusted publishing is temporarily disabled. See " "https://pypi.org/help#admin-intervention for details." msgstr "" -#: warehouse/manage/views/__init__.py:2022 -#: warehouse/manage/views/__init__.py:2323 -#: warehouse/manage/views/__init__.py:2431 +#: warehouse/manage/views/__init__.py:2067 +#: warehouse/manage/views/__init__.py:2368 +#: warehouse/manage/views/__init__.py:2476 msgid "" "Project deletion temporarily disabled. See https://pypi.org/help#admin-" "intervention for details." msgstr "" -#: warehouse/manage/views/__init__.py:2166 +#: warehouse/manage/views/__init__.py:2211 msgid "Could not yank release - " msgstr "" -#: warehouse/manage/views/__init__.py:2251 +#: warehouse/manage/views/__init__.py:2296 msgid "Could not un-yank release - " msgstr "" -#: warehouse/manage/views/__init__.py:2352 +#: warehouse/manage/views/__init__.py:2397 msgid "Could not delete release - " msgstr "" -#: warehouse/manage/views/__init__.py:2452 +#: warehouse/manage/views/__init__.py:2497 msgid "Could not find file" msgstr "" -#: warehouse/manage/views/__init__.py:2456 +#: warehouse/manage/views/__init__.py:2501 msgid "Could not delete file - " msgstr "" -#: warehouse/manage/views/__init__.py:2606 +#: warehouse/manage/views/__init__.py:2651 msgid "Team '${team_name}' already has ${role_name} role for project" msgstr "" -#: warehouse/manage/views/__init__.py:2713 +#: warehouse/manage/views/__init__.py:2758 msgid "User '${username}' already has ${role_name} role for project" msgstr "" -#: warehouse/manage/views/__init__.py:2780 +#: warehouse/manage/views/__init__.py:2825 msgid "${username} is now ${role} of the '${project_name}' project." msgstr "" -#: warehouse/manage/views/__init__.py:2812 +#: warehouse/manage/views/__init__.py:2857 msgid "" "User '${username}' does not have a verified primary email address and " "cannot be added as a ${role_name} for project" msgstr "" -#: warehouse/manage/views/__init__.py:2825 +#: warehouse/manage/views/__init__.py:2870 #: warehouse/manage/views/organizations.py:878 msgid "User '${username}' already has an active invite. Please try again later." msgstr "" -#: warehouse/manage/views/__init__.py:2890 +#: warehouse/manage/views/__init__.py:2935 #: warehouse/manage/views/organizations.py:943 msgid "Invitation sent to '${username}'" msgstr "" -#: warehouse/manage/views/__init__.py:2923 +#: warehouse/manage/views/__init__.py:2968 msgid "Could not find role invitation." msgstr "" -#: warehouse/manage/views/__init__.py:2934 +#: warehouse/manage/views/__init__.py:2979 msgid "Invitation already expired." msgstr "" -#: warehouse/manage/views/__init__.py:2966 +#: warehouse/manage/views/__init__.py:3011 #: warehouse/manage/views/organizations.py:1130 msgid "Invitation revoked from '${username}'." msgstr "" @@ -840,8 +848,8 @@ msgstr "" #: warehouse/templates/manage/project/release.html:194 #: warehouse/templates/manage/project/releases.html:140 #: warehouse/templates/manage/project/releases.html:179 -#: warehouse/templates/packaging/detail.html:407 -#: warehouse/templates/packaging/detail.html:427 +#: warehouse/templates/packaging/detail.html:417 +#: warehouse/templates/packaging/detail.html:437 #: warehouse/templates/pages/classifiers.html:25 #: warehouse/templates/pages/help.html:20 #: warehouse/templates/pages/help.html:228 @@ -1078,9 +1086,9 @@ msgstr "" #: warehouse/templates/manage/organization/settings.html:286 #: warehouse/templates/manage/project/documentation.html:27 #: warehouse/templates/manage/project/release.html:182 -#: warehouse/templates/manage/project/settings.html:87 -#: warehouse/templates/manage/project/settings.html:136 -#: warehouse/templates/manage/project/settings.html:357 +#: warehouse/templates/manage/project/settings.html:105 +#: warehouse/templates/manage/project/settings.html:154 +#: warehouse/templates/manage/project/settings.html:375 #: warehouse/templates/manage/team/settings.html:84 msgid "Warning" msgstr "" @@ -1446,9 +1454,9 @@ msgstr "" #: warehouse/templates/manage/project/roles.html:328 #: warehouse/templates/manage/project/roles.html:359 #: warehouse/templates/manage/project/roles.html:380 -#: warehouse/templates/manage/project/settings.html:287 -#: warehouse/templates/manage/project/settings.html:307 -#: warehouse/templates/manage/project/settings.html:327 +#: warehouse/templates/manage/project/settings.html:305 +#: warehouse/templates/manage/project/settings.html:325 +#: warehouse/templates/manage/project/settings.html:345 #: warehouse/templates/manage/team/roles.html:106 #: warehouse/templates/manage/team/settings.html:35 #: warehouse/templates/packaging/submit-malware-observation.html:58 @@ -1742,9 +1750,9 @@ msgstr "" #: warehouse/templates/manage/project/history.html:312 #: warehouse/templates/manage/project/history.html:323 #: warehouse/templates/manage/project/history.html:334 -#: warehouse/templates/manage/project/settings.html:224 -#: warehouse/templates/manage/project/settings.html:285 -#: warehouse/templates/manage/project/settings.html:291 +#: warehouse/templates/manage/project/settings.html:242 +#: warehouse/templates/manage/project/settings.html:303 +#: warehouse/templates/manage/project/settings.html:309 #: warehouse/templates/manage/unverified-account.html:112 msgid "Name" msgstr "" @@ -2667,7 +2675,7 @@ msgstr "" #: warehouse/templates/manage/manage_base.html:331 #: warehouse/templates/manage/project/release.html:137 #: warehouse/templates/manage/project/releases.html:178 -#: warehouse/templates/manage/project/settings.html:72 +#: warehouse/templates/manage/project/settings.html:90 #: warehouse/templates/manage/unverified-account.html:172 #: warehouse/templates/manage/unverified-account.html:174 #: warehouse/templates/manage/unverified-account.html:184 @@ -3324,12 +3332,12 @@ msgid "Update password" msgstr "" #: warehouse/templates/manage/account.html:472 -#: warehouse/templates/manage/project/settings.html:43 +#: warehouse/templates/manage/project/settings.html:61 msgid "API tokens" msgstr "" #: warehouse/templates/manage/account.html:473 -#: warehouse/templates/manage/project/settings.html:44 +#: warehouse/templates/manage/project/settings.html:62 msgid "" "API tokens provide an alternative way to authenticate when uploading " "packages to PyPI." @@ -4536,7 +4544,7 @@ msgstr "" #: warehouse/templates/manage/project/publishing.html:275 #: warehouse/templates/manage/project/publishing.html:357 #: warehouse/templates/manage/project/roles.html:341 -#: warehouse/templates/manage/project/settings.html:348 +#: warehouse/templates/manage/project/settings.html:366 #: warehouse/templates/manage/team/roles.html:131 msgid "Add" msgstr "" @@ -5584,7 +5592,7 @@ msgstr "" #: warehouse/templates/manage/organization/roles.html:252 #: warehouse/templates/manage/project/release.html:106 #: warehouse/templates/manage/project/releases.html:109 -#: warehouse/templates/manage/project/settings.html:252 +#: warehouse/templates/manage/project/settings.html:270 msgid "Delete" msgstr "" @@ -5981,9 +5989,9 @@ msgstr "" #: warehouse/templates/manage/project/history.html:313 #: warehouse/templates/manage/project/history.html:324 #: warehouse/templates/manage/project/history.html:335 -#: warehouse/templates/manage/project/settings.html:225 -#: warehouse/templates/manage/project/settings.html:305 -#: warehouse/templates/manage/project/settings.html:311 +#: warehouse/templates/manage/project/settings.html:243 +#: warehouse/templates/manage/project/settings.html:323 +#: warehouse/templates/manage/project/settings.html:329 msgid "Url" msgstr "" @@ -6162,7 +6170,7 @@ msgstr "" #: warehouse/templates/manage/project/release.html:137 #: warehouse/templates/manage/project/releases.html:178 -#: warehouse/templates/manage/project/settings.html:72 +#: warehouse/templates/manage/project/settings.html:90 msgid "Dismiss" msgstr "" @@ -6505,23 +6513,37 @@ msgstr "" msgid " (request an increase) " msgstr "" -#: warehouse/templates/manage/project/settings.html:48 +#: warehouse/templates/manage/project/settings.html:43 +msgid "Project status" +msgstr "" + +#: warehouse/templates/manage/project/settings.html:44 +msgid "" +"The project status indicates what users should expect in terms of active " +"development and mainteinance." +msgstr "" + +#: warehouse/templates/manage/project/settings.html:58 +msgid "Set status" +msgstr "" + +#: warehouse/templates/manage/project/settings.html:66 #, python-format msgid "Create a token for %(project_name)s" msgstr "" -#: warehouse/templates/manage/project/settings.html:53 +#: warehouse/templates/manage/project/settings.html:71 #, python-format msgid "" "Verify your primary email address to add an API " "token for %(project_name)s." msgstr "" -#: warehouse/templates/manage/project/settings.html:60 +#: warehouse/templates/manage/project/settings.html:78 msgid "Project description and sidebar" msgstr "" -#: warehouse/templates/manage/project/settings.html:62 +#: warehouse/templates/manage/project/settings.html:80 #, python-format msgid "" "To set the '%(project_name)s' description, author, links, classifiers, " @@ -6537,147 +6559,147 @@ msgid "" "Python Packaging User Guide for more help." msgstr "" -#: warehouse/templates/manage/project/settings.html:85 +#: warehouse/templates/manage/project/settings.html:103 msgid "Remove project from organization" msgstr "" -#: warehouse/templates/manage/project/settings.html:88 +#: warehouse/templates/manage/project/settings.html:106 msgid "Removing this project from the organization will:" msgstr "" -#: warehouse/templates/manage/project/settings.html:92 -#: warehouse/templates/manage/project/settings.html:142 +#: warehouse/templates/manage/project/settings.html:110 +#: warehouse/templates/manage/project/settings.html:160 #, python-format msgid "Remove this project from the '%(organization_name)s' organization." msgstr "" -#: warehouse/templates/manage/project/settings.html:95 -#: warehouse/templates/manage/project/settings.html:145 +#: warehouse/templates/manage/project/settings.html:113 +#: warehouse/templates/manage/project/settings.html:163 #, python-format msgid "" "Revoke project permissions for teams in the '%(organization_name)s' " "organization." msgstr "" -#: warehouse/templates/manage/project/settings.html:99 -#: warehouse/templates/manage/project/settings.html:105 +#: warehouse/templates/manage/project/settings.html:117 +#: warehouse/templates/manage/project/settings.html:123 msgid "" "Individual owners and maintainers of the project will retain their " "project permissions." msgstr "" -#: warehouse/templates/manage/project/settings.html:104 +#: warehouse/templates/manage/project/settings.html:122 #, python-format msgid "" "This will remove the project from the '%(organization_name)s' " "organization." msgstr "" -#: warehouse/templates/manage/project/settings.html:108 +#: warehouse/templates/manage/project/settings.html:126 msgid "Remove project" msgstr "" -#: warehouse/templates/manage/project/settings.html:108 -#: warehouse/templates/manage/project/settings.html:179 -#: warehouse/templates/manage/project/settings.html:395 +#: warehouse/templates/manage/project/settings.html:126 +#: warehouse/templates/manage/project/settings.html:197 +#: warehouse/templates/manage/project/settings.html:413 msgid "Project Name" msgstr "" -#: warehouse/templates/manage/project/settings.html:112 +#: warehouse/templates/manage/project/settings.html:130 msgid "Cannot remove project from organization" msgstr "" -#: warehouse/templates/manage/project/settings.html:114 +#: warehouse/templates/manage/project/settings.html:132 msgid "" "Your organization is currently the sole owner of the " "project. You must add an individual owner to the project before you can " "remove the project from your organization." msgstr "" -#: warehouse/templates/manage/project/settings.html:130 +#: warehouse/templates/manage/project/settings.html:148 msgid "Transfer project to another organization" msgstr "" -#: warehouse/templates/manage/project/settings.html:132 +#: warehouse/templates/manage/project/settings.html:150 msgid "Transfer project to an organization" msgstr "" -#: warehouse/templates/manage/project/settings.html:137 +#: warehouse/templates/manage/project/settings.html:155 msgid "Transferring this project will:" msgstr "" -#: warehouse/templates/manage/project/settings.html:149 +#: warehouse/templates/manage/project/settings.html:167 msgid "Revoke your direct Owner role on the project." msgstr "" -#: warehouse/templates/manage/project/settings.html:152 +#: warehouse/templates/manage/project/settings.html:170 msgid "" "You will retain Owner permissions on the project through your " "organization role." msgstr "" -#: warehouse/templates/manage/project/settings.html:157 +#: warehouse/templates/manage/project/settings.html:175 msgid "Add the project to another organization that you own." msgstr "" -#: warehouse/templates/manage/project/settings.html:159 +#: warehouse/templates/manage/project/settings.html:177 msgid "Add the project to an organization that you own." msgstr "" -#: warehouse/templates/manage/project/settings.html:163 +#: warehouse/templates/manage/project/settings.html:181 msgid "Grant full project permissions to owners of the organization." msgstr "" -#: warehouse/templates/manage/project/settings.html:167 +#: warehouse/templates/manage/project/settings.html:185 msgid "" "All other individual owners and maintainers of the project will retain " "their project permissions." msgstr "" -#: warehouse/templates/manage/project/settings.html:179 +#: warehouse/templates/manage/project/settings.html:197 msgid "Transfer project" msgstr "" -#: warehouse/templates/manage/project/settings.html:185 +#: warehouse/templates/manage/project/settings.html:203 msgid "Cannot transfer project to another organization" msgstr "" -#: warehouse/templates/manage/project/settings.html:187 +#: warehouse/templates/manage/project/settings.html:205 msgid "Cannot transfer project to an organization" msgstr "" -#: warehouse/templates/manage/project/settings.html:192 +#: warehouse/templates/manage/project/settings.html:210 msgid "" "Organization owners can transfer the project to organizations that they " "own or manage." msgstr "" -#: warehouse/templates/manage/project/settings.html:193 +#: warehouse/templates/manage/project/settings.html:211 msgid "You are not an owner or manager of any other organizations." msgstr "" -#: warehouse/templates/manage/project/settings.html:195 +#: warehouse/templates/manage/project/settings.html:213 msgid "" "Project owners can transfer the project to organizations that they own or" " manage." msgstr "" -#: warehouse/templates/manage/project/settings.html:196 +#: warehouse/templates/manage/project/settings.html:214 msgid "You are not an owner or manager of any organizations." msgstr "" -#: warehouse/templates/manage/project/settings.html:205 +#: warehouse/templates/manage/project/settings.html:223 msgid "Alternate repository locations" msgstr "" -#: warehouse/templates/manage/project/settings.html:209 +#: warehouse/templates/manage/project/settings.html:227 #, python-format msgid "" "Provisional support for PEP 708 \"Alternate " "Locations\" Metadata." msgstr "" -#: warehouse/templates/manage/project/settings.html:213 +#: warehouse/templates/manage/project/settings.html:231 #, python-format msgid "" "Implementation may change, consider subscribing to %(count)s" @@ -6744,15 +6766,15 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: warehouse/templates/manage/project/settings.html:369 +#: warehouse/templates/manage/project/settings.html:387 msgid "Irreversibly delete the project" msgstr "" -#: warehouse/templates/manage/project/settings.html:373 +#: warehouse/templates/manage/project/settings.html:391 msgid "Make the project name available to any other PyPI user" msgstr "" -#: warehouse/templates/manage/project/settings.html:375 +#: warehouse/templates/manage/project/settings.html:393 msgid "" "This user will be able to make new releases under this project name, so " "long as the distribution filenames do not match filenames from a " @@ -6942,7 +6964,7 @@ msgstr "" #: warehouse/templates/packaging/detail.html:238 #: warehouse/templates/packaging/detail.html:272 -#: warehouse/templates/packaging/detail.html:322 +#: warehouse/templates/packaging/detail.html:332 msgid "Project description" msgstr "" @@ -6953,7 +6975,7 @@ msgstr "" #: warehouse/templates/packaging/detail.html:244 #: warehouse/templates/packaging/detail.html:284 -#: warehouse/templates/packaging/detail.html:344 +#: warehouse/templates/packaging/detail.html:354 msgid "Release history" msgstr "" @@ -6964,7 +6986,7 @@ msgstr "" #: warehouse/templates/packaging/detail.html:251 #: warehouse/templates/packaging/detail.html:291 -#: warehouse/templates/packaging/detail.html:406 +#: warehouse/templates/packaging/detail.html:416 msgid "Download files" msgstr "" @@ -6973,40 +6995,50 @@ msgid "Project details. Focus will be moved to the project details." msgstr "" #: warehouse/templates/packaging/detail.html:278 -#: warehouse/templates/packaging/detail.html:336 +#: warehouse/templates/packaging/detail.html:346 msgid "Project details" msgstr "" #: warehouse/templates/packaging/detail.html:318 -#: warehouse/templates/packaging/detail.html:393 +msgid "This project has been archived." +msgstr "" + +#: warehouse/templates/packaging/detail.html:320 +msgid "" +"The maintainers of this project have marked this project as archived. No " +"new releases are expected." +msgstr "" + +#: warehouse/templates/packaging/detail.html:328 +#: warehouse/templates/packaging/detail.html:403 msgid "Reason this release was yanked:" msgstr "" -#: warehouse/templates/packaging/detail.html:329 +#: warehouse/templates/packaging/detail.html:339 msgid "The author of this package has not provided a project description" msgstr "" -#: warehouse/templates/packaging/detail.html:346 +#: warehouse/templates/packaging/detail.html:356 msgid "Release notifications" msgstr "" -#: warehouse/templates/packaging/detail.html:347 +#: warehouse/templates/packaging/detail.html:357 msgid "RSS feed" msgstr "" -#: warehouse/templates/packaging/detail.html:359 +#: warehouse/templates/packaging/detail.html:369 msgid "This version" msgstr "" -#: warehouse/templates/packaging/detail.html:379 +#: warehouse/templates/packaging/detail.html:389 msgid "pre-release" msgstr "" -#: warehouse/templates/packaging/detail.html:384 +#: warehouse/templates/packaging/detail.html:394 msgid "yanked" msgstr "" -#: warehouse/templates/packaging/detail.html:407 +#: warehouse/templates/packaging/detail.html:417 #, python-format msgid "" "Download the file for your platform. If you're not sure which to choose, " @@ -7014,24 +7046,24 @@ msgid "" "target=\"_blank\" rel=\"noopener\">installing packages." msgstr "" -#: warehouse/templates/packaging/detail.html:410 +#: warehouse/templates/packaging/detail.html:420 msgid "Source Distribution" msgid_plural "Source Distributions" msgstr[0] "" msgstr[1] "" -#: warehouse/templates/packaging/detail.html:426 +#: warehouse/templates/packaging/detail.html:436 msgid "No source distribution files available for this release." msgstr "" -#: warehouse/templates/packaging/detail.html:427 +#: warehouse/templates/packaging/detail.html:437 #, python-format msgid "" "See tutorial on generating distribution archives." msgstr "" -#: warehouse/templates/packaging/detail.html:434 +#: warehouse/templates/packaging/detail.html:444 msgid "Built Distribution" msgid_plural "Built Distributions" msgstr[0] "" diff --git a/warehouse/manage/forms.py b/warehouse/manage/forms.py index 58539de2c435..5b90238b3a21 100644 --- a/warehouse/manage/forms.py +++ b/warehouse/manage/forms.py @@ -31,6 +31,7 @@ OrganizationType, TeamProjectRoleType, ) +from warehouse.packaging.models import LifecycleStatus from warehouse.utils.project import PROJECT_NAME_RE # /manage/account/ forms @@ -715,6 +716,21 @@ class CreateTeamForm(SaveTeamForm): __params__ = SaveTeamForm.__params__ +class SetProjectStatusForm(wtforms.Form): + """Form to set a project's status.""" + + __params__ = ["project_status"] + + project_status = wtforms.SelectField( + "Select status", + choices=[ + ("", "(no status)"), + (LifecycleStatus.Archived, "Archived"), + ], + coerce=lambda string: LifecycleStatus(string) if string else None, + ) + + class AddAlternateRepositoryForm(wtforms.Form): """Form to add an Alternate Repository Location for a Project.""" diff --git a/warehouse/manage/views/__init__.py b/warehouse/manage/views/__init__.py index a5b176807cb8..5d2a88e9e6d8 100644 --- a/warehouse/manage/views/__init__.py +++ b/warehouse/manage/views/__init__.py @@ -90,6 +90,7 @@ ProvisionTOTPForm, ProvisionWebAuthnForm, SaveAccountForm, + SetProjectStatusForm, TransferOrganizationProjectForm, ) from warehouse.manage.views.organizations import ( @@ -1125,6 +1126,7 @@ class ManageProjectSettingsViews: def __init__(self, project, request): self.project = project self.request = request + self.set_project_status_form_class = SetProjectStatusForm self.transfer_organization_project_form_class = TransferOrganizationProjectForm self.add_alternate_repository_form_class = AddAlternateRepositoryForm @@ -1154,11 +1156,17 @@ def manage_project_settings(self): ) - current_organization add_alt_repo_form = self.add_alternate_repository_form_class() + set_project_status_form = self.set_project_status_form_class( + project_status=( + self.project.lifecycle_status if self.project.lifecycle_status else "" + ) + ) return { "project": self.project, "MAX_FILESIZE": MAX_FILESIZE, "MAX_PROJECT_SIZE": MAX_PROJECT_SIZE, + "set_project_status_form": set_project_status_form, "transfer_organization_project_form": ( self.transfer_organization_project_form_class( organization_choices=organization_choices, @@ -1167,6 +1175,43 @@ def manage_project_settings(self): "add_alternate_repository_form_class": add_alt_repo_form, } + @view_config( + request_method="POST", + request_param=SetProjectStatusForm.__params__, + require_reauth=True, + permission=Permissions.ProjectsWrite, + ) + def set_project_status(self): + form = self.set_project_status_form_class(self.request.POST) + if not form.validate(): + self.request.session.flash( + self.request._("Invalid project status"), + queue="error", + ) + return HTTPSeeOther( + self.request.route_path( + "manage.project.settings", + project_name=self.project.name, + ) + ) + + self.project.lifecycle_status = form.project_status.data + + self.request.session.flash( + self.request._( + "Set project status to '${status_name}'", + mapping={"status_name": form.project_status.data}, + ), + queue="success", + ) + + return HTTPSeeOther( + self.request.route_path( + "manage.project.settings", + project_name=self.project.name, + ) + ) + @view_config( request_method="POST", request_param=AddAlternateRepositoryForm.__params__ diff --git a/warehouse/migrations/versions/12a43f12cc18_add_new_lifecycle_statuses.py b/warehouse/migrations/versions/12a43f12cc18_add_new_lifecycle_statuses.py new file mode 100644 index 000000000000..30415d7f47b3 --- /dev/null +++ b/warehouse/migrations/versions/12a43f12cc18_add_new_lifecycle_statuses.py @@ -0,0 +1,55 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Add new lifecycle statuses + +Revision ID: 12a43f12cc18 +Revises: 6ee23f5a6c1b +""" + +from alembic import op +from alembic_postgresql_enum import TableReference + +revision = "12a43f12cc18" +down_revision = "6ee23f5a6c1b" + + +def upgrade(): + op.sync_enum_values( + "public", + "lifecyclestatus", + ["quarantine-enter", "quarantine-exit", "archived"], + [ + TableReference( + table_schema="public", + table_name="projects", + column_name="lifecycle_status", + ) + ], + enum_values_to_rename=[], + ) + + +def downgrade(): + op.sync_enum_values( + "public", + "lifecyclestatus", + ["quarantine-enter", "quarantine-exit"], + [ + TableReference( + table_schema="public", + table_name="projects", + column_name="lifecycle_status", + ) + ], + enum_values_to_rename=[], + ) diff --git a/warehouse/packaging/models.py b/warehouse/packaging/models.py index e0c27e9bf0b2..ec37fb38cee1 100644 --- a/warehouse/packaging/models.py +++ b/warehouse/packaging/models.py @@ -166,6 +166,7 @@ def __contains__(self, project): class LifecycleStatus(enum.StrEnum): QuarantineEnter = "quarantine-enter" QuarantineExit = "quarantine-exit" + Archived = "archived" class Project(SitemapMixin, HasEvents, HasObservations, db.Model): diff --git a/warehouse/templates/manage/project/settings.html b/warehouse/templates/manage/project/settings.html index 609b684067f6..9c374e30542c 100644 --- a/warehouse/templates/manage/project/settings.html +++ b/warehouse/templates/manage/project/settings.html @@ -40,6 +40,24 @@

{% trans %}Project settings{% endtrans %}

(request an increase) {% endtrans %} +

{% trans %}Project status {% endtrans %}

+

{% trans %}The project status indicates what users should expect in terms of active development and mainteinance.{% endtrans %}

+
+ +
+

+ {{ set_project_status_form.project_status( + class_="form-group__field", + aria_describedby="project-status-errors", + ) }} +

+
+ {{ field_errors(set_project_status_form.project_status) }} +
+
+ +
+

{% trans %}API tokens{% endtrans %}

{% trans %}API tokens provide an alternative way to authenticate when uploading packages to PyPI.{% endtrans %}

{% if user.has_primary_verified_email %} diff --git a/warehouse/templates/packaging/detail.html b/warehouse/templates/packaging/detail.html index fef56c293cda..a789e0897261 100644 --- a/warehouse/templates/packaging/detail.html +++ b/warehouse/templates/packaging/detail.html @@ -313,6 +313,16 @@ {% endtrans %}

+ {% elif project.lifecycle_status == "archived" %} +
+

{% trans %}This project has been archived.{% endtrans %}

+

+ {% trans %} + The maintainers of this project have marked this project as archived. + No new releases are expected. + {% endtrans %} +

+
{% elif release.yanked and release.yanked_reason %}

{% trans %}Reason this release was yanked:{% endtrans %}