Skip to content

Commit ae6c392

Browse files
committed
refactor(grants): use send_conference_voucher_email; drop grant_voucher_code
- create_and_send_voucher_to_grantee queues send_conference_voucher_email with conference_voucher_id (lazy import avoids circular import with get_name). - Remove send_grant_voucher_email; grantees get the shared voucher_code template like the Voucher admin action. - send_conference_voucher_email always sends and updates voucher_email_sent_at (reminders and admin resends supported). - Remove EmailTemplateIdentifier.grant_voucher_code; migration reassigns any rows to voucher_code then tightens choices.
1 parent d32d672 commit ae6c392

File tree

6 files changed

+134
-166
lines changed

6 files changed

+134
-166
lines changed

backend/conferences/tasks.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
from django.utils import timezone
22
from notifications.models import EmailTemplate, EmailTemplateIdentifier
33
from grants.tasks import get_name
4-
import logging
54
from pycon.celery import app
65

7-
logger = logging.getLogger(__name__)
8-
96

107
@app.task
11-
def send_conference_voucher_email(conference_voucher_id):
8+
def send_conference_voucher_email(conference_voucher_id: int) -> None:
129
from conferences.models import ConferenceVoucher
1310

1411
conference_voucher = ConferenceVoucher.objects.get(id=conference_voucher_id)
@@ -31,4 +28,4 @@ def send_conference_voucher_email(conference_voucher_id):
3128
)
3229

3330
conference_voucher.voucher_email_sent_at = timezone.now()
34-
conference_voucher.save()
31+
conference_voucher.save(update_fields=["voucher_email_sent_at"])

backend/conferences/tests/test_tasks.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,26 @@ def test_send_conference_voucher_email(voucher_type, sent_emails):
6060
assert conference_voucher.voucher_email_sent_at == datetime(
6161
2020, 10, 10, 10, 0, 0, tzinfo=timezone.utc
6262
)
63+
64+
65+
def test_send_conference_voucher_email_sends_again_when_already_sent(sent_emails):
66+
user = UserFactory(full_name="Remind Me")
67+
conference_voucher = ConferenceVoucherFactory(
68+
user=user,
69+
voucher_type=ConferenceVoucher.VoucherType.SPEAKER,
70+
voucher_code="REM123",
71+
voucher_email_sent_at=datetime(2020, 1, 1, tzinfo=timezone.utc),
72+
)
73+
EmailTemplateFactory(
74+
conference=conference_voucher.conference,
75+
identifier=EmailTemplateIdentifier.voucher_code,
76+
)
77+
78+
with time_machine.travel("2020-06-01 12:00:00Z", tick=False):
79+
send_conference_voucher_email(conference_voucher_id=conference_voucher.id)
80+
81+
assert sent_emails().count() == 1
82+
conference_voucher.refresh_from_db()
83+
assert conference_voucher.voucher_email_sent_at == datetime(
84+
2020, 6, 1, 12, 0, 0, tzinfo=timezone.utc
85+
)

backend/grants/tasks.py

Lines changed: 7 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -23,51 +23,10 @@ def get_name(user: User | None, fallback: str = "<no name specified>") -> str:
2323
return user.full_name or user.name or user.username or fallback
2424

2525

26-
@app.task
27-
def send_grant_voucher_email(*, grant_id: int) -> None:
28-
grant = Grant.objects.select_related("conference", "user").get(pk=grant_id)
29-
if not grant.user_id:
30-
return
31-
32-
conference_voucher = (
33-
ConferenceVoucher.objects.for_conference(grant.conference)
34-
.for_user(grant.user)
35-
.first()
36-
)
37-
if not conference_voucher:
38-
return
39-
40-
if conference_voucher.voucher_email_sent_at is not None:
41-
logger.info(
42-
"Grant voucher email already sent for user %s conference %s, skipping",
43-
grant.user_id,
44-
grant.conference_id,
45-
)
46-
return
47-
48-
visa_page_link = urljoin(settings.FRONTEND_URL, "/visa")
49-
conference_name = grant.conference.name.localize("en")
50-
51-
email_template = EmailTemplate.objects.for_conference(
52-
grant.conference
53-
).get_by_identifier(EmailTemplateIdentifier.grant_voucher_code)
54-
email_template.send_email(
55-
recipient=grant.user,
56-
placeholders={
57-
"conference_name": conference_name,
58-
"user_name": get_name(grant.user, "there"),
59-
"voucher_code": conference_voucher.voucher_code,
60-
"has_approved_accommodation": grant.has_approved_accommodation(),
61-
"visa_page_link": visa_page_link,
62-
},
63-
)
64-
65-
conference_voucher.voucher_email_sent_at = timezone.now()
66-
conference_voucher.save(update_fields=["voucher_email_sent_at"])
67-
68-
6926
@app.task
7027
def create_and_send_voucher_to_grantee(*, grant_id: int) -> None:
28+
from conferences.tasks import send_conference_voucher_email
29+
7130
grant = Grant.objects.select_related("user", "conference").get(pk=grant_id)
7231
if grant.status != Grant.Status.confirmed:
7332
return
@@ -97,15 +56,17 @@ def create_and_send_voucher_to_grantee(*, grant_id: int) -> None:
9756
conference_voucher.save(
9857
update_fields=["voucher_type", "voucher_email_sent_at"]
9958
)
100-
send_grant_voucher_email.delay(grant_id=grant.id)
59+
send_conference_voucher_email.delay(
60+
conference_voucher_id=conference_voucher.id
61+
)
10162
return
10263

103-
create_conference_voucher(
64+
new_voucher = create_conference_voucher(
10465
conference=conference,
10566
user=user,
10667
voucher_type=ConferenceVoucher.VoucherType.GRANT,
10768
)
108-
send_grant_voucher_email.delay(grant_id=grant.id)
69+
send_conference_voucher_email.delay(conference_voucher_id=new_voucher.id)
10970

11071

11172
@app.task

backend/grants/tests/test_tasks.py

Lines changed: 33 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
send_grant_reply_rejected_email,
1717
send_grant_reply_waiting_list_email,
1818
send_grant_reply_waiting_list_update_email,
19-
send_grant_voucher_email,
2019
)
2120
from grants.tests.factories import GrantFactory, GrantReimbursementFactory
2221
from users.tests.factories import UserFactory
@@ -474,110 +473,29 @@ def test_send_grant_waiting_list_email_missing_deadline():
474473
send_grant_reply_waiting_list_email(grant_id=grant.id)
475474

476475

477-
def test_send_grant_voucher_email(settings, sent_emails):
478-
from notifications.models import EmailTemplateIdentifier
479-
from notifications.tests.factories import EmailTemplateFactory
480-
481-
settings.FRONTEND_URL = "https://pycon.it"
482-
user = UserFactory(
483-
full_name="Marco Acierno",
484-
email="marco@placeholder.it",
485-
name="Marco",
486-
username="marco",
487-
)
488-
grant = GrantFactory(user=user, status=Grant.Status.confirmed)
489-
ConferenceVoucherFactory(
490-
user=user,
491-
conference=grant.conference,
492-
voucher_type=ConferenceVoucher.VoucherType.GRANT,
493-
voucher_code="GRANT99",
494-
)
495-
496-
EmailTemplateFactory(
497-
conference=grant.conference,
498-
identifier=EmailTemplateIdentifier.grant_voucher_code,
499-
)
500-
501-
with time_machine.travel("2020-10-10 10:00:00Z", tick=False):
502-
send_grant_voucher_email(grant_id=grant.id)
503-
504-
emails_sent = sent_emails()
505-
assert emails_sent.count() == 1
506-
507-
sent_email = emails_sent.first()
508-
assert (
509-
sent_email.email_template.identifier
510-
== EmailTemplateIdentifier.grant_voucher_code
511-
)
512-
assert sent_email.email_template.conference == grant.conference
513-
assert sent_email.recipient == user
514-
assert sent_email.placeholders["user_name"] == "Marco Acierno"
515-
assert sent_email.placeholders["conference_name"] == grant.conference.name.localize(
516-
"en"
517-
)
518-
assert sent_email.placeholders["voucher_code"] == "GRANT99"
519-
assert sent_email.placeholders["has_approved_accommodation"] is False
520-
assert sent_email.placeholders["visa_page_link"] == "https://pycon.it/visa"
521-
522-
voucher = ConferenceVoucher.objects.get(
523-
conference=grant.conference,
524-
user=user,
525-
)
526-
assert voucher.voucher_email_sent_at == datetime(
527-
2020, 10, 10, 10, 0, 0, tzinfo=timezone.utc
528-
)
529-
530-
531-
def test_send_grant_voucher_email_skips_when_already_sent(settings, sent_emails):
532-
from notifications.models import EmailTemplateIdentifier
533-
from notifications.tests.factories import EmailTemplateFactory
534-
535-
settings.FRONTEND_URL = "https://pycon.it"
536-
user = UserFactory()
537-
grant = GrantFactory(user=user, status=Grant.Status.confirmed)
538-
ConferenceVoucherFactory(
539-
user=user,
540-
conference=grant.conference,
541-
voucher_type=ConferenceVoucher.VoucherType.GRANT,
542-
voucher_code="GRANT99",
543-
voucher_email_sent_at=datetime(2020, 1, 1, tzinfo=timezone.utc),
544-
)
545-
EmailTemplateFactory(
546-
conference=grant.conference,
547-
identifier=EmailTemplateIdentifier.grant_voucher_code,
548-
)
549-
550-
send_grant_voucher_email(grant_id=grant.id)
551-
552-
assert sent_emails().count() == 0
553-
554-
555476
def test_create_and_send_voucher_to_grantee(mocker):
556477
mock_create = mocker.patch(
557478
"conferences.vouchers.create_voucher", return_value={"id": 123}
558479
)
559-
mock_send_email = mocker.patch("grants.tasks.send_grant_voucher_email")
480+
mock_send_email = mocker.patch("conferences.tasks.send_conference_voucher_email")
560481

561482
grant = GrantFactory(status=Grant.Status.confirmed)
562483

563484
create_and_send_voucher_to_grantee(grant_id=grant.id)
564485

565-
assert (
566-
ConferenceVoucher.objects.for_conference(grant.conference)
567-
.filter(
568-
user=grant.user,
569-
voucher_type=ConferenceVoucher.VoucherType.GRANT,
570-
)
571-
.exists()
486+
voucher = ConferenceVoucher.objects.get(
487+
conference=grant.conference,
488+
user=grant.user,
489+
voucher_type=ConferenceVoucher.VoucherType.GRANT,
572490
)
573491

574492
mock_create.assert_called_once()
575-
mock_send_email.delay.assert_called_once_with(grant_id=grant.id)
493+
mock_send_email.delay.assert_called_once_with(conference_voucher_id=voucher.id)
576494

577495

578496
def test_create_and_send_voucher_to_grantee_does_nothing_if_not_confirmed(mocker):
579497
mock_create = mocker.patch("conferences.vouchers.create_voucher")
580-
mock_send_email = mocker.patch("grants.tasks.send_grant_voucher_email")
498+
mock_send_email = mocker.patch("conferences.tasks.send_conference_voucher_email")
581499

582500
grant = GrantFactory(status=Grant.Status.waiting_for_confirmation)
583501

@@ -589,7 +507,7 @@ def test_create_and_send_voucher_to_grantee_does_nothing_if_not_confirmed(mocker
589507

590508
def test_create_and_send_voucher_to_grantee_does_nothing_if_no_user(mocker):
591509
mock_create = mocker.patch("conferences.vouchers.create_voucher")
592-
mock_send_email = mocker.patch("grants.tasks.send_grant_voucher_email")
510+
mock_send_email = mocker.patch("conferences.tasks.send_conference_voucher_email")
593511

594512
grant = GrantFactory(status=Grant.Status.confirmed)
595513
Grant.objects.filter(pk=grant.pk).update(user_id=None)
@@ -612,7 +530,7 @@ def test_create_and_send_voucher_to_grantee_does_nothing_if_voucher_exists(
612530
mocker, voucher_type
613531
):
614532
mock_create = mocker.patch("conferences.vouchers.create_voucher")
615-
mock_send_email = mocker.patch("grants.tasks.send_grant_voucher_email")
533+
mock_send_email = mocker.patch("conferences.tasks.send_conference_voucher_email")
616534

617535
grant = GrantFactory(status=Grant.Status.confirmed)
618536
ConferenceVoucherFactory(
@@ -629,34 +547,30 @@ def test_create_and_send_voucher_to_grantee_does_nothing_if_voucher_exists(
629547

630548
def test_create_and_send_voucher_to_grantee_upgrades_co_speaker(mocker):
631549
mock_create = mocker.patch("conferences.vouchers.create_voucher")
632-
mock_send_email = mocker.patch("grants.tasks.send_grant_voucher_email")
550+
mock_send_email = mocker.patch("conferences.tasks.send_conference_voucher_email")
633551

634552
grant = GrantFactory(status=Grant.Status.confirmed)
635-
ConferenceVoucherFactory(
553+
voucher = ConferenceVoucherFactory(
636554
conference=grant.conference,
637555
user=grant.user,
638556
voucher_type=ConferenceVoucher.VoucherType.CO_SPEAKER,
639557
)
640558

641559
create_and_send_voucher_to_grantee(grant_id=grant.id)
642560

643-
voucher = ConferenceVoucher.objects.get(
644-
conference=grant.conference,
645-
user=grant.user,
646-
)
561+
voucher.refresh_from_db()
647562
assert voucher.voucher_type == ConferenceVoucher.VoucherType.GRANT
648563

649564
mock_create.assert_not_called()
650-
mock_send_email.delay.assert_called_once_with(grant_id=grant.id)
565+
mock_send_email.delay.assert_called_once_with(conference_voucher_id=voucher.id)
651566

652567

653568
def test_create_and_send_voucher_to_grantee_upgrades_co_speaker_clears_email_sent_at(
654-
mocker, settings, sent_emails
569+
mocker, sent_emails
655570
):
656571
from notifications.models import EmailTemplateIdentifier
657572
from notifications.tests.factories import EmailTemplateFactory
658573

659-
settings.FRONTEND_URL = "https://pycon.it"
660574
mock_create = mocker.patch("conferences.vouchers.create_voucher")
661575
grant = GrantFactory(status=Grant.Status.confirmed)
662576
prior_sent = datetime(2020, 5, 5, 12, 0, 0, tzinfo=timezone.utc)
@@ -668,20 +582,27 @@ def test_create_and_send_voucher_to_grantee_upgrades_co_speaker_clears_email_sen
668582
)
669583
EmailTemplateFactory(
670584
conference=grant.conference,
671-
identifier=EmailTemplateIdentifier.grant_voucher_code,
585+
identifier=EmailTemplateIdentifier.voucher_code,
672586
)
673587

674-
create_and_send_voucher_to_grantee(grant_id=grant.id)
588+
with time_machine.travel("2020-10-10 10:00:00Z", tick=False):
589+
create_and_send_voucher_to_grantee(grant_id=grant.id)
675590

676591
mock_create.assert_not_called()
677592
voucher = ConferenceVoucher.objects.get(
678593
conference=grant.conference,
679594
user=grant.user,
680595
)
681596
assert voucher.voucher_type == ConferenceVoucher.VoucherType.GRANT
682-
assert voucher.voucher_email_sent_at is not None
683-
assert voucher.voucher_email_sent_at != prior_sent
597+
assert voucher.voucher_email_sent_at == datetime(
598+
2020, 10, 10, 10, 0, 0, tzinfo=timezone.utc
599+
)
684600
assert sent_emails().count() == 1
601+
sent_email = sent_emails().first()
602+
assert sent_email.email_template.identifier == EmailTemplateIdentifier.voucher_code
603+
assert (
604+
sent_email.placeholders["voucher_type"] == ConferenceVoucher.VoucherType.GRANT
605+
)
685606

686607

687608
def test_create_and_send_voucher_to_grantee_creates_when_voucher_on_other_conference(
@@ -690,7 +611,7 @@ def test_create_and_send_voucher_to_grantee_creates_when_voucher_on_other_confer
690611
mock_create = mocker.patch(
691612
"conferences.vouchers.create_voucher", return_value={"id": 123}
692613
)
693-
mock_send_email = mocker.patch("grants.tasks.send_grant_voucher_email")
614+
mock_send_email = mocker.patch("conferences.tasks.send_conference_voucher_email")
694615

695616
other_conference = ConferenceFactory()
696617
grant = GrantFactory(status=Grant.Status.confirmed)
@@ -711,5 +632,11 @@ def test_create_and_send_voucher_to_grantee_creates_when_voucher_on_other_confer
711632
.exists()
712633
)
713634

635+
voucher = ConferenceVoucher.objects.get(
636+
conference=grant.conference,
637+
user=grant.user,
638+
voucher_type=ConferenceVoucher.VoucherType.GRANT,
639+
)
640+
714641
mock_create.assert_called_once()
715-
mock_send_email.delay.assert_called_once_with(grant_id=grant.id)
642+
mock_send_email.delay.assert_called_once_with(conference_voucher_id=voucher.id)

0 commit comments

Comments
 (0)