diff --git a/physionet-django/training/views.py b/physionet-django/training/views.py index 6b2b56aa1..028096ce9 100644 --- a/physionet-django/training/views.py +++ b/physionet-django/training/views.py @@ -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 @@ -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() @@ -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() diff --git a/physionet-django/user/enums.py b/physionet-django/user/enums.py index 004ebbc96..f7d06194e 100644 --- a/physionet-django/user/enums.py +++ b/physionet-django/user/enums.py @@ -8,6 +8,7 @@ class TrainingStatus(IntEnum): WITHDRAWN = 1 REJECTED = 2 ACCEPTED = 3 + IN_PROGRESS = 4 @classmethod def choices(cls): diff --git a/physionet-django/user/managers.py b/physionet-django/user/managers.py index d92b0040f..0a17c95ab 100644 --- a/physionet-django/user/managers.py +++ b/physionet-django/user/managers.py @@ -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) diff --git a/physionet-django/user/migrations/0063_alter_training_status.py b/physionet-django/user/migrations/0063_alter_training_status.py new file mode 100644 index 000000000..d2e523de0 --- /dev/null +++ b/physionet-django/user/migrations/0063_alter_training_status.py @@ -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"], + ), + ), + ] diff --git a/physionet-django/user/views.py b/physionet-django/user/views.py index 28361a6d2..77d5d77a4 100644 --- a/physionet-django/user/views.py +++ b/physionet-django/user/views.py @@ -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(),