✨(backend) manage reconciliation requests for user accounts (#1878)
For now, the reconciliation requests are imported through CSV in the Django admin, which sends confirmation email to both addresses. When both are checked, the actual reconciliation is processed, and all user-related content is updated. ## Purpose Fix #1616 // Replaces #1708 For now, the reconciliation requests are imported through CSV in the Django admin, which sends confirmation email to both addresses. When both are checked, the actual reconciliation is processed, and all user-related content is updated. ## Proposal - [x] New `UserReconciliationCsvImport` model to manage the import of reconciliation requests through a task (`user_reconciliation_csv_import_job`) - [x] New `UserReconciliation` model to store the user reconciliation requests themselves (a row = a `active_user`/`inactive_user` pair) - [x] On save, a confirmation email is sent to the users - [x] A `process_reconciliation` admin action process the action on the requested entries, if both emails have been checked. - [x] Bulk update the `DocumentAccess` items, while managing the case where both users have access to the document (keeping the higher role) - [x] Bulk update the `LinkTrace` items, while managing the case where both users have link traces to the document - [x] Bulk update the `DocumentFavorite` items, while managing the case where both users have put the document in their favorites - [x] Bulk update the comment system items (`Thread`, `Comment` and `Reaction` items) - [x] Bulk update the `is_active` status on both users - [x] New `USER_RECONCILIATION_FORM_URL` env variable for the "make a new request" URL in an email. - [x] Write unit tests - [x] Remove the unused `email_user()` method on `User`, replaced with `send_email()` similar to the one on the `Document` model ## Demo page reconciliation success <img width="1149" height="746" alt="image" src="https://github.com/user-attachments/assets/09ba2b38-7af3-41fa-a64f-ce3c4fd8548d" /> --------- Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
This commit is contained in:
@@ -41,6 +41,7 @@ from lasuite.tools.email import get_domain_from_email
|
||||
from rest_framework import filters, status, viewsets
|
||||
from rest_framework import response as drf_response
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from core import authentication, choices, enums, models
|
||||
from core.api.filters import remove_accents
|
||||
@@ -316,6 +317,59 @@ class UserViewSet(
|
||||
)
|
||||
|
||||
|
||||
class ReconciliationConfirmView(APIView):
|
||||
"""API endpoint to confirm user reconciliation emails.
|
||||
|
||||
GET /user-reconciliations/{user_type}/{confirmation_id}/
|
||||
Marks `active_email_checked` or `inactive_email_checked` to True.
|
||||
"""
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
def get(self, request, user_type, confirmation_id):
|
||||
"""
|
||||
Check the confirmation ID and mark the corresponding email as checked.
|
||||
"""
|
||||
try:
|
||||
# validate UUID
|
||||
uuid_obj = uuid.UUID(str(confirmation_id))
|
||||
except ValueError:
|
||||
return drf_response.Response(
|
||||
{"detail": "Badly formatted confirmation id"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if user_type not in ("active", "inactive"):
|
||||
return drf_response.Response(
|
||||
{"detail": "Invalid user_type"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
lookup = (
|
||||
{"active_email_confirmation_id": uuid_obj}
|
||||
if user_type == "active"
|
||||
else {"inactive_email_confirmation_id": uuid_obj}
|
||||
)
|
||||
|
||||
try:
|
||||
rec = models.UserReconciliation.objects.get(**lookup)
|
||||
except models.UserReconciliation.DoesNotExist:
|
||||
return drf_response.Response(
|
||||
{"detail": "Reconciliation entry not found"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
field_name = (
|
||||
"active_email_checked"
|
||||
if user_type == "active"
|
||||
else "inactive_email_checked"
|
||||
)
|
||||
if not getattr(rec, field_name):
|
||||
setattr(rec, field_name, True)
|
||||
rec.save()
|
||||
|
||||
return drf_response.Response({"detail": "Confirmation received"})
|
||||
|
||||
|
||||
class ResourceAccessViewsetMixin:
|
||||
"""Mixin with methods common to all access viewsets."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user