launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #26898
[Merge] ~ilasc/launchpad:add-delete-account-ui into launchpad:master
Ioana Lasc has proposed merging ~ilasc/launchpad:add-delete-account-ui into launchpad:master.
Commit message:
Add UI for close account celery job
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~ilasc/launchpad/+git/launchpad/+merge/401207
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~ilasc/launchpad:add-delete-account-ui into launchpad:master.
diff --git a/lib/lp/registry/browser/configure.zcml b/lib/lp/registry/browser/configure.zcml
index 8895a76..1a7e2c4 100644
--- a/lib/lp/registry/browser/configure.zcml
+++ b/lib/lp/registry/browser/configure.zcml
@@ -982,6 +982,13 @@
template="../templates/person-review.pt"
/>
<browser:page
+ name="+deleteaccount"
+ for="lp.registry.interfaces.person.IPerson"
+ class="lp.registry.browser.person.PersonDeleteView"
+ permission="launchpad.Moderate"
+ template="../templates/person-delete.pt"
+ />
+ <browser:page
name="+reviewaccount"
for="lp.registry.interfaces.person.IPerson"
class="lp.registry.browser.person.PersonAccountAdministerView"
diff --git a/lib/lp/registry/browser/person.py b/lib/lp/registry/browser/person.py
index 2b71694..441cfb3 100644
--- a/lib/lp/registry/browser/person.py
+++ b/lib/lp/registry/browser/person.py
@@ -185,6 +185,7 @@ from lp.registry.interfaces.persondistributionsourcepackage import (
from lp.registry.interfaces.personociproject import IPersonOCIProjectFactory
from lp.registry.interfaces.personproduct import IPersonProductFactory
from lp.registry.interfaces.persontransferjob import (
+ IPersonCloseAccountJobs,
IPersonDeactivateJobSource,
)
from lp.registry.interfaces.pillar import IPillarNameSet
@@ -1275,6 +1276,29 @@ class PersonAdministerView(PersonRenameFormMixin):
self.updateContextFromData(data)
+class PersonDeleteView(LaunchpadFormView):
+ """Delete an `IPerson`."""
+ schema = Interface
+ label = "Delete person"
+
+ @property
+ def next_url(self):
+ """See `LaunchpadEditFormView`."""
+ return canonical_url(self.context)
+
+ @property
+ def cancel_url(self):
+ """See `LaunchpadEditFormView`."""
+ return canonical_url(self.context)
+
+ @action('Delete', name='delete')
+ def delete_action(self, action, data):
+ """Delete the IPerson."""
+ getUtility(IPersonCloseAccountJobs).create(self.context)
+ self.request.response.addInfoNotification(
+ "This account will now be permanently removed.")
+
+
class IAccountAdministerSchema(Interface):
status = copy_field(IAccount['status'], required=True, readonly=False)
diff --git a/lib/lp/registry/browser/tests/test_person.py b/lib/lp/registry/browser/tests/test_person.py
index b327adc..a3decc4 100644
--- a/lib/lp/registry/browser/tests/test_person.py
+++ b/lib/lp/registry/browser/tests/test_person.py
@@ -55,7 +55,11 @@ from lp.registry.browser.person import PersonView
from lp.registry.browser.team import TeamInvitationView
from lp.registry.enums import PersonVisibility
from lp.registry.interfaces.karma import IKarmaCacheManager
-from lp.registry.interfaces.persontransferjob import IPersonMergeJobSource
+from lp.registry.interfaces.person import IPersonSet
+from lp.registry.interfaces.persontransferjob import (
+ IPersonCloseAccountJobs,
+ IPersonMergeJobSource,
+ )
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.interfaces.teammembership import (
ITeamMembershipSet,
@@ -70,6 +74,7 @@ from lp.services.database.interfaces import IStore
from lp.services.features.testing import FeatureFixture
from lp.services.identity.interfaces.account import AccountStatus
from lp.services.identity.interfaces.emailaddress import IEmailAddressSet
+from lp.services.job.interfaces.job import JobStatus
from lp.services.log.logger import DevNullLogger
from lp.services.mail import stub
from lp.services.propertycache import clear_property_cache
@@ -90,10 +95,12 @@ from lp.soyuz.enums import (
from lp.soyuz.interfaces.livefs import LIVEFS_FEATURE_FLAG
from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
from lp.testing import (
+ admin_logged_in,
ANONYMOUS,
BrowserTestCase,
login,
login_person,
+ logout,
monkey_patch,
person_logged_in,
record_two_runs,
@@ -116,6 +123,7 @@ from lp.testing.pages import (
setupBrowserForUser,
)
from lp.testing.publication import test_traverse
+from lp.testing.sampledata import ADMIN_EMAIL
from lp.testing.views import (
create_initialized_view,
create_view,
@@ -318,6 +326,43 @@ class TestPersonIndexView(BrowserTestCase):
self.assertEqual(1, len(notifications))
self.assertEqual(message, notifications[0].message)
+ def test_deleteAccount_admin(self):
+ person = self.factory.makePerson(name='finch')
+ admin = getUtility(IPersonSet).find(ADMIN_EMAIL).any()
+ browser = self.getViewBrowser(
+ person, view_name='+deleteaccount', user=admin)
+ browser.getControl("Delete").click()
+ self.assertIn(
+ "This account will now be permanently removed.",
+ six.ensure_text(browser.contents))
+
+ # the delete job is created with Wainting Status
+ job_source = getUtility(IPersonCloseAccountJobs)
+ with person_logged_in(admin):
+ job = removeSecurityProxy(job_source.find(person).one())
+ self.assertEqual(JobStatus.WAITING, job.status)
+
+ def test_deleteAccount_registry_expert(self):
+ person = self.factory.makePerson(name='finch')
+ registry_expert = self.factory.makePerson()
+ admin = getUtility(ILaunchpadCelebrities).admin.teamowner
+ with admin_logged_in():
+ getUtility(ILaunchpadCelebrities).registry_experts.addMember(
+ registry_expert, admin)
+ logout()
+ with person_logged_in(registry_expert):
+ browser = self.getViewBrowser(
+ person, view_name='+deleteaccount', user=registry_expert)
+ browser.getControl("Delete").click()
+ self.assertIn(
+ "This account will now be permanently removed.",
+ six.ensure_text(browser.contents))
+ # the delete job is created with Wainting Status
+ job_source = getUtility(IPersonCloseAccountJobs)
+ with person_logged_in(admin):
+ job = removeSecurityProxy(job_source.find(person).one())
+ self.assertEqual(JobStatus.WAITING, job.status)
+
def test_display_utcoffset(self):
person = self.factory.makePerson(time_zone='Asia/Kolkata')
html = create_initialized_view(person, '+portlet-contact-details')()
diff --git a/lib/lp/registry/model/persontransferjob.py b/lib/lp/registry/model/persontransferjob.py
index 010a1af..9e2b4c2 100644
--- a/lib/lp/registry/model/persontransferjob.py
+++ b/lib/lp/registry/model/persontransferjob.py
@@ -468,16 +468,16 @@ class PersonCloseAccountJob(PersonTransferJobDerived):
def find(cls, person=None):
"""See `IPersonMergeJobSource`."""
conditions = [
- PersonCloseAccountJob.job_type == cls.class_job_type,
- PersonCloseAccountJob.job_id == Job.id,
+ PersonTransferJob.job_type == cls.class_job_type,
+ PersonTransferJob.job_id == Job.id,
Job._status.is_in(Job.PENDING_STATUSES)]
arg_conditions = []
if person:
- arg_conditions.append(PersonCloseAccountJob.major_person == person)
+ arg_conditions.append(PersonTransferJob.major_person == person)
conditions.extend(arg_conditions)
return DecoratedResultSet(
- IStore(PersonCloseAccountJob).find(
- PersonCloseAccountJob, *conditions), cls)
+ IStore(PersonTransferJob).find(
+ PersonTransferJob, *conditions), cls)
@property
def person(self):
diff --git a/lib/lp/registry/templates/person-delete.pt b/lib/lp/registry/templates/person-delete.pt
new file mode 100644
index 0000000..17afd65
--- /dev/null
+++ b/lib/lp/registry/templates/person-delete.pt
@@ -0,0 +1,38 @@
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ metal:use-macro="view/macro:page/main_only"
+ i18n:domain="launchpad"
+>
+ <head>
+ <metal:css fill-slot="head_epilogue">
+ <style type="text/css">
+ .yui-main {
+ clear: both;
+ }
+ </style>
+ </metal:css>
+ </head>
+ <body>
+ <div metal:fill-slot="main">
+ <div metal:use-macro="context/@@launchpad_form/form">
+ <div metal:fill-slot="extra_info">
+ <p>
+ This is an invasive and irreversible erasure of data operation.
+ </p>
+ <p>
+ It will change the account's display name to "Removed by request",
+ change the username to an anonymous generated name starting with "removed",
+ remove all email addresses and OpenID identifiers, unassign all bugs and questions
+ assigned to the user, mark all unsolved questions as Solved,
+ and remove all code-of-conduct signatures, OpenPGP keys, subscriptions,
+ links to other accounts, SSH keys, karma, team memberships, answer contacts,
+ and attendance records for future sprints.
+ </p>
+ </div>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/lib/lp/registry/templates/person-review.pt b/lib/lp/registry/templates/person-review.pt
index d1addb7..b157ac2 100644
--- a/lib/lp/registry/templates/person-review.pt
+++ b/lib/lp/registry/templates/person-review.pt
@@ -38,6 +38,12 @@
tal:attributes="alt string:edit" src="/@@/edit" />
Review the user's Launchpad information</a>.
</p>
+ <p>
+ <a tal:attributes="
+ href string:${view/person/fmt:url}/+deleteaccount"><img
+ tal:attributes="alt string:remove" src="/@@/remove" />
+ Permanently delete this account</a>.
+ </p>
<table id="summary">
<tr>
<th>Created:</th>
diff --git a/lib/lp/services/config/schema-lazr.conf b/lib/lp/services/config/schema-lazr.conf
index 158a30b..2964aac 100644
--- a/lib/lp/services/config/schema-lazr.conf
+++ b/lib/lp/services/config/schema-lazr.conf
@@ -1995,7 +1995,7 @@ dbuser: webhookrunner
[IPersonCloseAccountJobs]
module: lp.registry.interfaces.persontransferjob
-dbuser: launchpad
+dbuser: person-transfer-job
crontab_group: MAIN
[job_runner_queues]
Follow ups