(backend) process reconciliation requests as transactions

Process each reconciliation requests individually as a transaction,
with the bulk updates/deletes made for each requests, to avoid
interference between requests and unwanted states
This commit is contained in:
Sylvain Boissel
2026-02-03 10:48:31 +01:00
parent c65ab34ffb
commit 313e14db4b
2 changed files with 39 additions and 36 deletions
+2 -27
View File
@@ -2,6 +2,7 @@
from django.contrib import admin, messages
from django.contrib.auth import admin as auth_admin
from django.db import transaction
from django.shortcuts import redirect
from django.utils.translation import gettext_lazy as _
@@ -119,39 +120,13 @@ def process_reconciliation(_modeladmin, _request, queryset):
Admin action to process selected user reconciliations.
The action will process only entries that are ready and have both emails checked.
Its action is threefold:
- Transfer document accesses from inactive to active user, updating roles as needed.
- Activate the active user and deactivate the inactive user.
"""
processable_entries = queryset.filter(
status="ready", active_email_checked=True, inactive_email_checked=True
)
# Prepare the bulk operations
updated_documentaccess = []
removed_documentaccess = []
update_users_active_status = []
for entry in processable_entries:
new_updated_documentaccess, new_removed_documentaccess = (
entry.process_documentaccess_reconciliation()
)
updated_documentaccess += new_updated_documentaccess
removed_documentaccess += new_removed_documentaccess
entry.active_user.is_active = True
entry.inactive_user.is_active = False
update_users_active_status.append(entry.active_user)
update_users_active_status.append(entry.inactive_user)
# Actually perform the bulk operations
models.DocumentAccess.objects.bulk_update(updated_documentaccess, ["user", "role"])
if removed_documentaccess:
ids_to_delete = [rd.id for rd in removed_documentaccess]
models.DocumentAccess.objects.filter(id__in=ids_to_delete).delete()
models.User.objects.bulk_update(update_users_active_status, ["is_active"])
entry.process_reconciliation_request()
@admin.register(models.UserReconciliation)
+37 -9
View File
@@ -378,9 +378,44 @@ class UserReconciliation(BaseModel):
super().save(*args, **kwargs)
def process_documentaccess_reconciliation(self):
@transaction.atomic
def process_reconciliation_request(self):
"""
Process the reconciliation by transferring document accesses from the inactive user
Process the reconciliation request as a transaction.
Its action is threefold:
- Transfer document accesses from inactive to active user, updating roles as needed.
- Activate the active user and deactivate the inactive user.
- Update the reconciliation entry itself.
"""
# Prepare the data to perform the reconciliation on
updated_accesses, removed_accesses = (
self.prepare_documentaccess_reconciliation()
)
self.active_user.is_active = True
self.inactive_user.is_active = False
# Actually perform the bulk operations
DocumentAccess.objects.bulk_update(updated_accesses, ["user", "role"])
if removed_accesses:
ids_to_delete = [rd.id for rd in removed_accesses]
DocumentAccess.objects.filter(id__in=ids_to_delete).delete()
User.objects.bulk_update([self.active_user, self.inactive_user], ["is_active"])
# Wrap up the reconciliation entry
self.logs += f"""Requested update for {len(updated_accesses)} DocumentAccess items
and deletion for {len(removed_accesses)} DocumentAccess items.\n"""
self.status = "done"
self.save()
self.send_reconciliation_done_email()
def prepare_documentaccess_reconciliation(self):
"""
Prepare the reconciliation by transferring document accesses from the inactive user
to the active user.
"""
updated_accesses = []
@@ -408,13 +443,6 @@ class UserReconciliation(BaseModel):
entry.user = self.active_user
updated_accesses.append(entry)
self.logs += f"""Requested update for {len(updated_accesses)} DocumentAccess items
and deletion for {len(removed_accesses)} DocumentAccess items.\n"""
self.status = "done"
self.send_reconciliation_done_email()
self.save()
return updated_accesses, removed_accesses
def send_reconciliation_confirm_email(