← Back to team overview

launchpad-reviewers team mailing list archive

[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