Skip to content

Commit

Permalink
Updating the Certification Page for course based trainings (#2223)
Browse files Browse the repository at this point in the history
## What
This is a small PR making a tiny change in the queryset for the
trainings. The logic is updated to also include custom logic for courses
as their expiry is determined in slightly different way than the regular
one.

## Why
A TrainingType can have multiple courses attached to it, each being a
unique version in itself. Each course version should have it's own valid
duration - and hence it's own expiry date.

Whenever a user takes an on-platform course, it is the latest version of
the course available at that time, but with time new versions can come
in, and at some point, the version of the course that this training was
attached to, will be deprecated. At that point all the trainings
associated with this course version should be renewed. Thus, admin panel
provides an option to set an expiry date to the associated trainings
when deprecating a course version. That is how the expiry date for the
trainings is decided for course version, which is different from the way
it is determined for other types of trainings.

Thus, this PR adds onto the logic of querysets so that all the courses
are accurately identified and put under active / expired versions.

### This PR does not disturb the regular flow - it only add onto the
logic to also fetch the course based trainings (A feature in the
upcoming Trainings part-2 PR)
  • Loading branch information
tompollard authored Nov 26, 2024
2 parents c4c9a31 + 3db17cb commit ed6a23f
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 21 deletions.
33 changes: 20 additions & 13 deletions physionet-django/training/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,24 @@ def get_course_and_module_progress(user, course, module_order):
This function takes a user, course, and module order as input parameters,
and returns the course progress and module progress objects associated with the user, course, and module order.
"""
course_progress, _ = CourseProgress.objects.get_or_create(user=user, course=course)
# get the course progress of the user for the course
course_progress = CourseProgress.objects.filter(user=user, course=course).first()
if not course_progress:
course_progress = CourseProgress.objects.create(user=user, course=course)
# Initiate the training and set the status to review
slug = get_random_string(20)
while Training.objects.filter(slug=slug).exists():
slug = get_random_string(20)
Training.objects.create(
slug=slug,
training_type=course.training_type,
user=user,
course=course,
process_datetime=timezone.now(),
status=TrainingStatus.IN_PROGRESS
)


module = get_object_or_404(course.modules, order=module_order)
module_progress, _ = ModuleProgress.objects.get_or_create(course_progress=course_progress, module=module)
return course_progress, module_progress
Expand Down Expand Up @@ -88,16 +105,7 @@ def handle_course_completion(course, course_progress):
with transaction.atomic():
course_progress.status = CourseProgress.Status.COMPLETED
course_progress.save()
training = Training()
slug = get_random_string(20)
while Training.objects.filter(slug=slug).exists():
slug = get_random_string(20)

training.slug = slug
training.training_type = course_progress.course.training_type
training.course = course_progress.course
training.user = course_progress.user
training.process_datetime = timezone.now()
training = Training.objects.filter(course=course, user=course_progress.user).first()
training.status = TrainingStatus.ACCEPTED
training.save()

Expand Down Expand Up @@ -275,8 +283,7 @@ def take_training(request, training_slug):
if course is None:
raise Http404()
modules = Module.objects.filter(course=course).order_by('order')
# get the progress of the user for the modules, updated_date
course_progress, _ = CourseProgress.objects.get_or_create(user=request.user, course=course)
course_progress, _ = get_course_and_module_progress(request.user, course, modules.first().order)

for module in modules:
module_progress = course_progress.module_progresses.filter(module_id=module.id).last()
Expand Down
1 change: 1 addition & 0 deletions physionet-django/user/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class TrainingStatus(IntEnum):
WITHDRAWN = 1
REJECTED = 2
ACCEPTED = 3
IN_PROGRESS = 4

@classmethod
def choices(cls):
Expand Down
55 changes: 47 additions & 8 deletions physionet-django/user/managers.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,71 @@
from django.db.models import DateTimeField, ExpressionWrapper, QuerySet, F, Q
from django.db.models import (DateTimeField, ExpressionWrapper, QuerySet, F, Q,
OuterRef, Subquery, Case, When)
from django.utils import timezone

from user.enums import TrainingStatus

from user.enums import TrainingStatus, RequiredField
from training.models import Course

class TrainingQuerySet(QuerySet):
def get_review(self):
return self.filter(status=TrainingStatus.REVIEW)
"""
Get the document-based or URL-based training objects for the user, that are in the status REVIEW.
"""
return self.filter(
Q(status=TrainingStatus.REVIEW),
Q(training_type__required_field=RequiredField.DOCUMENT)
| Q(training_type__required_field=RequiredField.URL)
)

def get_in_progress(self):
"""
Get the on-platform training objects for the user, that are in the status IN-PROGRESS.
"""
return self.filter(
Q(status=TrainingStatus.IN_PROGRESS),
Q(training_type__required_field=RequiredField.PLATFORM)
)

def get_valid(self):
"""
Get all the training objects that are in the status ACCEPTED and have not expired.
"""
return self.filter(
Q(status=TrainingStatus.ACCEPTED),
Q(training_type__valid_duration__isnull=True)
| Q(process_datetime__gte=timezone.now() - F('training_type__valid_duration')),
| Q(process_datetime__gte=timezone.now() - Case(
When(training_type__required_field=RequiredField.PLATFORM, then=F('course__valid_duration')),
default=F('training_type__valid_duration')
)),
).annotate(
valid_datetime=ExpressionWrapper(
F('process_datetime') + F('training_type__valid_duration'), output_field=DateTimeField()
F('process_datetime') + Case(
When(training_type__required_field=RequiredField.PLATFORM, then=F('course__valid_duration')),
default=F('training_type__valid_duration')
), output_field=DateTimeField()
)
)

def get_expired(self):
"""
Get all the training objects that are in the status ACCEPTED and have expired
"""
return self.filter(
status=TrainingStatus.ACCEPTED, process_datetime__lt=timezone.now() - F('training_type__valid_duration')
Q(status=TrainingStatus.ACCEPTED),
Q(process_datetime__lt=timezone.now() - Case(
When(training_type__required_field=RequiredField.PLATFORM, then=F('course__valid_duration')),
default=F('training_type__valid_duration')
)),
).annotate(
valid_datetime=ExpressionWrapper(
F('process_datetime') + F('training_type__valid_duration'), output_field=DateTimeField()
F('process_datetime') + Case(
When(training_type__required_field=RequiredField.PLATFORM, then=F('course__valid_duration')),
default=F('training_type__valid_duration')
), output_field=DateTimeField()
)
)

def get_rejected(self):
"""
Get all the training objects that are in the status REJECTED.
"""
return self.filter(status=TrainingStatus.REJECTED)
28 changes: 28 additions & 0 deletions physionet-django/user/migrations/0063_alter_training_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 4.2.14 on 2024-11-15 00:47

from django.db import migrations, models
import user.enums


class Migration(migrations.Migration):

dependencies = [
("user", "0062_training_course"),
]

operations = [
migrations.AlterField(
model_name="training",
name="status",
field=models.PositiveSmallIntegerField(
choices=[
(0, "REVIEW"),
(1, "WITHDRAWN"),
(2, "REJECTED"),
(3, "ACCEPTED"),
(4, "IN_PROGRESS"),
],
default=user.enums.TrainingStatus["REVIEW"],
),
),
]
1 change: 1 addition & 0 deletions physionet-django/user/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,7 @@ def edit_certification(request):
)
training_by_status = {
"under review": training.get_review(),
"in progress": training.get_in_progress(),
"active": training.get_valid(),
"expired": training.get_expired(),
"rejected": training.get_rejected(),
Expand Down

0 comments on commit ed6a23f

Please sign in to comment.