Skip to content

Commit

Permalink
refactor: [ACI-977] upgrade requirement processing (#170)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrii <[email protected]>
  • Loading branch information
andrii-hantkovskyi and Andrii authored May 15, 2024
1 parent 910bc8d commit 8dee580
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 37 deletions.
68 changes: 37 additions & 31 deletions credentials/apps/badges/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ def save(self, *args, **kwargs):
if not self.origin:
self.origin = self.ORIGIN
self.save(*args, **kwargs)

@property
def groups(self):
return self.requirements.values_list("group", flat=True).distinct()

@classmethod
def by_uuid(cls, template_uuid):
Expand Down Expand Up @@ -184,7 +188,7 @@ def fulfill(self, username: str):
"""
template_id = self.template.id
progress = BadgeProgress.for_user(username=username, template_id=template_id)
fulfillment, created = Fulfillment.objects.get_or_create(progress=progress, requirement=self)
fulfillment, created = Fulfillment.objects.get_or_create(progress=progress, requirement=self, group=self.group)

if created:
notify_requirement_fulfilled(
Expand Down Expand Up @@ -225,6 +229,18 @@ def is_fulfilled(self, username: str) -> bool:

return self.fulfillments.filter(progress__username=username, progress__template=self.template).exists()

@classmethod
def is_group_fulfilled(cls, *, group: str, template: BadgeTemplate, username: str) -> bool:
"""
Checks if the group is fulfilled.
"""

progress = BadgeProgress.for_user(username=username, template_id=template.id)
requirements = cls.objects.filter(template=template, group=group)
fulfilled_requirements = requirements.filter(fulfillments__progress=progress).count()

return fulfilled_requirements > 0

def apply_rules(self, data: dict) -> bool:
"""
Evaluates payload rules.
Expand Down Expand Up @@ -454,29 +470,18 @@ def ratio(self) -> float:
Calculates badge template progress ratio.
"""

requirements = BadgeRequirement.objects.filter(template=self.template)
group_ids = requirements.filter(group__isnull=False).values_list("group", flat=True).distinct()

requirements_count = requirements.filter(group__isnull=True).count() + group_ids.count()
fulfilled_requirements_count = Fulfillment.objects.filter(
progress=self,
requirement__template=self.template,
requirement__group__isnull=True,
).count()

for group_id in group_ids:
group_requirements = requirements.filter(group=group_id)
group_fulfilled_requirements_count = Fulfillment.objects.filter(
progress=self,
requirement__in=group_requirements,
).count()

if group_fulfilled_requirements_count > 0:
fulfilled_requirements_count += 1

if 0 in (requirements_count, fulfilled_requirements_count):
if not self.groups:
return 0.00
return round(fulfilled_requirements_count / requirements_count, 2)

true_values = len(list(filter(lambda x: x, self.groups.values())))
return round(true_values / len(self.groups.keys()), 2)

@property
def groups(self):
return {
group: BadgeRequirement.is_group_fulfilled(group=group, template=self.template, username=self.username)
for group in self.template.groups
}

@property
def completed(self):
Expand All @@ -486,16 +491,17 @@ def completed(self):

return self.ratio == 1.00

def validate(self):
def progress(self):
"""
Performs self-check and notifies about the current status.
Notify about the progress.
"""

if self.completed:
notify_progress_complete(self, self.username, self.template.id)

if not self.completed:
notify_progress_incomplete(self, self.username, self.template.id)
notify_progress_complete(self, self.username, self.template.id)

def regress(self):
"""
Notify about the regression.
"""
notify_progress_incomplete(self, self.username, self.template.id)

def reset(self):
Fulfillment.objects.filter(progress=self).delete()
Expand Down
11 changes: 8 additions & 3 deletions credentials/apps/badges/signals/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,19 @@ def handle_badging_event(sender, signal, **kwargs):

process_event(signal, **kwargs)


@receiver(BADGE_REQUIREMENT_FULFILLED)
def handle_requirement_fulfilled(sender, username, **kwargs): # pylint: disable=unused-argument
"""
On user's Badge progression (completion).
"""
BadgeProgress.for_user(username=username, template_id=sender.template.id).progress()

@receiver(BADGE_REQUIREMENT_REGRESSED)
def handle_requirement_regressed(sender, username, **kwargs): # pylint: disable=unused-argument
"""
Revises user's progress status each time relevant requirement's progress was updated (+/-).
On user's Badge regression (incompletion).
"""
BadgeProgress.for_user(username=username, template_id=sender.template.id).validate()
BadgeProgress.for_user(username=username, template_id=sender.template.id).regress()


@receiver(BADGE_PROGRESS_COMPLETE)
Expand Down
5 changes: 5 additions & 0 deletions credentials/apps/badges/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,16 +217,19 @@ def setUp(self):
template=self.badge_template,
event_type="org.openedx.learning.course.passing.status.updated.v1",
description="Test description",
group="A"
)
self.requirement2 = BadgeRequirement.objects.create(
template=self.badge_template,
event_type="org.openedx.learning.course.passing.status.updated.v1",
description="Test description",
group="B"
)
self.requirement3 = BadgeRequirement.objects.create(
template=self.badge_template,
event_type="org.openedx.learning.ccx.course.passing.status.updated.v1",
description="Test description",
group="C"
)

def test_user_progress_success(self):
Expand Down Expand Up @@ -294,11 +297,13 @@ def setUp(self):
template=self.badge_template,
event_type="org.openedx.learning.course.passing.status.updated.v1",
description="Test description",
group="A"
)
self.requirement2 = BadgeRequirement.objects.create(
template=self.badge_template,
event_type="org.openedx.learning.course.passing.status.updated.v1",
description="Test description",
group="B"
)

self.group_requirement1 = BadgeRequirement.objects.create(
Expand Down
2 changes: 1 addition & 1 deletion credentials/apps/badges/tests/test_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

COURSE_PASSING_EVENT = "org.openedx.learning.course.passing.status.updated.v1"
COURSE_PASSING_DATA = CoursePassingStatusData(
status="passing",
is_passing=True,
course=CourseData(course_key=CourseKey.from_string("course-v1:edX+DemoX.1+2014"), display_name="A"),
user=UserData(
id=1,
Expand Down
4 changes: 2 additions & 2 deletions credentials/apps/badges/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def setUp(self):

self.passing_status_1 = {
"course_passing_status": CoursePassingStatusData(
status=CoursePassingStatusData.PASSING, course=self.course_data_1, user=self.user_data_1
is_passing=True, course=self.course_data_1, user=self.user_data_1
)
}

Expand Down Expand Up @@ -99,7 +99,7 @@ def test_extract_payload(self):
id=1, is_active=True, pii=UserPersonalData(username="user1", email="[email protected] ", name="John Doe")
)
course_passing_status = CoursePassingStatusData(
status=CoursePassingStatusData.PASSING, course=self.course_data, user=user_data
is_passing=True, course=self.course_data, user=user_data
)
public_signal_kwargs = {"course_passing_status": course_passing_status}
result = extract_payload(public_signal_kwargs)
Expand Down

0 comments on commit 8dee580

Please sign in to comment.