Skip to content

Commit

Permalink
Merge pull request #2720 from carpentries/bugfix/2719-change-related-…
Browse files Browse the repository at this point in the history
…object-to-task-in-some-email-actions

[Emails] Change related object to Task in Instructor Confirmed for Workshop and to Award in Instructor Badge Awarded
  • Loading branch information
pbanaszkiewicz authored Nov 9, 2024
2 parents 73d48e0 + d2c771f commit 72a2929
Show file tree
Hide file tree
Showing 20 changed files with 243 additions and 74 deletions.
21 changes: 21 additions & 0 deletions amy/api/v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
Organization,
Person,
TagQuerySet,
Task,
TrainingProgress,
TrainingRequirement,
)
Expand Down Expand Up @@ -291,6 +292,26 @@ class ScheduledEmailLogDetailsSerializer(serializers.Serializer):
details = serializers.CharField(max_length=MAX_LENGTH)


class TaskSerializer(serializers.ModelSerializer):
event = serializers.SlugRelatedField(read_only=True, slug_field="slug")
person = serializers.SlugRelatedField(read_only=True, slug_field="username")
role = serializers.SlugRelatedField(read_only=True, slug_field="name")
seat_membership = serializers.SlugRelatedField(read_only=True, slug_field="name")

class Meta:
model = Task
fields = (
"event",
"person",
"role",
"title",
"url",
"seat_membership",
"seat_public",
"seat_open_training",
)


class TrainingProgressSerializer(serializers.ModelSerializer):
trainee = serializers.SlugRelatedField(read_only=True, slug_field="username")
requirement = serializers.SlugRelatedField(read_only=True, slug_field="name")
Expand Down
1 change: 1 addition & 0 deletions amy/api/v2/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
router.register("membership", views.MembershipViewSet)
router.register("person", views.PersonViewSet)
router.register("scheduledemail", views.ScheduledEmailViewSet)
router.register("task", views.TaskViewSet)
router.register("trainingprogress", views.TrainingProgressViewSet)
router.register("trainingrequirement", views.TrainingRequirementViewSet)
router.register("selforganisedsubmission", views.SelfOrganisedSubmissionViewSet)
Expand Down
20 changes: 20 additions & 0 deletions amy/api/v2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
ScheduledEmailLogDetailsSerializer,
ScheduledEmailSerializer,
SelfOrganisedSubmissionSerializer,
TaskSerializer,
TrainingProgressSerializer,
TrainingRequirementSerializer,
)
Expand All @@ -31,6 +32,7 @@
Membership,
Organization,
Person,
Task,
TrainingProgress,
TrainingRequirement,
)
Expand Down Expand Up @@ -234,6 +236,24 @@ def cancel(self, request, pk=None):
return Response(self.get_serializer(locked_email).data)


class TaskViewSet(viewsets.ReadOnlyModelViewSet):
authentication_classes = (
TokenAuthentication,
SessionAuthentication,
)
permission_classes = (
IsAuthenticated,
ApiAccessPermission,
)
queryset = (
Task.objects.select_related("person", "event", "role", "seat_membership")
.order_by("pk")
.all()
)
serializer_class = TaskSerializer
pagination_class = StandardResultsSetPagination


class TrainingProgressViewSet(viewsets.ReadOnlyModelViewSet):
authentication_classes = (
TokenAuthentication,
Expand Down
21 changes: 18 additions & 3 deletions amy/emails/actions/base_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,16 @@ def get_recipients(self, context: dict[str, Any], **kwargs) -> list[str]:
def get_scheduled_at(self, **kwargs) -> datetime:
raise NotImplementedError()

def get_generic_relation_content_type(
self, context: dict[str, Any], generic_relation_obj: Any
) -> ContentType:
return ContentType.objects.get_for_model(generic_relation_obj)

def get_generic_relation_pk(
self, context: dict[str, Any], generic_relation_obj: Any
) -> int | Any:
return generic_relation_obj.pk

def __call__(self, sender: Any, **kwargs) -> None:
if not feature_flag_enabled("EMAIL_MODULE", f"{self.signal}_cancel", **kwargs):
return
Expand All @@ -220,10 +230,15 @@ def __call__(self, sender: Any, **kwargs) -> None:
generic_relation_obj = self.get_generic_relation_object(context, **kwargs)
signal_name = self.signal

ct = ContentType.objects.get_for_model(generic_relation_obj)
generic_relation_ct = self.get_generic_relation_content_type(
context, generic_relation_obj
)
generic_relation_pk = self.get_generic_relation_pk(
context, generic_relation_obj
)
scheduled_emails = ScheduledEmail.objects.filter(
generic_relation_content_type=ct,
generic_relation_pk=generic_relation_obj.pk,
generic_relation_content_type=generic_relation_ct,
generic_relation_pk=generic_relation_pk,
template__signal=signal_name,
state=ScheduledEmailStatus.SCHEDULED,
).select_for_update()
Expand Down
39 changes: 30 additions & 9 deletions amy/emails/actions/instructor_badge_awarded.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from datetime import datetime
import logging
from typing import Any

from django.contrib.contenttypes.models import ContentType
from django.http import HttpRequest
Expand All @@ -26,30 +27,37 @@
immediate_action,
log_condition_elements,
scalar_value_none,
scalar_value_url,
)
from workshops.models import Award, Person

logger = logging.getLogger("amy")


def instructor_badge_awarded_strategy(
award: Award | None, person: Person
award: Award | None, person: Person, optional_award_pk: int | None = None
) -> StrategyEnum:
logger.info(f"Running InstructorBadgeAwarded strategy for {award=}")

award_pk = getattr(award, "pk", None)
award_exists = award is not None and award_pk is not None
instructor_award = award is not None and award.badge.name == "instructor"

log_condition_elements(award_exists=award_exists, instructor_award=instructor_award)
log_condition_elements(
award=award,
award_pk=award_pk,
optional_award_pk=optional_award_pk,
award_exists=award_exists,
instructor_award=instructor_award,
)

email_should_exist = award_exists and instructor_award
logger.debug(f"{email_should_exist=}")

ct = ContentType.objects.get_for_model(person)
ct = ContentType.objects.get_for_model(Award)
has_email_scheduled = ScheduledEmail.objects.filter(
generic_relation_content_type=ct,
generic_relation_pk=person.pk,
generic_relation_pk=optional_award_pk or award_pk,
template__signal=INSTRUCTOR_BADGE_AWARDED_SIGNAL_NAME,
state=ScheduledEmailStatus.SCHEDULED,
).exists()
Expand Down Expand Up @@ -98,6 +106,7 @@ def get_context(
return {
"person": person,
"award": award,
"award_id": kwargs["award_id"],
}


Expand All @@ -107,15 +116,17 @@ def get_context_json(context: InstructorBadgeAwardedContext) -> ContextModel:
{
"person": api_model_url("person", context["person"].pk),
"award": api_model_url("award", award.pk) if award else scalar_value_none(),
"award_id": scalar_value_url("int", f"{context['award_id']}"),
},
)


def get_generic_relation_object(
context: InstructorBadgeAwardedContext,
**kwargs: Unpack[InstructorBadgeAwardedKwargs],
) -> Person:
return context["person"]
) -> Award:
# When removing award, this will be None.
return context["award"] # type: ignore


def get_recipients(
Expand Down Expand Up @@ -160,7 +171,7 @@ def get_generic_relation_object(
self,
context: InstructorBadgeAwardedContext,
**kwargs: Unpack[InstructorBadgeAwardedKwargs],
) -> Person:
) -> Award:
return get_generic_relation_object(context, **kwargs)

def get_recipients(
Expand Down Expand Up @@ -198,7 +209,7 @@ def get_generic_relation_object(
self,
context: InstructorBadgeAwardedContext,
**kwargs: Unpack[InstructorBadgeAwardedKwargs],
) -> Person:
) -> Award:
return get_generic_relation_object(context, **kwargs)

def get_recipients(
Expand Down Expand Up @@ -227,11 +238,21 @@ def get_context(
def get_context_json(self, context: InstructorBadgeAwardedContext) -> ContextModel:
return get_context_json(context)

def get_generic_relation_content_type(
self, context: InstructorBadgeAwardedContext, generic_relation_obj: Any
) -> ContentType:
return ContentType.objects.get_for_model(Award)

def get_generic_relation_pk(
self, context: InstructorBadgeAwardedContext, generic_relation_obj: Any
) -> int | Any:
return context["award_id"]

def get_generic_relation_object(
self,
context: InstructorBadgeAwardedContext,
**kwargs: Unpack[InstructorBadgeAwardedKwargs],
) -> Person:
) -> Award:
return get_generic_relation_object(context, **kwargs)

def get_recipients_context_json(
Expand Down
40 changes: 32 additions & 8 deletions amy/emails/actions/instructor_confirmed_for_workshop.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from datetime import datetime
import logging
from typing import Any

from django.contrib.contenttypes.models import ContentType
from django.http import HttpRequest
Expand All @@ -26,14 +27,17 @@
immediate_action,
log_condition_elements,
scalar_value_none,
scalar_value_url,
)
from recruitment.models import InstructorRecruitmentSignup
from workshops.models import Event, Person, TagQuerySet, Task

logger = logging.getLogger("amy")


def instructor_confirmed_for_workshop_strategy(task: Task) -> StrategyEnum:
def instructor_confirmed_for_workshop_strategy(
task: Task, optional_task_pk: int | None = None
) -> StrategyEnum:
logger.info(f"Running InstructorConfirmedForWorkshop strategy for {task=}")

instructor_role = task.role.name == "instructor"
Expand All @@ -46,6 +50,9 @@ def instructor_confirmed_for_workshop_strategy(task: Task) -> StrategyEnum:
)

log_condition_elements(
task=task,
task_pk=task.pk,
optional_task_pk=optional_task_pk,
instructor_role=instructor_role,
person_email_exists=person_email_exists,
carpentries_tags=carpentries_tags,
Expand All @@ -61,10 +68,10 @@ def instructor_confirmed_for_workshop_strategy(task: Task) -> StrategyEnum:
)
logger.debug(f"{email_should_exist=}")

ct = ContentType.objects.get_for_model(task.person) # type: ignore
ct = ContentType.objects.get_for_model(Task) # type: ignore
has_email_scheduled = ScheduledEmail.objects.filter(
generic_relation_content_type=ct,
generic_relation_pk=task.person.pk,
generic_relation_pk=optional_task_pk or task.pk,
template__signal=INSTRUCTOR_CONFIRMED_FOR_WORKSHOP_SIGNAL_NAME,
state=ScheduledEmailStatus.SCHEDULED,
).exists()
Expand Down Expand Up @@ -111,22 +118,28 @@ def get_context(
) -> InstructorConfirmedContext:
person = Person.objects.get(pk=kwargs["person_id"])
event = Event.objects.get(pk=kwargs["event_id"])
task = Task.objects.filter(pk=kwargs["task_id"]).first()
instructor_recruitment_signup = InstructorRecruitmentSignup.objects.filter(
pk=kwargs["instructor_recruitment_signup_id"]
).first()
return {
"person": person,
"event": event,
"task": task,
"task_id": kwargs["task_id"],
"instructor_recruitment_signup": instructor_recruitment_signup,
}


def get_context_json(context: InstructorConfirmedContext) -> ContextModel:
signup = context["instructor_recruitment_signup"]
task = context["task"]
return ContextModel(
{
"person": api_model_url("person", context["person"].pk),
"event": api_model_url("event", context["event"].pk),
"task": api_model_url("task", task.pk) if task else scalar_value_none(),
"task_id": scalar_value_url("int", f"{context['task_id']}"),
"instructor_recruitment_signup": (
api_model_url(
"instructorrecruitmentsignup",
Expand All @@ -141,8 +154,9 @@ def get_context_json(context: InstructorConfirmedContext) -> ContextModel:

def get_generic_relation_object(
context: InstructorConfirmedContext, **kwargs: Unpack[InstructorConfirmedKwargs]
) -> Person:
return context["person"]
) -> Task:
# When removing task, this will be None.
return context["task"] # type: ignore


def get_recipients(
Expand Down Expand Up @@ -184,7 +198,7 @@ def get_generic_relation_object(
self,
context: InstructorConfirmedContext,
**kwargs: Unpack[InstructorConfirmedKwargs],
) -> Person:
) -> Task:
return get_generic_relation_object(context, **kwargs)

def get_recipients(
Expand Down Expand Up @@ -220,7 +234,7 @@ def get_generic_relation_object(
self,
context: InstructorConfirmedContext,
**kwargs: Unpack[InstructorConfirmedKwargs],
) -> Person:
) -> Task:
return get_generic_relation_object(context, **kwargs)

def get_recipients(
Expand Down Expand Up @@ -249,11 +263,21 @@ def get_context(
def get_context_json(self, context: InstructorConfirmedContext) -> ContextModel:
return get_context_json(context)

def get_generic_relation_content_type(
self, context: InstructorConfirmedContext, generic_relation_obj: Any
) -> ContentType:
return ContentType.objects.get_for_model(Task)

def get_generic_relation_pk(
self, context: InstructorConfirmedContext, generic_relation_obj: Any
) -> int | Any:
return context["task_id"]

def get_generic_relation_object(
self,
context: InstructorConfirmedContext,
**kwargs: Unpack[InstructorConfirmedKwargs],
) -> Person:
) -> Task:
return get_generic_relation_object(context, **kwargs)

def get_recipients_context_json(
Expand Down
Loading

0 comments on commit 72a2929

Please sign in to comment.