Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion backend/custom_admin/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,13 @@ def wrapper(modeladmin, request, queryset):
def confirm_pending_status(modeladmin, request, queryset):
queryset.update(
status=F("pending_status"),
pending_status=None,
)


@admin.action(description="Reset pending status to status")
@validate_single_conference_selection
def reset_pending_status_back_to_status(modeladmin, request, queryset):
queryset.update(
pending_status=F("status"),
pending_status=None,
)
4 changes: 2 additions & 2 deletions backend/grants/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,8 +652,8 @@ def get_queryset(self, request):
return (
super()
.get_queryset(request)
.exclude(
pending_status=F("status"),
.filter(
pending_status__isnull=False,
)
)

Expand Down
18 changes: 18 additions & 0 deletions backend/grants/migrations/0029_alter_grant_pending_status.py
Original file line number Diff line number Diff line change
@@ -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'),
),
]
11 changes: 6 additions & 5 deletions backend/grants/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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")
Expand All @@ -249,7 +246,7 @@ def save(self, *args, **kwargs):
self._original_status = self.status

def _calculate_grant_amounts(self):
if self.pending_status != Grant.Status.approved:
if self.current_or_pending_status != Grant.Status.approved:
return

if (
Expand Down Expand Up @@ -332,6 +329,10 @@ def has_approved_accommodation(self):
or self.approved_type == Grant.ApprovedType.ticket_travel_accommodation
)

@property
def current_or_pending_status(self):
return self.pending_status or self.status


class GrantConfirmPendingStatusProxy(Grant):
class Meta:
Expand Down
11 changes: 8 additions & 3 deletions backend/grants/tests/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -524,13 +529,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
Expand Down
37 changes: 34 additions & 3 deletions backend/grants/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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


Expand All @@ -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
12 changes: 8 additions & 4 deletions backend/reviews/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ def go_to_review_screen(self, obj):

return mark_safe(
f"""
<a href="{reverse('admin:reviews-start', kwargs={'review_session_id': obj.id})}">
<a href="{reverse("admin:reviews-start", kwargs={"review_session_id": obj.id})}">
Go to review screen
</a>
"""
Expand All @@ -176,7 +176,7 @@ def go_to_recap_screen(self, obj):

return mark_safe(
f"""
<a href="{reverse('admin:reviews-recap', kwargs={'review_session_id': obj.id})}">
<a href="{reverse("admin:reviews-recap", kwargs={"review_session_id": obj.id})}">
Go to recap screen
</a>
"""
Expand Down Expand Up @@ -282,7 +282,11 @@ def _review_grants_recap_view(self, request, review_session):

approved_type = approved_type_decisions.get(grant.id, "")

grant.pending_status = decision
if decision != grant.status:
grant.pending_status = decision
elif decision == grant.status:
grant.pending_status = None

grant.approved_type = (
approved_type if decision == Grant.Status.approved else None
)
Expand Down Expand Up @@ -736,7 +740,7 @@ def _render_proposal_review(
seen=request.GET.get("seen", "").split(","),
existing_comment=existing_comment,
review_session_repr=str(review_session),
title=f'Proposal Review: {proposal.title.localize("en")}',
title=f"Proposal Review: {proposal.title.localize('en')}",
)
return TemplateResponse(request, "proposal-review.html", context)

Expand Down
12 changes: 6 additions & 6 deletions backend/reviews/templates/grants-recap.html
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@
updateBottomBarUI();
});

waitinglistInput.addEventListener("change", () => {
waitinglistmaybeInput.addEventListener("change", () => {
grantData.status = "waiting_list_maybe";
updateBottomBarUI();
});
Expand Down Expand Up @@ -469,13 +469,13 @@ <h3>
<script>
grantsById[{{item.id}}] = {
id: {{item.id}},
status: "{{ item.pending_status }}",
originalStatus: "{{ item.pending_status }}",
status: "{{ item.current_or_pending_status }}",
originalStatus: "{{ item.current_or_pending_status }}",
numOfVotes: {{item.userreview_set.count}},
};
</script>
<tr
data-original-status="{{ item.pending_status }}"
data-original-status="{{ item.current_or_pending_status }}"
data-original-approved-type="{{ item.approved_type }}"
class="grant-item"
id="grant-{{ item.id }}"
Expand Down Expand Up @@ -612,7 +612,7 @@ <h3>
{% for status in all_review_statuses %}
<li>
<label>
<input {% if item.pending_status == status.0 %}checked{% endif %}
<input {% if item.current_or_pending_status == status.0 %}checked{% endif %}
type="radio"
class="status-decision-radio"
name="decision-{{item.id}}"
Expand Down Expand Up @@ -641,7 +641,7 @@ <h3>
{% if perms.reviews.decision_reviewsession %}
<ul
data-item-id="{{ item.id }}"
class="approved-type-choices {% if item.pending_status != 'approved' %}hidden{% endif %}"
class="approved-type-choices {% if item.current_or_pending_status != 'approved' %}hidden{% endif %}"
>
{% for approved_type in all_approved_types %}
<li>
Expand Down
Loading