Skip to content

Commit 90b3468

Browse files
committed
Add audit log entries for bulk actions on grants
1 parent a7c4957 commit 90b3468

2 files changed

Lines changed: 75 additions & 2 deletions

File tree

backend/custom_admin/admin.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from django.contrib import admin
77
from django.urls import path
88

9+
from custom_admin.audit import create_change_admin_log_entry
10+
911
SITE_NAME = "PyCon Italia"
1012

1113
admin.site.site_header = SITE_NAME
@@ -58,15 +60,50 @@ def wrapper(modeladmin, request, queryset):
5860
@admin.action(description="Confirm pending status change")
5961
@validate_single_conference_selection
6062
def confirm_pending_status(modeladmin, request, queryset):
63+
"""
64+
Efficiently bulk-update status with pending_status, and accurately log the change per object.
65+
"""
66+
# Use values_list to fetch ids and old statuses before updating.
67+
changed_objs_info = list(queryset.values_list("pk", "status", "pending_status"))
68+
69+
# Perform the bulk update.
6170
queryset.update(
6271
status=F("pending_status"),
6372
pending_status=None,
6473
)
6574

75+
model = queryset.model
76+
for pk, old_status, pending_status in changed_objs_info:
77+
obj = model.objects.get(pk=pk)
78+
create_change_admin_log_entry(
79+
request.user,
80+
obj,
81+
change_message=(
82+
f"[Bulk Admin Action] Status changed from '{old_status}' to '{pending_status}'."
83+
),
84+
)
85+
6686

6787
@admin.action(description="Reset pending status to status")
6888
@validate_single_conference_selection
6989
def reset_pending_status_back_to_status(modeladmin, request, queryset):
90+
"""
91+
Efficiently bulk-reset pending_status to None, and accurately log the change per object.
92+
"""
93+
changed_objs_info = list(queryset.values_list("pk", "pending_status"))
94+
7095
queryset.update(
7196
pending_status=None,
7297
)
98+
99+
model = queryset.model
100+
for pk, old_pending_status in changed_objs_info:
101+
if old_pending_status is not None:
102+
obj = model.objects.get(pk=pk)
103+
create_change_admin_log_entry(
104+
request.user,
105+
obj,
106+
change_message=(
107+
f"[Bulk Admin Action] pending_status reset from '{old_pending_status}' to None."
108+
),
109+
)

backend/grants/tests/test_admin.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ def test_mark_rejected_and_send_email(rf, mocker, admin_user):
586586
).exists()
587587

588588

589-
def test_confirm_pending_status_action(rf):
589+
def test_confirm_pending_status_action(rf, admin_user):
590590
grant_1 = GrantFactory(
591591
status=Grant.Status.pending,
592592
pending_status=Grant.Status.confirmed,
@@ -611,6 +611,7 @@ def test_confirm_pending_status_action(rf):
611611
)
612612

613613
request = rf.get("/")
614+
request.user = admin_user
614615
confirm_pending_status(
615616
None, request, Grant.objects.filter(id__in=[grant_1.id, grant_2.id, grant_3.id])
616617
)
@@ -628,11 +629,28 @@ def test_confirm_pending_status_action(rf):
628629
assert grant_2.pending_status is None
629630
assert grant_3.pending_status is None
630631

632+
# Verify audit log entries were created correctly
633+
assert LogEntry.objects.filter(
634+
object_id=grant_1.id,
635+
change_message="[Bulk Admin Action] Status changed from 'pending' to 'confirmed'.",
636+
).exists()
637+
assert LogEntry.objects.filter(
638+
object_id=grant_2.id,
639+
change_message="[Bulk Admin Action] Status changed from 'rejected' to 'waiting_list'.",
640+
).exists()
641+
assert LogEntry.objects.filter(
642+
object_id=grant_3.id,
643+
change_message="[Bulk Admin Action] Status changed from 'waiting_list' to 'waiting_list_maybe'.",
644+
).exists()
645+
631646
# Left out from the action
632647
assert grant_4.status == Grant.Status.waiting_list_maybe
648+
assert not LogEntry.objects.filter(
649+
object_id=grant_4.id,
650+
).exists()
633651

634652

635-
def test_reset_pending_status_back_to_status_action(rf):
653+
def test_reset_pending_status_back_to_status_action(rf, admin_user):
636654
grant_1 = GrantFactory(
637655
status=Grant.Status.pending,
638656
pending_status=Grant.Status.confirmed,
@@ -657,6 +675,7 @@ def test_reset_pending_status_back_to_status_action(rf):
657675
)
658676

659677
request = rf.get("/")
678+
request.user = admin_user
660679
reset_pending_status_back_to_status(
661680
None, request, Grant.objects.filter(id__in=[grant_1.id, grant_2.id, grant_3.id])
662681
)
@@ -675,9 +694,26 @@ def test_reset_pending_status_back_to_status_action(rf):
675694
assert grant_3.status == Grant.Status.waiting_list
676695
assert grant_3.pending_status is None
677696

697+
# Verify audit log entries were created correctly
698+
assert LogEntry.objects.filter(
699+
object_id=grant_1.id,
700+
change_message="[Bulk Admin Action] pending_status reset from 'confirmed' to None.",
701+
).exists()
702+
assert LogEntry.objects.filter(
703+
object_id=grant_2.id,
704+
change_message="[Bulk Admin Action] pending_status reset from 'waiting_list' to None.",
705+
).exists()
706+
assert LogEntry.objects.filter(
707+
object_id=grant_3.id,
708+
change_message="[Bulk Admin Action] pending_status reset from 'waiting_list_maybe' to None.",
709+
).exists()
710+
678711
# Left out from the action
679712
assert grant_4.status == Grant.Status.waiting_list_maybe
680713
assert grant_4.pending_status == Grant.Status.confirmed
714+
assert not LogEntry.objects.filter(
715+
object_id=grant_4.id,
716+
).exists()
681717

682718

683719
def test_delete_reimbursement_from_admin_logs_audit_log_entry(rf, admin_user):

0 commit comments

Comments
 (0)