Skip to content

Commit ee5c98f

Browse files
committed
Update grant form texts for gender neutrality and consistency
Revised several Italian grant-related strings in `index.ts` to use more gender neutral language and improve consistency in tone. This helps ensure the form is welcoming and inclusive to all users, and maintains a coherent voice across the application.
1 parent 23ceeb7 commit ee5c98f

16 files changed

+543
-627
lines changed

backend/conferences/migrations/0055_remove_conference_grants_default_accommodation_amount_and_more.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class Migration(migrations.Migration):
77

88
dependencies = [
99
('conferences', '0054_conference_frontend_revalidate_secret_and_more'),
10+
('grants', '0030_remove_grant_accommodation_amount_and_more'),
1011
]
1112

1213
operations = [

backend/grants/admin.py

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
from django.contrib import admin, messages
66
from django.contrib.admin import SimpleListFilter
77
from django.db import transaction
8-
from django.db.models import Exists, IntegerField, OuterRef, Sum, Value
9-
from django.db.models.functions import Coalesce
8+
from django.db.models import Exists, OuterRef
109
from django.db.models.query import QuerySet
1110
from django.urls import reverse
1211
from django.utils import timezone
@@ -172,27 +171,13 @@ class Meta:
172171

173172

174173
def _check_amounts_are_not_empty(grant: Grant, request):
175-
if grant.total_amount is None:
174+
if grant.total_allocated_amount == 0:
176175
messages.error(
177176
request,
178177
f"Grant for {grant.name} is missing 'Total Amount'!",
179178
)
180179
return False
181180

182-
if grant.has_approved_accommodation() and grant.accommodation_amount is None:
183-
messages.error(
184-
request,
185-
f"Grant for {grant.name} is missing 'Accommodation Amount'!",
186-
)
187-
return False
188-
189-
if grant.has_approved_travel() and grant.travel_amount is None:
190-
messages.error(
191-
request,
192-
f"Grant for {grant.name} is missing 'Travel Amount'!",
193-
)
194-
return False
195-
196181
return True
197182

198183

@@ -216,10 +201,10 @@ def send_reply_emails(modeladmin, request, queryset):
216201

217202
for grant in queryset:
218203
if grant.status in (Grant.Status.approved,):
219-
if grant.approved_type is None:
204+
if not grant.reimbursements.exists():
220205
messages.error(
221206
request,
222-
f"Grant for {grant.name} is missing 'Grant Approved Type'!",
207+
f"Grant for {grant.name} is missing reimbursement categories!",
223208
)
224209
return
225210

@@ -663,11 +648,6 @@ def get_queryset(self, request):
663648
requester_id=OuterRef("user_id"),
664649
)
665650
),
666-
total_allocated_amount=Coalesce(
667-
Sum("reimbursements__granted_amount"),
668-
Value(0),
669-
output_field=IntegerField(),
670-
),
671651
)
672652
)
673653

backend/grants/migrations/0030_remove_grant_accommodation_amount_and_more.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def reverse_migration(apps, schema_editor):
132132
class Migration(migrations.Migration):
133133

134134
dependencies = [
135-
('conferences', '0055_remove_conference_grants_default_accommodation_amount_and_more'),
135+
('conferences', '0054_conference_frontend_revalidate_secret_and_more'),
136136
('grants', '0029_alter_grant_pending_status'),
137137
]
138138

backend/grants/summary.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def calculate(self, conference_id):
4444
filtered_grants, statuses
4545
)
4646
gender_stats = self._aggregate_data_by_gender(filtered_grants, statuses)
47-
financial_summary, total_amount = self._aggregate_financial_data_by_status_new(
47+
financial_summary, total_amount = self._aggregate_financial_data_by_status(
4848
filtered_grants, statuses
4949
)
5050
grant_type_summary = self._aggregate_data_by_grant_type(

backend/grants/tasks.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1+
import logging
12
from datetime import timedelta
23
from urllib.parse import urljoin
34

45
from django.conf import settings
56
from django.utils import timezone
6-
from notifications.models import EmailTemplate, EmailTemplateIdentifier
77

8-
from users.models import User
98
from grants.models import Grant
109
from integrations import slack
11-
12-
import logging
13-
10+
from notifications.models import EmailTemplate, EmailTemplateIdentifier
1411
from pycon.celery import app
12+
from users.models import User
1513

1614
logger = logging.getLogger(__name__)
1715

@@ -32,7 +30,7 @@ def send_grant_reply_approved_email(*, grant_id, is_reminder):
3230
variables = {
3331
"reply_url": reply_url,
3432
"start_date": f"{grant.conference.start:%-d %B}",
35-
"end_date": f"{grant.conference.end+timedelta(days=1):%-d %B}",
33+
"end_date": f"{grant.conference.end + timedelta(days=1):%-d %B}",
3634
"deadline_date_time": f"{grant.applicant_reply_deadline:%-d %B %Y %H:%M %Z}",
3735
"deadline_date": f"{grant.applicant_reply_deadline:%-d %B %Y}",
3836
"visa_page_link": urljoin(settings.FRONTEND_URL, "/visa"),
@@ -42,12 +40,19 @@ def send_grant_reply_approved_email(*, grant_id, is_reminder):
4240
}
4341

4442
if grant.has_approved_travel():
45-
if not grant.travel_amount:
43+
from grants.models import GrantReimbursementCategory
44+
45+
travel_reimbursements = grant.reimbursements.filter(
46+
category__category=GrantReimbursementCategory.Category.TRAVEL
47+
)
48+
travel_amount = sum(r.granted_amount for r in travel_reimbursements)
49+
50+
if not travel_amount or travel_amount == 0:
4651
raise ValueError(
4752
"Grant travel amount is set to Zero, can't send the email!"
4853
)
4954

50-
variables["travel_amount"] = f"{grant.travel_amount:.0f}"
55+
variables["travel_amount"] = f"{travel_amount:.0f}"
5156

5257
_new_send_grant_email(
5358
template_identifier=EmailTemplateIdentifier.grant_approved,

backend/grants/tests/factories.py

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
import random
2+
from decimal import Decimal
3+
14
import factory.fuzzy
25
from factory.django import DjangoModelFactory
36

47
from conferences.tests.factories import ConferenceFactory
5-
from grants.models import Grant
6-
from helpers.constants import GENDERS
7-
from users.tests.factories import UserFactory
88
from countries import countries
9-
from participants.tests.factories import ParticipantFactory
9+
from grants.models import Grant, GrantReimbursement, GrantReimbursementCategory
10+
from helpers.constants import GENDERS
1011
from participants.models import Participant
11-
import random
12+
from participants.tests.factories import ParticipantFactory
13+
from users.tests.factories import UserFactory
1214

1315

1416
class GrantFactory(DjangoModelFactory):
@@ -57,3 +59,54 @@ def _create(self, model_class, *args, **kwargs):
5759
ParticipantFactory(user_id=grant.user.id, conference=grant.conference)
5860

5961
return grant
62+
63+
64+
class GrantReimbursementCategoryFactory(DjangoModelFactory):
65+
class Meta:
66+
model = GrantReimbursementCategory
67+
68+
conference = factory.SubFactory(ConferenceFactory)
69+
name = factory.LazyAttribute(
70+
lambda obj: GrantReimbursementCategory.Category(obj.category).label
71+
)
72+
description = factory.Faker("sentence", nb_words=6)
73+
max_amount = factory.fuzzy.FuzzyInteger(0, 1000)
74+
category = factory.fuzzy.FuzzyChoice(
75+
[choice[0] for choice in GrantReimbursementCategory.Category.choices]
76+
)
77+
included_by_default = False
78+
79+
class Params:
80+
ticket = factory.Trait(
81+
category=GrantReimbursementCategory.Category.TICKET,
82+
name="Ticket",
83+
max_amount=Decimal("100"),
84+
included_by_default=True,
85+
)
86+
travel = factory.Trait(
87+
category=GrantReimbursementCategory.Category.TRAVEL,
88+
name="Travel",
89+
max_amount=Decimal("500"),
90+
included_by_default=False,
91+
)
92+
accommodation = factory.Trait(
93+
category=GrantReimbursementCategory.Category.ACCOMMODATION,
94+
name="Accommodation",
95+
max_amount=Decimal("300"),
96+
included_by_default=False,
97+
)
98+
other = factory.Trait(
99+
category=GrantReimbursementCategory.Category.OTHER,
100+
name="Other",
101+
max_amount=Decimal("200"),
102+
included_by_default=False,
103+
)
104+
105+
106+
class GrantReimbursementFactory(DjangoModelFactory):
107+
class Meta:
108+
model = GrantReimbursement
109+
110+
grant = factory.SubFactory(GrantFactory)
111+
category = factory.SubFactory(GrantReimbursementCategoryFactory)
112+
granted_amount = factory.fuzzy.FuzzyInteger(0, 1000)

backend/grants/tests/test_admin.py

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
from datetime import timedelta
2+
from decimal import Decimal
23
from unittest.mock import call
34

4-
from conferences.models.conference_voucher import ConferenceVoucher
5-
from conferences.tests.factories import ConferenceFactory, ConferenceVoucherFactory
6-
from grants.tests.factories import GrantFactory
75
import pytest
86
from django.utils import timezone
97

8+
from conferences.models.conference_voucher import ConferenceVoucher
9+
from conferences.tests.factories import ConferenceFactory, ConferenceVoucherFactory
1010
from grants.admin import (
1111
confirm_pending_status,
1212
create_grant_vouchers,
13+
mark_rejected_and_send_email,
1314
reset_pending_status_back_to_status,
1415
send_reply_emails,
15-
mark_rejected_and_send_email,
1616
)
1717
from grants.models import Grant
18-
18+
from grants.tests.factories import (
19+
GrantFactory,
20+
GrantReimbursementFactory,
21+
)
1922

2023
pytestmark = pytest.mark.django_db
2124

@@ -60,9 +63,11 @@ def test_send_reply_emails_with_grants_from_multiple_conferences_fails(
6063
mock_send_rejected_email.assert_not_called()
6164

6265

63-
def test_send_reply_emails_approved_grant_missing_approved_type(rf, mocker, admin_user):
66+
def test_send_reply_emails_approved_grant_missing_reimbursements(
67+
rf, mocker, admin_user
68+
):
6469
mock_messages = mocker.patch("grants.admin.messages")
65-
grant = GrantFactory(status=Grant.Status.approved, approved_type=None)
70+
grant = GrantFactory(status=Grant.Status.approved)
6671
request = rf.get("/")
6772
request.user = admin_user
6873
mock_send_approved_email = mocker.patch(
@@ -73,20 +78,21 @@ def test_send_reply_emails_approved_grant_missing_approved_type(rf, mocker, admi
7378

7479
mock_messages.error.assert_called_once_with(
7580
request,
76-
f"Grant for {grant.name} is missing 'Grant Approved Type'!",
81+
f"Grant for {grant.name} is missing reimbursement categories!",
7782
)
7883
mock_send_approved_email.assert_not_called()
7984

8085

8186
def test_send_reply_emails_approved_missing_amount(rf, mocker, admin_user):
8287
mock_messages = mocker.patch("grants.admin.messages")
83-
grant = GrantFactory(
84-
status=Grant.Status.approved,
85-
approved_type=Grant.ApprovedType.ticket_accommodation,
86-
total_amount=None,
88+
grant = GrantFactory(status=Grant.Status.approved)
89+
# Create reimbursement with 0 amount so total_allocated_amount is 0
90+
GrantReimbursementFactory(
91+
grant=grant,
92+
category__conference=grant.conference,
93+
category__ticket=True,
94+
granted_amount=Decimal("0"),
8795
)
88-
grant.total_amount = None
89-
grant.save()
9096
request = rf.get("/")
9197
request.user = admin_user
9298
mock_send_approved_email = mocker.patch(
@@ -106,10 +112,19 @@ def test_send_reply_emails_approved_set_deadline_in_fourteen_days(
106112
rf, mocker, admin_user
107113
):
108114
mock_messages = mocker.patch("grants.admin.messages")
109-
grant = GrantFactory(
110-
status=Grant.Status.approved,
111-
approved_type=Grant.ApprovedType.ticket_accommodation,
112-
total_amount=800,
115+
grant = GrantFactory(status=Grant.Status.approved)
116+
GrantReimbursementFactory(
117+
grant=grant,
118+
category__conference=grant.conference,
119+
category__ticket=True,
120+
granted_amount=Decimal("100"),
121+
)
122+
GrantReimbursementFactory(
123+
grant=grant,
124+
category__conference=grant.conference,
125+
category__accommodation=True,
126+
category__max_amount=Decimal("700"),
127+
granted_amount=Decimal("700"),
113128
)
114129
request = rf.get("/")
115130
request.user = admin_user

0 commit comments

Comments
 (0)