diff --git a/tests/unit/email/test_init.py b/tests/unit/email/test_init.py index cbe230b597a4..759fbde61243 100644 --- a/tests/unit/email/test_init.py +++ b/tests/unit/email/test_init.py @@ -10,6 +10,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import datetime + import attr import celery.exceptions import pretend @@ -1092,6 +1094,402 @@ def test_added_as_collaborator_email_unverified( assert send_email.delay.calls == [] +class TestRemovedPackageEmail: + def test_removed_project_email_to_maintainer( + self, pyramid_request, pyramid_config, monkeypatch + ): + stub_user = pretend.stub( + username="username", + name="", + email="email@example.com", + primary_email=pretend.stub(email="email@example.com", verified=True), + ) + stub_submitter_user = pretend.stub( + username="submitterusername", + name="", + email="submiteremail@example.com", + primary_email=pretend.stub( + email="submiteremail@example.com", verified=True + ), + ) + subject_renderer = pyramid_config.testing_add_renderer( + "email/removed-project/subject.txt" + ) + subject_renderer.string_response = "Email Subject" + body_renderer = pyramid_config.testing_add_renderer( + "email/removed-project/body.txt" + ) + body_renderer.string_response = "Email Body" + html_renderer = pyramid_config.testing_add_renderer( + "email/removed-project/body.html" + ) + html_renderer.string_response = "Email HTML Body" + + send_email = pretend.stub( + delay=pretend.call_recorder(lambda *args, **kwargs: None) + ) + pyramid_request.task = pretend.call_recorder(lambda *args, **kwargs: send_email) + monkeypatch.setattr(email, "send_email", send_email) + + result = email.send_removed_project_email( + pyramid_request, + [stub_user, stub_submitter_user], + project_name="test_project", + submitter_name=stub_submitter_user.username, + submitter_role="Owner", + recipient_role="Maintainer", + ) + + assert result == { + "project": "test_project", + "submitter": stub_submitter_user.username, + "submitter_role": "owner", + "recipient_role_descr": "a maintainer", + } + + subject_renderer.assert_(project="test_project") + body_renderer.assert_(project="test_project") + body_renderer.assert_(submitter=stub_submitter_user.username) + body_renderer.assert_(submitter_role="owner") + body_renderer.assert_(recipient_role_descr="a maintainer") + + assert pyramid_request.task.calls == [ + pretend.call(send_email), + pretend.call(send_email), + ] + + assert send_email.delay.calls == [ + pretend.call( + "username ", + attr.asdict( + EmailMessage( + subject="Email Subject", + body_text="Email Body", + body_html=( + "\n\n" + "

Email HTML Body

\n\n" + ), + ), + ), + ), + pretend.call( + "submitterusername ", + attr.asdict( + EmailMessage( + subject="Email Subject", + body_text="Email Body", + body_html=( + "\n\n" + "

Email HTML Body

\n\n" + ), + ) + ), + ), + ] + + def test_removed_project_email_to_owner( + self, pyramid_request, pyramid_config, monkeypatch + ): + stub_user = pretend.stub( + username="username", + name="", + email="email@example.com", + primary_email=pretend.stub(email="email@example.com", verified=True), + ) + stub_submitter_user = pretend.stub( + username="submitterusername", + name="", + email="submiteremail@example.com", + primary_email=pretend.stub( + email="submiteremail@example.com", verified=True + ), + ) + subject_renderer = pyramid_config.testing_add_renderer( + "email/removed-project/subject.txt" + ) + subject_renderer.string_response = "Email Subject" + body_renderer = pyramid_config.testing_add_renderer( + "email/removed-project/body.txt" + ) + body_renderer.string_response = "Email Body" + html_renderer = pyramid_config.testing_add_renderer( + "email/removed-project/body.html" + ) + html_renderer.string_response = "Email HTML Body" + + send_email = pretend.stub( + delay=pretend.call_recorder(lambda *args, **kwargs: None) + ) + pyramid_request.task = pretend.call_recorder(lambda *args, **kwargs: send_email) + monkeypatch.setattr(email, "send_email", send_email) + + result = email.send_removed_project_email( + pyramid_request, + [stub_user, stub_submitter_user], + project_name="test_project", + submitter_name=stub_submitter_user.username, + submitter_role="Owner", + recipient_role="Owner", + ) + + assert result == { + "project": "test_project", + "submitter": stub_submitter_user.username, + "submitter_role": "owner", + "recipient_role_descr": "an owner", + } + + subject_renderer.assert_(project="test_project") + body_renderer.assert_(project="test_project") + body_renderer.assert_(submitter=stub_submitter_user.username) + body_renderer.assert_(submitter_role="owner") + body_renderer.assert_(recipient_role_descr="an owner") + + assert pyramid_request.task.calls == [ + pretend.call(send_email), + pretend.call(send_email), + ] + + assert send_email.delay.calls == [ + pretend.call( + "username ", + attr.asdict( + EmailMessage( + subject="Email Subject", + body_text="Email Body", + body_html=( + "\n\n" + "

Email HTML Body

\n\n" + ), + ), + ), + ), + pretend.call( + "submitterusername ", + attr.asdict( + EmailMessage( + subject="Email Subject", + body_text="Email Body", + body_html=( + "\n\n" + "

Email HTML Body

\n\n" + ), + ) + ), + ), + ] + + +class TestRemovedReleaseEmail: + def test_send_removed_project_release_email_to_maintainer( + self, pyramid_request, pyramid_config, monkeypatch + ): + stub_user = pretend.stub( + username="username", + name="", + email="email@example.com", + primary_email=pretend.stub(email="email@example.com", verified=True), + ) + stub_submitter_user = pretend.stub( + username="submitterusername", + name="", + email="submiteremail@example.com", + primary_email=pretend.stub( + email="submiteremail@example.com", verified=True + ), + ) + + subject_renderer = pyramid_config.testing_add_renderer( + "email/removed-project-release/subject.txt" + ) + subject_renderer.string_response = "Email Subject" + body_renderer = pyramid_config.testing_add_renderer( + "email/removed-project-release/body.txt" + ) + body_renderer.string_response = "Email Body" + html_renderer = pyramid_config.testing_add_renderer( + "email/removed-project-release/body.html" + ) + html_renderer.string_response = "Email HTML Body" + + send_email = pretend.stub( + delay=pretend.call_recorder(lambda *args, **kwargs: None) + ) + pyramid_request.task = pretend.call_recorder(lambda *args, **kwargs: send_email) + monkeypatch.setattr(email, "send_email", send_email) + + release = pretend.stub( + version="0.0.0", + project=pretend.stub(name="test_project"), + created=datetime.datetime(2017, 2, 5, 0, 0, 0, 0), + ) + + result = email.send_removed_project_release_email( + pyramid_request, + [stub_user, stub_submitter_user], + release=release, + submitter_name=stub_submitter_user.username, + submitter_role="Owner", + recipient_role="Maintainer", + ) + + assert result == { + "project": release.project.name, + "release": release.version, + "release_date": release.created.strftime("%Y-%m-%d"), + "submitter": stub_submitter_user.username, + "submitter_role": "owner", + "recipient_role_descr": "a maintainer", + } + + subject_renderer.assert_(project="test_project") + subject_renderer.assert_(release="0.0.0") + body_renderer.assert_(project="test_project") + body_renderer.assert_(release="0.0.0") + body_renderer.assert_(release_date=release.created.strftime("%Y-%m-%d")) + body_renderer.assert_(submitter=stub_submitter_user.username) + body_renderer.assert_(submitter_role="owner") + body_renderer.assert_(recipient_role_descr="a maintainer") + + assert pyramid_request.task.calls == [ + pretend.call(send_email), + pretend.call(send_email), + ] + + assert send_email.delay.calls == [ + pretend.call( + "username ", + attr.asdict( + EmailMessage( + subject="Email Subject", + body_text="Email Body", + body_html=( + "\n\n" + "

Email HTML Body

\n\n" + ), + ), + ), + ), + pretend.call( + "submitterusername ", + attr.asdict( + EmailMessage( + subject="Email Subject", + body_text="Email Body", + body_html=( + "\n\n" + "

Email HTML Body

\n\n" + ), + ) + ), + ), + ] + + def test_send_removed_project_release_emai_to_owner( + self, pyramid_request, pyramid_config, monkeypatch + ): + stub_user = pretend.stub( + username="username", + name="", + email="email@example.com", + primary_email=pretend.stub(email="email@example.com", verified=True), + ) + stub_submitter_user = pretend.stub( + username="submitterusername", + name="", + email="submiteremail@example.com", + primary_email=pretend.stub( + email="submiteremail@example.com", verified=True + ), + ) + + subject_renderer = pyramid_config.testing_add_renderer( + "email/removed-project-release/subject.txt" + ) + subject_renderer.string_response = "Email Subject" + body_renderer = pyramid_config.testing_add_renderer( + "email/removed-project-release/body.txt" + ) + body_renderer.string_response = "Email Body" + html_renderer = pyramid_config.testing_add_renderer( + "email/removed-project-release/body.html" + ) + html_renderer.string_response = "Email HTML Body" + + send_email = pretend.stub( + delay=pretend.call_recorder(lambda *args, **kwargs: None) + ) + pyramid_request.task = pretend.call_recorder(lambda *args, **kwargs: send_email) + monkeypatch.setattr(email, "send_email", send_email) + + release = pretend.stub( + version="0.0.0", + project=pretend.stub(name="test_project"), + created=datetime.datetime(2017, 2, 5, 0, 0, 0, 0), + ) + + result = email.send_removed_project_release_email( + pyramid_request, + [stub_user, stub_submitter_user], + release=release, + submitter_name=stub_submitter_user.username, + submitter_role="Owner", + recipient_role="Owner", + ) + + assert result == { + "project": release.project.name, + "release": release.version, + "release_date": release.created.strftime("%Y-%m-%d"), + "submitter": stub_submitter_user.username, + "submitter_role": "owner", + "recipient_role_descr": "an owner", + } + + subject_renderer.assert_(project="test_project") + subject_renderer.assert_(release="0.0.0") + body_renderer.assert_(project="test_project") + body_renderer.assert_(release="0.0.0") + body_renderer.assert_(release_date=release.created.strftime("%Y-%m-%d")) + body_renderer.assert_(submitter=stub_submitter_user.username) + body_renderer.assert_(submitter_role="owner") + body_renderer.assert_(recipient_role_descr="an owner") + + assert pyramid_request.task.calls == [ + pretend.call(send_email), + pretend.call(send_email), + ] + + assert send_email.delay.calls == [ + pretend.call( + "username ", + attr.asdict( + EmailMessage( + subject="Email Subject", + body_text="Email Body", + body_html=( + "\n\n" + "

Email HTML Body

\n\n" + ), + ), + ), + ), + pretend.call( + "submitterusername ", + attr.asdict( + EmailMessage( + subject="Email Subject", + body_text="Email Body", + body_html=( + "\n\n" + "

Email HTML Body

\n\n" + ), + ) + ), + ), + ] + + class TestTwoFactorEmail: @pytest.mark.parametrize( ("action", "method", "pretty_method"), diff --git a/tests/unit/manage/test_views.py b/tests/unit/manage/test_views.py index b3b04869931c..870d650f2f95 100644 --- a/tests/unit/manage/test_views.py +++ b/tests/unit/manage/test_views.py @@ -2310,7 +2310,75 @@ def test_delete_project_disallow_deletion(self): pretend.call("manage.project.settings", project_name="foo") ] - def test_delete_project(self, db_request): + def test_get_project_contributors(self, db_request): + project = ProjectFactory.create(name="foo") + db_request.session = pretend.stub( + flash=pretend.call_recorder(lambda *a, **kw: None), + ) + + db_request.user = UserFactory.create() + project.users = [db_request.user] + + res = views.get_project_contributors(project.name, db_request) + assert res == [db_request.user] + + def test_get_user_role_in_project_single_role_owner(self, db_request): + project = ProjectFactory.create(name="foo") + db_request.session = pretend.stub( + flash=pretend.call_recorder(lambda *a, **kw: None), + ) + db_request.user = UserFactory.create() + project.users = [db_request.user] + RoleFactory(user=db_request.user, project=project) + + res = views.get_user_role_in_project( + project.name, db_request.user.username, db_request + ) + assert res == "Owner" + + def test_get_user_role_in_project_single_role_maintainer(self, db_request): + project = ProjectFactory.create(name="foo") + db_request.session = pretend.stub( + flash=pretend.call_recorder(lambda *a, **kw: None), + ) + db_request.user = UserFactory.create() + project.users = [db_request.user] + RoleFactory(user=db_request.user, project=project, role_name="Maintainer") + + res = views.get_user_role_in_project( + project.name, db_request.user.username, db_request + ) + assert res == "Maintainer" + + def test_get_user_role_in_project_two_roles_owner_and_maintainer(self, db_request): + project = ProjectFactory.create(name="foo") + db_request.session = pretend.stub( + flash=pretend.call_recorder(lambda *a, **kw: None), + ) + db_request.user = UserFactory.create() + project.users = [db_request.user] + RoleFactory(user=db_request.user, project=project, role_name="Owner") + RoleFactory(user=db_request.user, project=project, role_name="Maintainer") + + res = views.get_user_role_in_project( + project.name, db_request.user.username, db_request + ) + assert res == "Owner" + + def test_get_user_role_in_project_no_role(self, db_request): + project = ProjectFactory.create(name="foo") + db_request.session = pretend.stub( + flash=pretend.call_recorder(lambda *a, **kw: None), + ) + db_request.user = UserFactory.create() + project.users = [db_request.user] + + res = views.get_user_role_in_project( + project.name, db_request.user.username, db_request + ) + assert res == "" + + def test_delete_project(self, monkeypatch, db_request): project = ProjectFactory.create(name="foo") db_request.route_path = pretend.call_recorder(lambda *a, **kw: "/the-redirect") @@ -2319,6 +2387,22 @@ def test_delete_project(self, db_request): ) db_request.POST["confirm_project_name"] = project.normalized_name db_request.user = UserFactory.create() + + get_user_role_in_project = pretend.call_recorder( + lambda project_name, username, req: "Owner" + ) + monkeypatch.setattr(views, "get_user_role_in_project", get_user_role_in_project) + + get_project_contributors = pretend.call_recorder( + lambda project_name, req: [db_request.user] + ) + monkeypatch.setattr(views, "get_project_contributors", get_project_contributors) + + send_removed_project_email = pretend.call_recorder(lambda req, user, **k: None) + monkeypatch.setattr( + views, "send_removed_project_email", send_removed_project_email + ) + db_request.remote_addr = "192.168.1.1" result = views.delete_project(project, db_request) @@ -2329,6 +2413,26 @@ def test_delete_project(self, db_request): assert db_request.route_path.calls == [pretend.call("manage.projects")] assert isinstance(result, HTTPSeeOther) assert result.headers["Location"] == "/the-redirect" + + assert get_user_role_in_project.calls == [ + pretend.call(project.name, db_request.user.username, db_request,), + pretend.call(project.name, db_request.user.username, db_request,), + ] + + assert get_project_contributors.calls == [ + pretend.call(project.name, db_request,) + ] + + assert send_removed_project_email.calls == [ + pretend.call( + db_request, + db_request.user, + project_name=project.name, + submitter_name=db_request.user.username, + submitter_role="Owner", + recipient_role="Owner", + ) + ] assert not (db_request.db.query(Project).filter(Project.name == "foo").count()) @@ -2495,6 +2599,7 @@ def test_delete_project_release(self, monkeypatch): project=pretend.stub( name="foobar", record_event=pretend.call_recorder(lambda *a, **kw: None) ), + created=datetime.datetime(2017, 2, 5, 17, 18, 18, 462_634), ) request = pretend.stub( POST={"confirm_version": release.version}, @@ -2511,7 +2616,25 @@ def test_delete_project_release(self, monkeypatch): ) journal_obj = pretend.stub() journal_cls = pretend.call_recorder(lambda **kw: journal_obj) + + get_user_role_in_project = pretend.call_recorder( + lambda project_name, username, req: "Owner" + ) + monkeypatch.setattr(views, "get_user_role_in_project", get_user_role_in_project) + get_project_contributors = pretend.call_recorder( + lambda project_name, request: [request.user] + ) + monkeypatch.setattr(views, "get_project_contributors", get_project_contributors) + monkeypatch.setattr(views, "JournalEntry", journal_cls) + send_removed_project_release_email = pretend.call_recorder( + lambda req, contrib, **k: None + ) + monkeypatch.setattr( + views, + "send_removed_project_release_email", + send_removed_project_release_email, + ) view = views.ManageProjectRelease(release, request) @@ -2520,6 +2643,25 @@ def test_delete_project_release(self, monkeypatch): assert isinstance(result, HTTPSeeOther) assert result.headers["Location"] == "/the-redirect" + assert get_user_role_in_project.calls == [ + pretend.call(release.project.name, request.user.username, request,), + pretend.call(release.project.name, request.user.username, request,), + ] + assert get_project_contributors.calls == [ + pretend.call(release.project.name, request,) + ] + + assert send_removed_project_release_email.calls == [ + pretend.call( + request, + request.user, + release=release, + submitter_name=request.user.username, + submitter_role="Owner", + recipient_role="Owner", + ) + ] + assert request.db.delete.calls == [pretend.call(release)] assert request.db.add.calls == [pretend.call(journal_obj)] assert request.flags.enabled.calls == [ diff --git a/warehouse/email/__init__.py b/warehouse/email/__init__.py index 8b6b170498b7..9ff6b2118b9a 100644 --- a/warehouse/email/__init__.py +++ b/warehouse/email/__init__.py @@ -213,6 +213,40 @@ def send_two_factor_removed_email(request, user, method): return {"method": pretty_methods[method], "username": user.username} +@_email("removed-project") +def send_removed_project_email( + request, user, *, project_name, submitter_name, submitter_role, recipient_role +): + recipient_role_descr = "an owner" + if recipient_role == "Maintainer": + recipient_role_descr = "a maintainer" + + return { + "project": project_name, + "submitter": submitter_name, + "submitter_role": submitter_role.lower(), + "recipient_role_descr": recipient_role_descr, + } + + +@_email("removed-project-release") +def send_removed_project_release_email( + request, user, *, release, submitter_name, submitter_role, recipient_role +): + recipient_role_descr = "an owner" + if recipient_role == "Maintainer": + recipient_role_descr = "a maintainer" + + return { + "project": release.project.name, + "release": release.version, + "release_date": release.created.strftime("%Y-%m-%d"), + "submitter": submitter_name, + "submitter_role": submitter_role.lower(), + "recipient_role_descr": recipient_role_descr, + } + + def includeme(config): email_sending_class = config.maybe_dotted(config.registry.settings["mail.backend"]) config.register_service_factory(email_sending_class.create_service, IEmailSender) diff --git a/warehouse/locale/messages.pot b/warehouse/locale/messages.pot index d200685160da..f6a609521a49 100644 --- a/warehouse/locale/messages.pot +++ b/warehouse/locale/messages.pot @@ -169,25 +169,25 @@ msgstr "" msgid "Email address ${email_address} verified. ${confirm_message}." msgstr "" -#: warehouse/manage/views.py:172 +#: warehouse/manage/views.py:174 msgid "Email ${email_address} added - check your email for a verification link" msgstr "" -#: warehouse/manage/views.py:653 warehouse/manage/views.py:689 +#: warehouse/manage/views.py:655 warehouse/manage/views.py:691 msgid "" "You must provision a two factor method before recovery codes can be " "generated" msgstr "" -#: warehouse/manage/views.py:664 +#: warehouse/manage/views.py:666 msgid "Recovery codes already generated" msgstr "" -#: warehouse/manage/views.py:665 +#: warehouse/manage/views.py:667 msgid "Generating new recovery codes will invalidate your existing codes." msgstr "" -#: warehouse/manage/views.py:715 +#: warehouse/manage/views.py:717 msgid "Invalid credentials. Try again" msgstr "" @@ -1255,6 +1255,48 @@ msgid "" "%(new_email)s" msgstr "" +#: warehouse/templates/email/removed-project/body.html:25 +#, python-format +msgid "The project %(project)s has been deleted." +msgstr "" + +#: warehouse/templates/email/removed-project-release/body.html:26 +#: warehouse/templates/email/removed-project/body.html:26 +#, python-format +msgid "" +"Deleted by: %(submitter)s with a role:\n" +" %(role)s." +msgstr "" + +#: warehouse/templates/email/removed-project-release/body.html:32 +#: warehouse/templates/email/removed-project/body.html:32 +#, python-format +msgid "" +"If this was a mistake, you can email %(email_address)s to communicate with the PyPI " +"administrators." +msgstr "" + +#: warehouse/templates/email/removed-project/body.html:38 +#, python-format +msgid "" +"You are receiving this because you are %(recipient_role_descr)s of this " +"project." +msgstr "" + +#: warehouse/templates/email/removed-project-release/body.html:25 +#, python-format +msgid "The %(project)s release %(release)s released on %(date)s has been deleted." +msgstr "" + +#: warehouse/templates/email/removed-project-release/body.html:38 +#, python-format +msgid "" +"\n" +"You are receiving this because you are %(recipient_role_descr)s of this " +"project." +msgstr "" + #: warehouse/templates/email/two-factor-added/body.html:18 #, python-format msgid "" diff --git a/warehouse/manage/views.py b/warehouse/manage/views.py index a8f807243961..a37ee2c1e5f4 100644 --- a/warehouse/manage/views.py +++ b/warehouse/manage/views.py @@ -38,6 +38,8 @@ send_email_verification_email, send_password_change_email, send_primary_email_change_email, + send_removed_project_email, + send_removed_project_release_email, send_two_factor_added_email, send_two_factor_removed_email, ) @@ -899,6 +901,46 @@ def manage_project_settings(project, request): return {"project": project} +def get_project_contributors(project_name, request): + query_res = ( + request.db.query(Project) + .join(User, Project.users) + .filter(Project.name == project_name) + .one() + ) + return query_res.users + + +def get_user_role_in_project(project_name, username, request): + raw_res = ( + request.db.query(Project) + .join(User, Project.users) + .filter(User.username == username, Project.name == project_name) + .with_entities(Role.role_name) + .distinct(Role.role_name) + .all() + ) + + query_res = [] + for el in raw_res: + if el.role_name is not None: + query_res.append(el) + + user_role = "" + # This check is needed because of + # issue https://github.com/pypa/warehouse/issues/2745 + # which is not yet resolved and a user could be an owner + # and a maintainer at the same time + if len(query_res) == 2 and ( + query_res[0].role_name == "Owner" or query_res[1].role_name == "Owner" + ): + user_role = "Owner" + if len(query_res) == 1: + user_role = query_res[0].role_name + + return user_role + + @view_config( route_name="manage.project.delete_project", context=Project, @@ -921,6 +963,26 @@ def delete_project(project, request): ) confirm_project(project, request, fail_route="manage.project.settings") + + submitter_role = get_user_role_in_project( + project.name, request.user.username, request + ) + contributors = get_project_contributors(project.name, request) + + for contributor in contributors: + contributor_role = get_user_role_in_project( + project.name, contributor.username, request + ) + + send_removed_project_email( + request, + contributor, + project_name=project.name, + submitter_name=request.user.username, + submitter_role=submitter_role, + recipient_role=contributor_role, + ) + remove_project(project, request) return HTTPSeeOther(request.route_path("manage.projects")) @@ -1053,6 +1115,11 @@ def delete_project_release(self): ) ) + submitter_role = get_user_role_in_project( + self.release.project.name, self.request.user.username, self.request + ) + contributors = get_project_contributors(self.release.project.name, self.request) + self.request.db.add( JournalEntry( name=self.release.project.name, @@ -1078,6 +1145,20 @@ def delete_project_release(self): f"Deleted release {self.release.version!r}", queue="success" ) + for contributor in contributors: + contributor_role = get_user_role_in_project( + self.release.project.name, contributor.username, self.request + ) + + send_removed_project_release_email( + self.request, + contributor, + release=self.release, + submitter_name=self.request.user.username, + submitter_role=submitter_role, + recipient_role=contributor_role, + ) + return HTTPSeeOther( self.request.route_path( "manage.project.releases", project_name=self.release.project.name diff --git a/warehouse/templates/email/removed-project-release/body.html b/warehouse/templates/email/removed-project-release/body.html new file mode 100644 index 000000000000..476fcdfc014e --- /dev/null +++ b/warehouse/templates/email/removed-project-release/body.html @@ -0,0 +1,41 @@ +{# + # 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. +-#} +{% extends "email/_base/body.html" %} + +{% block extra_style %} +ul.collaborator-details { +list-style-type: none; +} +{% endblock %} + +{% block content %} +

+

    +
  • {% trans project=project, release=release, date=release_date %}The {{ project }} release {{ release }} released on {{ date }} has been deleted.{% endtrans %}
  • +
  • {% trans submitter=submitter, role=submitter_role %}Deleted by: {{ submitter }} with a role: + {{ role }}.{% endtrans %} +
  • +
+

+ +

{% trans href='mailto:admin@pypi.org', email_address='admin@pypi.org' %}If this was a mistake, you can email {{ email_address }} to communicate with the PyPI administrators.{% endtrans %}

+{% endblock %} + +{% block reason %} + +

{% trans recipient_role_descr=recipient_role_descr %} +You are receiving this because you are {{ recipient_role_descr }} of this project.{% endtrans %}

+ +{% endblock %} diff --git a/warehouse/templates/email/removed-project-release/body.txt b/warehouse/templates/email/removed-project-release/body.txt new file mode 100644 index 000000000000..364d2c4c9555 --- /dev/null +++ b/warehouse/templates/email/removed-project-release/body.txt @@ -0,0 +1,29 @@ +{# + # 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. +-#} + +{% extends "email/_base/body.txt" %} + +{% block content %} + {% trans project=project, release=release, date=release_date %}The {{ project }} release {{ release }} released on {{ date }} has been deleted.{% endtrans %} + + {% trans submitter=submitter, role=submitter_role %}Deleted by: {{ submitter }} with a role: {{ role }}.{% endtrans %} + + {% trans email_address='admin@pypi.org' %}If this was a mistake, you can email {{ email_address }} to communicate with the PyPI administrators.{% endtrans %} +{% endblock %} + +{% block reason %} + {% trans recipient_role_descr=recipient_role_descr %} + You are receiving this because you are {{ recipient_role_descr }} of this project. + {% endtrans %} +{% endblock %} diff --git a/warehouse/templates/email/removed-project-release/subject.txt b/warehouse/templates/email/removed-project-release/subject.txt new file mode 100644 index 000000000000..1fc56452dd25 --- /dev/null +++ b/warehouse/templates/email/removed-project-release/subject.txt @@ -0,0 +1,18 @@ +{# + # 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. +-#} + +{% extends "email/_base/subject.txt" %} + +{% block content %} +{% trans project=project, release=release %}The {{ project }} release {{ release }} has been deleted.{% endtrans %}{% endblock %} diff --git a/warehouse/templates/email/removed-project/body.html b/warehouse/templates/email/removed-project/body.html new file mode 100644 index 000000000000..278f0554f5e3 --- /dev/null +++ b/warehouse/templates/email/removed-project/body.html @@ -0,0 +1,39 @@ +{# + # 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. +-#} +{% extends "email/_base/body.html" %} + +{% block extra_style %} +ul.collaborator-details { +list-style-type: none; +} +{% endblock %} + +{% block content %} +

+

    +
  • {% trans project=project %}The project {{ project }} has been deleted.{% endtrans %}
  • +
  • {% trans submitter=submitter, role=submitter_role %}Deleted by: {{ submitter }} with a role: + {{ role }}.{% endtrans %} +
  • +
+

+ +

{% trans href='mailto:admin@pypi.org', email_address='admin@pypi.org' %}If this was a mistake, you can email {{ email_address }} to communicate with the PyPI administrators.{% endtrans %}

+{% endblock %} + +{% block reason %} + +

{% trans recipient_role_descr=recipient_role_descr %}You are receiving this because you are {{ recipient_role_descr }} of this project.{% endtrans %}

+{% endblock %} diff --git a/warehouse/templates/email/removed-project/body.txt b/warehouse/templates/email/removed-project/body.txt new file mode 100644 index 000000000000..8a02f386eaff --- /dev/null +++ b/warehouse/templates/email/removed-project/body.txt @@ -0,0 +1,29 @@ +{# + # 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. +-#} + +{% extends "email/_base/body.txt" %} + +{% block content %} + {% trans project=project %}The project {{ project }} has been deleted.{% endtrans %} + + {% trans submitter=submitter, role=submitter_role %}Deleted by: {{ submitter }} with a role: {{ role }}.{% endtrans %} + + {% trans email_address='admin@pypi.org' %}If this was a mistake, you can email {{ email_address }} to communicate with the PyPI administrators.{% endtrans %} +{% endblock %} + +{% block reason %} + {% trans recipient_role_descr=recipient_role_descr %} + You are receiving this because you are {{ recipient_role_descr }} of this project. + {% endtrans %} +{% endblock %} diff --git a/warehouse/templates/email/removed-project/subject.txt b/warehouse/templates/email/removed-project/subject.txt new file mode 100644 index 000000000000..3738aa2c4f78 --- /dev/null +++ b/warehouse/templates/email/removed-project/subject.txt @@ -0,0 +1,19 @@ +{# + # 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. +-#} + +{% extends "email/_base/subject.txt" %} + +{% block content %} + {% trans project=project %}The project {{ project }} has been deleted.{% endtrans %} +{% endblock %}