diff --git a/odoo/addons/base/data/ir_cron_data.xml b/odoo/addons/base/data/ir_cron_data.xml
index fb9cd881dd0..662b6db3607 100644
--- a/odoo/addons/base/data/ir_cron_data.xml
+++ b/odoo/addons/base/data/ir_cron_data.xml
@@ -9,4 +9,14 @@
days
-1
+
+
+ Base: Portal Users Deletion
+
+ code
+ model._gc_portal_users()
+ 1
+ days
+ -1
+
diff --git a/odoo/addons/base/models/res_users_deletion.py b/odoo/addons/base/models/res_users_deletion.py
index 4cccf48502f..f61198b967a 100644
--- a/odoo/addons/base/models/res_users_deletion.py
+++ b/odoo/addons/base/models/res_users_deletion.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
+import threading
from odoo import api, fields, models
@@ -35,8 +36,8 @@ class ResUsersDeletion(models.Model):
if user_deletion.user_id:
user_deletion.user_id_int = user_deletion.user_id.id
- @api.autovacuum
- def _gc_portal_users(self):
+ @api.model
+ def _gc_portal_users(self, batch_size=10):
"""Remove the portal users that asked to deactivate their account.
(see ::_deactivate_portal_user)
@@ -45,22 +46,55 @@ class ResUsersDeletion(models.Model):
create_uid, write_uid on each models, which are not always indexed). Because of
that, this operation is done in a CRON.
"""
- delete_requests = self.search([('state', '=', 'todo')])
+ delete_requests = self.search([("state", "=", "todo")])
# filter the requests related to a deleted user
done_requests = delete_requests.filtered(lambda request: not request.user_id)
- done_requests.state = 'done'
+ done_requests.state = "done"
- for delete_request in (delete_requests - done_requests):
+ todo_requests = delete_requests - done_requests
+ batch_requests = todo_requests[:batch_size]
+
+ auto_commit = not getattr(threading.current_thread(), "testing", False)
+
+ for delete_request in batch_requests:
user = delete_request.user_id
user_name = user.name
+ requester_name = delete_request.create_uid.name
+ # Step 1: Delete User
try:
- with self.env.cr.savepoint():
- partner = user.partner_id
- user.unlink()
- partner.unlink()
- _logger.info('User #%i %r, deleted. Original request from %r.',
- user.id, user_name, delete_request.create_uid.name)
- delete_request.state = 'done'
- except Exception:
- delete_request.state = 'fail'
+ self.env.cr.execute("SAVEPOINT delete_user")
+ partner = user.partner_id
+ user.unlink()
+ _logger.info("User #%i %r, deleted. Original request from %r.",
+ user.id, user_name, delete_request.create_uid.name)
+ self.env.cr.execute("RELEASE SAVEPOINT delete_user")
+ delete_request.state = 'done'
+ except Exception as e:
+ _logger.error("User #%i %r could not be deleted. Original request from %r. Related error: %s",
+ user.id, user_name, requester_name, e)
+ self.env.cr.execute("ROLLBACK TO SAVEPOINT delete_user")
+ delete_request.state = "fail"
+ # make sure we never rollback the work we've done, this can take a long time
+ if auto_commit:
+ self.env.cr.commit()
+ if delete_request.state == "fail":
+ continue
+
+ # Step 2: Delete Linked Partner
+ # Could be impossible if the partner is linked to a SO for example
+ try:
+ self.env.cr.execute("SAVEPOINT delete_partner")
+ partner.unlink()
+ _logger.info("Partner #%i %r, deleted. Original request from %r.",
+ partner.id, user_name, delete_request.create_uid.name)
+ self.env.cr.execute("RELEASE SAVEPOINT delete_partner")
+ except Exception as e:
+ _logger.warning("Partner #%i %r could not be deleted. Original request from %r. Related error: %s",
+ partner.id, user_name, requester_name, e)
+ self.env.cr.execute("ROLLBACK TO SAVEPOINT delete_partner")
+ # make sure we never rollback the work we've done, this can take a long time
+ if auto_commit:
+ self.env.cr.commit()
+ if len(todo_requests) > batch_size:
+ self.env.ref("base.ir_cron_res_users_deletion")._trigger()
diff --git a/odoo/addons/base/tests/test_res_users.py b/odoo/addons/base/tests/test_res_users.py
index cd4a472de11..ac6dd21a07d 100644
--- a/odoo/addons/base/tests/test_res_users.py
+++ b/odoo/addons/base/tests/test_res_users.py
@@ -133,7 +133,7 @@ class TestUsers(TransactionCase):
with self.assertRaises(UserError, msg='Internal users should not be able to deactivate their account'):
user_internal._deactivate_portal_user()
- @mute_logger('odoo.sql_db')
+ @mute_logger('odoo.sql_db', 'odoo.addons.base.models.res_users_deletion')
def test_deactivate_portal_users_archive_and_remove(self):
"""Test that if the account can not be removed, it's archived instead
and sensitive information are removed.