Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create training basics --> implement take training feature and resume training feature for user #1951

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<div class="card mb-3">
<div class="card-body">
<div><button type="button" class="btn btn-sm btn-success" data-toggle="modal"
data-target="#create-course">Update</button></div>
data-target="#create-course">Upload new version</button></div>
<div class="modal fade" id="create-course" tabindex="-1">
<div class="modal-dialog">
<form action="{% url 'course_details' training_type.slug %}" method="POST" enctype="multipart/form-data" class="">
Expand Down Expand Up @@ -79,19 +79,16 @@ <h3 class="card-title text-center">Active Versions</h3>
class="fa fa-download"></i> Download</a>
</td>
<td>
<form action="{% url 'expire_course_version' training_type.slug course.version %}" method="POST">
<form action="{% url 'archive_course_version' training_type.slug course.version %}" method="POST">
{% csrf_token %}
<input type="date" name="expiry_date">
<button class="btn btn-sm btn-danger" type="submit"> Expire</button>
<button class="btn btn-sm btn-danger" type="submit"> Archive</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<p><b>Note:</b> Users that have taken the particular version of the course that is getting expired,
will need to retake the course or else they will loose credentialing after the number of
days specified above while expiring the course. </p>
<p><b>Note:</b> Users can no longer take the course if there are no active versions available.</p>
</div>
<h3 class="card-title text-center">Archived Versions</h3>
<div class="card-body">
Expand Down
4 changes: 2 additions & 2 deletions physionet-django/console/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@
path('courses/<training_slug>/', training_views.course_details, name='course_details'),
path('courses/<training_slug>/download/<str:version>',
training_views.download_course, name='download_course_version'),
path('courses/<training_slug>/expire/<str:version>',
training_views.expire_course, name='expire_course_version'),
path('courses/<training_slug>/archive/<str:version>',
training_views.archive_course, name='archive_course_version'),
]

# Parameters for testing URLs (see physionet/test_urls.py)
Expand Down
1 change: 1 addition & 0 deletions physionet-django/physionet/test_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ def _handle_request(self, url, _user_=None, _query_={}, _skip_=False,
self.client.force_login(user)

response = self.client.get(url, _query_)

self.assertGreaterEqual(response.status_code, 200)
self.assertLess(response.status_code, 400)

Expand Down
21 changes: 21 additions & 0 deletions physionet-django/static/custom/css/quiz.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.eachQuiz{
display: none
}

.blockContent{
display: none
}

.quizContainer {
max-width: 100%;
position: relative;
margin: auto;
display: block;
}

.blockContainer {
max-width: 100%;
position: relative;
margin: auto;
display: block;
}
2 changes: 1 addition & 1 deletion physionet-django/static/sample/example-course-update.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"title": "Course 1 Updated",
"title": "Course 1",
"description": "<p>Test content description Updated</div>",
"valid_duration": "1095 00:00:00",
"version": "1.5.1",
Expand Down
39 changes: 37 additions & 2 deletions physionet-django/training/fixtures/demo-training.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,19 @@
"pk": 1,
"fields": {
"training_type": 2,
"version": 1.0
"version": 1.2,
"description": "This course provides an introduction to the continents and countries of the world. It covers the major continents, including North America, South America, Europe, Asia, Africa, and Australia. The course also explores the countries within each continent, highlighting their geography, history, and culture.",
"valid_duration": "1095"
}
},
{
"model": "training.course",
"pk": 2,
"fields": {
"training_type": 2,
"version": 1.0,
"description": "This course provides an introduction to the continents and countries of the world. It covers the major continents, including North America, South America, Europe, Asia, Africa, and Australia. The course also explores the countries within each continent, highlighting their geography, history, and culture.",
"valid_duration": "1095"
}
},
{
Expand Down Expand Up @@ -351,7 +363,7 @@
"order": 9
}
},
{
{
"model": "training.quizchoice",
"pk": 17,
"fields": {
Expand Down Expand Up @@ -431,5 +443,28 @@
"body": "Cameroon",
"is_correct": false
}
},
{
"model": "training.courseprogress",
"pk": 1,
"fields": {
"user": 206,
"course": 1,
"status": "IP",
"started_at": "2024-05-12T00:01:05.884Z",
"completed_at": null
}
},
{
"model": "training.moduleprogress",
"pk": 1,
"fields": {
"course_progress": 1,
"module": 1,
"status": "IP",
"last_completed_order": 1,
"started_at": null,
"updated_at": "2024-05-12T00:01:09.318Z"
}
}
]
33 changes: 33 additions & 0 deletions physionet-django/training/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from django import forms
from django.db.models import Max, F, OuterRef

from training.models import Course
from user.models import TrainingType
from user.enums import RequiredField


class CourseForm(forms.ModelForm):

class Meta:
model = Course
fields = ('training_type', )
labels = {'training_type': 'Training Type'}

def __init__(self, *args, **kwargs):
training_id = kwargs.pop('training_type', None)
super().__init__(*args, **kwargs)

self.fields['training_type'].queryset = self.fields['training_type'].queryset.annotate(
max_version=Max('courses__version')
).filter(
courses__version=F('max_version'),
required_field=RequiredField.PLATFORM,
courses__is_active=True
)

self.training_type = TrainingType.objects.filter(id=training_id).first()

self.fields['training_type'].initial = self.training_type

if self.training_type:
self.fields['training_type'].help_text = self.training_type.description
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 4.2.11 on 2024-08-20 18:56

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("training", "0005_course_trainings"),
]

operations = [
migrations.RemoveField(
model_name="course",
name="trainings",
),
migrations.AlterUniqueTogether(
name="completedcontent",
unique_together={("module_progress", "content")},
),
migrations.AlterUniqueTogether(
name="completedquiz",
unique_together={("module_progress", "quiz")},
),
migrations.AlterUniqueTogether(
name="moduleprogress",
unique_together={("course_progress", "module")},
),
]
38 changes: 32 additions & 6 deletions physionet-django/training/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from token import NUMBER
from django.db import models
from django.utils import timezone

Expand All @@ -21,7 +20,6 @@ class Course(models.Model):
training_type = models.ForeignKey(
"user.TrainingType", on_delete=models.CASCADE, related_name="courses"
)
trainings = models.ManyToManyField("user.Training")
version = models.CharField(
max_length=15, default="", blank=True, validators=[validate_version]
)
Expand All @@ -38,14 +36,12 @@ class Meta:
("can_view_course_guidelines", "Can view course guidelines"),
]

def expire_course_version(self, instance, number_of_days):
def archive_course_version(self):
"""
This method expires the course by setting the is_active field to False and expires all
This method archives the course by setting the is_active field to False and expires all
the trainings associated with it.
"""
self.is_active = False
# reset the valid_duration to the number of days
self.valid_duration = timezone.timedelta(days=number_of_days)
self.save()

def __str__(self):
Expand Down Expand Up @@ -224,6 +220,30 @@ class Status(models.TextChoices):
def __str__(self):
return f"{self.course_progress.user.username} - {self.module}"

class Meta:
unique_together = ("course_progress", "module")

def get_next_content_or_quiz(self):
if self.status == self.Status.COMPLETED:
return None

next_content = self.module.contents.filter(order__gt=self.last_completed_order).order_by('order').first()
next_quiz = self.module.quizzes.filter(order__gt=self.last_completed_order).order_by('order').first()

if next_content and next_quiz:
return next_content if next_content.order < next_quiz.order else next_quiz
elif next_content:
return next_content
elif next_quiz:
return next_quiz
else:
return None

def update_last_completed_order(self, completed_content_or_quiz):
if completed_content_or_quiz.order > self.last_completed_order:
self.last_completed_order = completed_content_or_quiz.order
self.save()


class CompletedContent(models.Model):
"""
Expand All @@ -249,6 +269,9 @@ class CompletedContent(models.Model):
def __str__(self):
return f"{self.module_progress.course_progress.user.username} - {self.content}"

class Meta:
unique_together = ("module_progress", "content")


class CompletedQuiz(models.Model):
"""
Expand All @@ -273,3 +296,6 @@ class CompletedQuiz(models.Model):

def __str__(self):
return f"{self.module_progress.course_progress.user.username} - {self.quiz}"

class Meta:
unique_together = ("module_progress", "quiz")
74 changes: 74 additions & 0 deletions physionet-django/training/templates/training/course.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{% extends 'base.html' %}

{% load static %}

{% block title %}Training - {{ course.training_type.name }}{% endblock %}

{% block local_css %}
<link rel="stylesheet"
type="text/css"
href="{% static 'project/css/project-home.css' %}">
{% endblock %}

{% block content %}
<div class="container">
<h1>{{ course.training_type.name }}</h1>
{% include "message_snippet.html" %}
<div class="description">
<h2>Training Description</h2>
{{ course.training_type.description | safe }}
</div>

<div class="card mb-3">
<div class="card-header">
<h3>Training Modules</h3>
</div>
<div class="card-body">
{% if modules %}
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Module</th>
<th scope="col">Progress</th>
<th scope="col">Dates</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
{% for module in modules %}
<tr>
<td>
<strong>{{ module.name }}</strong>
</td>
<td>{{ module.progress_status }}</td>
<td>{{ module.progress_updated_date}}</td>
<td>
<a href="{% url 'current_module_block' course.training_type.slug module.pk module.last_completed_order %}">
{% if module.progress_status == ModuleStatus.COMPLETED.label %}
Review
{% elif module.progress_status == ModuleStatus.IN_PROGRESS.label %}
Continue
{% else %}
Start
{% endif %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No datasets available.</p>
{% endif %}
</div>
</div>
<br>

</div>
{% endblock %}

{% block local_js_bottom %}
<script>
</script>
<script src="{% static 'custom/js/resize-ck.js' %}"></script>
{% endblock %}
Loading
Loading