-
-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2729 from carpentries/feature/2728-complex-strate…
…gy-for-instructor-declined-for-workshop [Emails] Complex strategy for instructor declined for workshop
- Loading branch information
Showing
10 changed files
with
1,310 additions
and
81 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
288 changes: 251 additions & 37 deletions
288
amy/emails/actions/instructor_declined_from_workshop.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,79 +1,293 @@ | ||
from datetime import datetime | ||
import logging | ||
|
||
from django.contrib.contenttypes.models import ContentType | ||
from django.http import HttpRequest | ||
from django.utils import timezone | ||
from typing_extensions import Unpack | ||
|
||
from emails.actions.base_action import BaseAction | ||
from emails.actions.base_action import BaseAction, BaseActionCancel, BaseActionUpdate | ||
from emails.actions.base_strategy import run_strategy | ||
from emails.models import ScheduledEmail, ScheduledEmailStatus | ||
from emails.schemas import ContextModel, ToHeaderModel | ||
from emails.signals import instructor_declined_from_workshop_signal | ||
from emails.types import InstructorDeclinedContext, InstructorDeclinedKwargs | ||
from emails.utils import api_model_url, immediate_action | ||
from emails.signals import ( | ||
INSTRUCTOR_DECLINED_FROM_WORKSHOP_SIGNAL_NAME, | ||
Signal, | ||
instructor_declined_from_workshop_cancel_signal, | ||
instructor_declined_from_workshop_signal, | ||
instructor_declined_from_workshop_update_signal, | ||
) | ||
from emails.types import ( | ||
InstructorDeclinedContext, | ||
InstructorDeclinedKwargs, | ||
StrategyEnum, | ||
) | ||
from emails.utils import api_model_url, immediate_action, log_condition_elements | ||
from recruitment.models import InstructorRecruitmentSignup | ||
from workshops.models import Event, Person | ||
from workshops.models import Event, Person, TagQuerySet | ||
|
||
logger = logging.getLogger("amy") | ||
|
||
|
||
def instructor_declined_from_workshop_strategy( | ||
signup: InstructorRecruitmentSignup, | ||
) -> StrategyEnum: | ||
logger.info(f"Running InstructorDeclinedFromWorkshop strategy for {signup=}") | ||
|
||
signup_is_declined = signup.state == "d" | ||
person_email_exists = bool(signup.person.email) | ||
event = signup.recruitment.event | ||
carpentries_tags = event.tags.filter( | ||
name__in=TagQuerySet.CARPENTRIES_TAG_NAMES | ||
).exclude(name__in=TagQuerySet.NON_CARPENTRIES_TAG_NAMES) | ||
centrally_organised = ( | ||
event.administrator and event.administrator.domain != "self-organized" | ||
) | ||
start_date_in_future = event.start and event.start >= timezone.now().date() | ||
|
||
log_condition_elements( | ||
signup=signup, | ||
signup_pk=signup.pk, | ||
signup_is_declined=signup_is_declined, | ||
event=event, | ||
person_email_exists=person_email_exists, | ||
carpentries_tags=carpentries_tags, | ||
centrally_organised=centrally_organised, | ||
start_date_in_future=start_date_in_future, | ||
) | ||
|
||
email_should_exist = ( | ||
signup_is_declined | ||
and person_email_exists | ||
and carpentries_tags | ||
and centrally_organised | ||
and start_date_in_future | ||
) | ||
logger.debug(f"{email_should_exist=}") | ||
|
||
ct = ContentType.objects.get_for_model(InstructorRecruitmentSignup) | ||
email_exists = ScheduledEmail.objects.filter( | ||
generic_relation_content_type=ct, | ||
generic_relation_pk=signup.pk, | ||
template__signal=INSTRUCTOR_DECLINED_FROM_WORKSHOP_SIGNAL_NAME, | ||
state=ScheduledEmailStatus.SCHEDULED, | ||
).exists() | ||
logger.debug(f"{email_exists=}") | ||
|
||
if not email_exists and email_should_exist: | ||
result = StrategyEnum.CREATE | ||
elif email_exists and not email_should_exist: | ||
result = StrategyEnum.CANCEL | ||
elif email_exists and email_should_exist: | ||
result = StrategyEnum.UPDATE | ||
else: | ||
result = StrategyEnum.NOOP | ||
|
||
logger.debug(f"InstructorDeclinedFromWorkshop strategy {result = }") | ||
return result | ||
|
||
|
||
def run_instructor_declined_from_workshop_strategy( | ||
strategy: StrategyEnum, | ||
request: HttpRequest, | ||
signup: InstructorRecruitmentSignup, | ||
**kwargs, | ||
) -> None: | ||
signal_mapping: dict[StrategyEnum, Signal | None] = { | ||
StrategyEnum.CREATE: instructor_declined_from_workshop_signal, | ||
StrategyEnum.UPDATE: instructor_declined_from_workshop_update_signal, | ||
StrategyEnum.CANCEL: instructor_declined_from_workshop_cancel_signal, | ||
StrategyEnum.NOOP: None, | ||
} | ||
return run_strategy( | ||
strategy, | ||
signal_mapping, | ||
request, | ||
sender=signup, | ||
signup=signup, | ||
**kwargs, | ||
) | ||
|
||
|
||
def get_scheduled_at(**kwargs: Unpack[InstructorDeclinedKwargs]) -> datetime: | ||
return immediate_action() | ||
|
||
|
||
def get_context( | ||
**kwargs: Unpack[InstructorDeclinedKwargs], | ||
) -> InstructorDeclinedContext: | ||
person = Person.objects.get(pk=kwargs["person_id"]) | ||
event = Event.objects.get(pk=kwargs["event_id"]) | ||
instructor_recruitment_signup = InstructorRecruitmentSignup.objects.get( | ||
pk=kwargs["instructor_recruitment_signup_id"] | ||
) | ||
return { | ||
"person": person, | ||
"event": event, | ||
"instructor_recruitment_signup": instructor_recruitment_signup, | ||
} | ||
|
||
|
||
def get_context_json(context: InstructorDeclinedContext) -> ContextModel: | ||
return ContextModel( | ||
{ | ||
"person": api_model_url("person", context["person"].pk), | ||
"event": api_model_url("event", context["event"].pk), | ||
"instructor_recruitment_signup": api_model_url( | ||
"instructorrecruitmentsignup", | ||
context["instructor_recruitment_signup"].pk, | ||
), | ||
}, | ||
) | ||
|
||
|
||
def get_generic_relation_object( | ||
context: InstructorDeclinedContext, | ||
**kwargs: Unpack[InstructorDeclinedKwargs], | ||
) -> InstructorRecruitmentSignup: | ||
return context["instructor_recruitment_signup"] | ||
|
||
|
||
def get_recipients( | ||
context: InstructorDeclinedContext, | ||
**kwargs: Unpack[InstructorDeclinedKwargs], | ||
) -> list[str]: | ||
person = context["person"] | ||
return [person.email] if person.email else [] | ||
|
||
|
||
def get_recipients_context_json( | ||
context: InstructorDeclinedContext, | ||
**kwargs: Unpack[InstructorDeclinedKwargs], | ||
) -> ToHeaderModel: | ||
return ToHeaderModel( | ||
[ | ||
{ | ||
"api_uri": api_model_url("person", context["person"].pk), | ||
"property": "email", | ||
}, # type: ignore | ||
], | ||
) | ||
|
||
|
||
class InstructorDeclinedFromWorkshopReceiver(BaseAction): | ||
signal = instructor_declined_from_workshop_signal.signal_name | ||
|
||
def get_scheduled_at(self, **kwargs: Unpack[InstructorDeclinedKwargs]) -> datetime: | ||
return immediate_action() | ||
return get_scheduled_at(**kwargs) | ||
|
||
def get_context( | ||
self, **kwargs: Unpack[InstructorDeclinedKwargs] | ||
) -> InstructorDeclinedContext: | ||
person = Person.objects.get(pk=kwargs["person_id"]) | ||
event = Event.objects.get(pk=kwargs["event_id"]) | ||
instructor_recruitment_signup = InstructorRecruitmentSignup.objects.get( | ||
pk=kwargs["instructor_recruitment_signup_id"] | ||
) | ||
return { | ||
"person": person, | ||
"event": event, | ||
"instructor_recruitment_signup": instructor_recruitment_signup, | ||
} | ||
return get_context(**kwargs) | ||
|
||
def get_context_json(self, context: InstructorDeclinedContext) -> ContextModel: | ||
return ContextModel( | ||
{ | ||
"person": api_model_url("person", context["person"].pk), | ||
"event": api_model_url("event", context["event"].pk), | ||
"instructor_recruitment_signup": api_model_url( | ||
"instructorrecruitmentsignup", | ||
context["instructor_recruitment_signup"].pk, | ||
), | ||
}, | ||
) | ||
return get_context_json(context) | ||
|
||
def get_generic_relation_object( | ||
self, | ||
context: InstructorDeclinedContext, | ||
**kwargs: Unpack[InstructorDeclinedKwargs], | ||
) -> InstructorRecruitmentSignup: | ||
return context["instructor_recruitment_signup"] | ||
return get_generic_relation_object(context, **kwargs) | ||
|
||
def get_recipients( | ||
self, | ||
context: InstructorDeclinedContext, | ||
**kwargs: Unpack[InstructorDeclinedKwargs], | ||
) -> list[str]: | ||
person = context["person"] | ||
return [person.email] if person.email else [] | ||
return get_recipients(context, **kwargs) | ||
|
||
def get_recipients_context_json( | ||
self, | ||
context: InstructorDeclinedContext, | ||
**kwargs: Unpack[InstructorDeclinedKwargs], | ||
) -> ToHeaderModel: | ||
return ToHeaderModel( | ||
[ | ||
{ | ||
"api_uri": api_model_url("person", context["person"].pk), | ||
"property": "email", | ||
}, # type: ignore | ||
], | ||
) | ||
return get_recipients_context_json(context, **kwargs) | ||
|
||
|
||
class InstructorDeclinedFromWorkshopUpdateReceiver(BaseActionUpdate): | ||
signal = instructor_declined_from_workshop_signal.signal_name | ||
|
||
def get_scheduled_at(self, **kwargs: Unpack[InstructorDeclinedKwargs]) -> datetime: | ||
return get_scheduled_at(**kwargs) | ||
|
||
def get_context( | ||
self, **kwargs: Unpack[InstructorDeclinedKwargs] | ||
) -> InstructorDeclinedContext: | ||
return get_context(**kwargs) | ||
|
||
def get_context_json(self, context: InstructorDeclinedContext) -> ContextModel: | ||
return get_context_json(context) | ||
|
||
def get_generic_relation_object( | ||
self, | ||
context: InstructorDeclinedContext, | ||
**kwargs: Unpack[InstructorDeclinedKwargs], | ||
) -> InstructorRecruitmentSignup: | ||
return get_generic_relation_object(context, **kwargs) | ||
|
||
def get_recipients( | ||
self, | ||
context: InstructorDeclinedContext, | ||
**kwargs: Unpack[InstructorDeclinedKwargs], | ||
) -> list[str]: | ||
return get_recipients(context, **kwargs) | ||
|
||
def get_recipients_context_json( | ||
self, | ||
context: InstructorDeclinedContext, | ||
**kwargs: Unpack[InstructorDeclinedKwargs], | ||
) -> ToHeaderModel: | ||
return get_recipients_context_json(context, **kwargs) | ||
|
||
|
||
class InstructorDeclinedFromWorkshopCancelReceiver(BaseActionCancel): | ||
signal = instructor_declined_from_workshop_signal.signal_name | ||
|
||
def get_context( | ||
self, **kwargs: Unpack[InstructorDeclinedKwargs] | ||
) -> InstructorDeclinedContext: | ||
return get_context(**kwargs) | ||
|
||
def get_context_json(self, context: InstructorDeclinedContext) -> ContextModel: | ||
return get_context_json(context) | ||
|
||
def get_generic_relation_object( | ||
self, | ||
context: InstructorDeclinedContext, | ||
**kwargs: Unpack[InstructorDeclinedKwargs], | ||
) -> InstructorRecruitmentSignup: | ||
return get_generic_relation_object(context, **kwargs) | ||
|
||
def get_recipients( | ||
self, | ||
context: InstructorDeclinedContext, | ||
**kwargs: Unpack[InstructorDeclinedKwargs], | ||
) -> list[str]: | ||
return get_recipients(context, **kwargs) | ||
|
||
def get_recipients_context_json( | ||
self, | ||
context: InstructorDeclinedContext, | ||
**kwargs: Unpack[InstructorDeclinedKwargs], | ||
) -> ToHeaderModel: | ||
return get_recipients_context_json(context, **kwargs) | ||
|
||
|
||
instructor_declined_from_workshop_receiver = InstructorDeclinedFromWorkshopReceiver() | ||
instructor_declined_from_workshop_signal.connect( | ||
instructor_declined_from_workshop_receiver | ||
) | ||
instructor_declined_from_workshop_update_receiver = ( | ||
InstructorDeclinedFromWorkshopUpdateReceiver() | ||
) | ||
instructor_declined_from_workshop_update_signal.connect( | ||
instructor_declined_from_workshop_update_receiver | ||
) | ||
instructor_declined_from_workshop_cancel_receiver = ( | ||
InstructorDeclinedFromWorkshopCancelReceiver() | ||
) | ||
instructor_declined_from_workshop_cancel_signal.connect( | ||
instructor_declined_from_workshop_cancel_receiver | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.