From 41415513f04d635750e866d1881ec7a6cfc1f8e3 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Sun, 27 Jul 2025 13:05:01 +0000 Subject: [PATCH 1/7] Fix grant pending status sync issue Change pending_status field logic: - pending_status=None means no pending change (use current status) - pending_status=value means there''s a pending status change This fixes the issue where confirmed grants still showed as pending. Changes: - Modified Grant.pending_status field to allow null values - Removed problematic sync logic in save() method - Updated _calculate_grant_amounts to use effective status - Updated admin queries and actions to work with new logic - Updated tests to reflect new behavior Co-authored-by: Marco Acierno --- backend/custom_admin/admin.py | 2 +- backend/grants/admin.py | 4 ++-- backend/grants/models.py | 9 ++++--- backend/grants/tests/test_models.py | 37 ++++++++++++++++++++++++++--- 4 files changed, 41 insertions(+), 11 deletions(-) diff --git a/backend/custom_admin/admin.py b/backend/custom_admin/admin.py index 7151a2d84e..7dad54229d 100644 --- a/backend/custom_admin/admin.py +++ b/backend/custom_admin/admin.py @@ -67,5 +67,5 @@ def confirm_pending_status(modeladmin, request, queryset): @validate_single_conference_selection def reset_pending_status_back_to_status(modeladmin, request, queryset): queryset.update( - pending_status=F("status"), + pending_status=None, ) diff --git a/backend/grants/admin.py b/backend/grants/admin.py index 6615e8bd06..bca135cd9e 100644 --- a/backend/grants/admin.py +++ b/backend/grants/admin.py @@ -652,8 +652,8 @@ def get_queryset(self, request): return ( super() .get_queryset(request) - .exclude( - pending_status=F("status"), + .filter( + pending_status__isnull=False, ) ) diff --git a/backend/grants/models.py b/backend/grants/models.py index a7bb447af8..5ac3fdce61 100644 --- a/backend/grants/models.py +++ b/backend/grants/models.py @@ -149,7 +149,7 @@ class ApprovedType(models.TextChoices): _("pending status"), choices=Status.choices, max_length=30, - default=Status.pending, + null=True, blank=True, ) approved_type = models.CharField( @@ -229,9 +229,6 @@ def save(self, *args, **kwargs): self._update_country_type() self._calculate_grant_amounts() - if self.pending_status == self._original_status: - self.pending_status = self.status - update_fields = kwargs.get("update_fields", None) if update_fields: update_fields.append("total_amount") @@ -249,7 +246,9 @@ def save(self, *args, **kwargs): self._original_status = self.status def _calculate_grant_amounts(self): - if self.pending_status != Grant.Status.approved: + # Use pending_status if set, otherwise use current status + effective_status = self.pending_status if self.pending_status is not None else self.status + if effective_status != Grant.Status.approved: return if ( diff --git a/backend/grants/tests/test_models.py b/backend/grants/tests/test_models.py index ce68b0479a..5bb922cb4f 100644 --- a/backend/grants/tests/test_models.py +++ b/backend/grants/tests/test_models.py @@ -162,7 +162,7 @@ def test_sets_country_type_does_nothing_if_unset(): assert grant.country_type is None -def test_syncs_pending_status_on_change(): +def test_pending_status_no_longer_syncs_with_status(): grant = GrantFactory( pending_status=Grant.Status.pending, status=Grant.Status.pending, @@ -171,10 +171,10 @@ def test_syncs_pending_status_on_change(): grant.status = Grant.Status.approved grant.save(update_fields=["status"]) - # Pending status should be updated to match the status + # Pending status should remain unchanged when status changes grant.refresh_from_db() - assert grant.pending_status == Grant.Status.approved + assert grant.pending_status == Grant.Status.pending # Should remain unchanged assert grant.status == Grant.Status.approved @@ -192,3 +192,34 @@ def test_doesnt_sync_pending_status_if_different_values(): assert grant.pending_status == Grant.Status.refused assert grant.status == Grant.Status.waiting_for_confirmation + + +def test_pending_status_none_means_no_pending_change(): + grant = GrantFactory( + pending_status=None, + status=Grant.Status.approved, + ) + + # When pending_status is None, the effective status should be the current status + # This affects the _calculate_grant_amounts method + grant.approved_type = Grant.ApprovedType.ticket_only + grant.departure_country = "IT" + grant.save() + + # Since effective status is approved (from status field), amounts should be calculated + assert grant.ticket_amount is not None + + +def test_pending_status_set_overrides_current_status(): + grant = GrantFactory( + pending_status=Grant.Status.approved, + status=Grant.Status.pending, + ) + + # When pending_status is set, it should be used as the effective status + grant.approved_type = Grant.ApprovedType.ticket_only + grant.departure_country = "IT" + grant.save() + + # Since effective status is approved (from pending_status), amounts should be calculated + assert grant.ticket_amount is not None From e437bafab8606049f3aeb4315da2c0ef4e4dd2e4 Mon Sep 17 00:00:00 2001 From: Marco Acierno Date: Sun, 27 Jul 2025 16:17:41 +0200 Subject: [PATCH 2/7] Add migration --- .../0029_alter_grant_pending_status.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 backend/grants/migrations/0029_alter_grant_pending_status.py diff --git a/backend/grants/migrations/0029_alter_grant_pending_status.py b/backend/grants/migrations/0029_alter_grant_pending_status.py new file mode 100644 index 0000000000..d04d003dfb --- /dev/null +++ b/backend/grants/migrations/0029_alter_grant_pending_status.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.4 on 2025-07-27 14:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('grants', '0028_remove_grant_pretix_voucher_id_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='grant', + name='pending_status', + field=models.CharField(blank=True, choices=[('pending', 'Pending'), ('rejected', 'Rejected'), ('approved', 'Approved'), ('waiting_list', 'Waiting List'), ('waiting_list_maybe', 'Waiting List, Maybe'), ('waiting_for_confirmation', 'Waiting for confirmation'), ('refused', 'Refused'), ('confirmed', 'Confirmed'), ('did_not_attend', 'Did Not Attend')], max_length=30, null=True, verbose_name='pending status'), + ), + ] From 49b25b0aa2dee14831fd03188a56e3a087b306e9 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Sun, 27 Jul 2025 14:24:11 +0000 Subject: [PATCH 3/7] Fix test_reset_pending_status_back_to_status_action for new pending_status logic Update test assertions to expect pending_status=None after reset action, which aligns with the new logic where None means no pending change. Co-authored-by: Marco Acierno --- backend/grants/tests/test_admin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/grants/tests/test_admin.py b/backend/grants/tests/test_admin.py index 9cf22c7ce8..5a29c921c2 100644 --- a/backend/grants/tests/test_admin.py +++ b/backend/grants/tests/test_admin.py @@ -524,13 +524,13 @@ def test_reset_pending_status_back_to_status_action(rf): grant_4.refresh_from_db() assert grant_1.status == Grant.Status.pending - assert grant_1.pending_status == Grant.Status.pending + assert grant_1.pending_status is None assert grant_2.status == Grant.Status.rejected - assert grant_2.pending_status == Grant.Status.rejected + assert grant_2.pending_status is None assert grant_3.status == Grant.Status.waiting_list - assert grant_3.pending_status == Grant.Status.waiting_list + assert grant_3.pending_status is None # Left out from the action assert grant_4.status == Grant.Status.waiting_list_maybe From 6a859d7beb4dee8d63167ff0cab5076593cdf77c Mon Sep 17 00:00:00 2001 From: Marco Acierno Date: Sat, 2 Aug 2025 23:55:31 +0200 Subject: [PATCH 4/7] Fixes --- backend/custom_admin/admin.py | 1 + backend/grants/models.py | 9 ++++++--- backend/grants/tests/test_admin.py | 5 +++++ backend/reviews/templates/grants-recap.html | 12 ++++++------ 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/backend/custom_admin/admin.py b/backend/custom_admin/admin.py index 7dad54229d..a1fe84fb7c 100644 --- a/backend/custom_admin/admin.py +++ b/backend/custom_admin/admin.py @@ -60,6 +60,7 @@ def wrapper(modeladmin, request, queryset): def confirm_pending_status(modeladmin, request, queryset): queryset.update( status=F("pending_status"), + pending_status=None, ) diff --git a/backend/grants/models.py b/backend/grants/models.py index 5ac3fdce61..6e98952b68 100644 --- a/backend/grants/models.py +++ b/backend/grants/models.py @@ -246,9 +246,7 @@ def save(self, *args, **kwargs): self._original_status = self.status def _calculate_grant_amounts(self): - # Use pending_status if set, otherwise use current status - effective_status = self.pending_status if self.pending_status is not None else self.status - if effective_status != Grant.Status.approved: + if self.effective_status != Grant.Status.approved: return if ( @@ -331,6 +329,11 @@ def has_approved_accommodation(self): or self.approved_type == Grant.ApprovedType.ticket_travel_accommodation ) + @property + def effective_status(self): + # If the grant is pending, use the pending status + return self.pending_status if self.pending_status is not None else self.status + class GrantConfirmPendingStatusProxy(Grant): class Meta: diff --git a/backend/grants/tests/test_admin.py b/backend/grants/tests/test_admin.py index 5a29c921c2..d394a1eff9 100644 --- a/backend/grants/tests/test_admin.py +++ b/backend/grants/tests/test_admin.py @@ -485,6 +485,11 @@ def test_confirm_pending_status_action(rf): assert grant_1.status == Grant.Status.confirmed assert grant_2.status == Grant.Status.waiting_list assert grant_3.status == Grant.Status.waiting_list_maybe + + assert grant_1.pending_status is None + assert grant_2.pending_status is None + assert grant_3.pending_status is None + # Left out from the action assert grant_4.status == Grant.Status.waiting_list_maybe diff --git a/backend/reviews/templates/grants-recap.html b/backend/reviews/templates/grants-recap.html index 967ca0074d..ed0b289143 100644 --- a/backend/reviews/templates/grants-recap.html +++ b/backend/reviews/templates/grants-recap.html @@ -254,7 +254,7 @@ updateBottomBarUI(); }); - waitinglistInput.addEventListener("change", () => { + waitinglistmaybeInput.addEventListener("change", () => { grantData.status = "waiting_list_maybe"; updateBottomBarUI(); }); @@ -469,13 +469,13 @@

{% for status in all_review_statuses %}