Skip to content

Commit

Permalink
Add option for users to set personalized points goals for modules
Browse files Browse the repository at this point in the history
  • Loading branch information
mikaelGusse committed Jun 13, 2024
1 parent 1604329 commit 6c70125
Show file tree
Hide file tree
Showing 19 changed files with 260 additions and 13 deletions.
1 change: 1 addition & 0 deletions assets/sass/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ $brand-success: $aplus-success;
$brand-info: $aplus-info;
$brand-warning: $aplus-warning;
$brand-danger: $aplus-danger;
$brand-focus: $aplus-focus;
// The default $headings-small-color has a low contrast in most of the cases.
$headings-small-color: #535353;

Expand Down
11 changes: 10 additions & 1 deletion course/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from aplus.api import api_reverse
from exercise.models import SubmissionDraft
from lib.fields import UsersSearchSelectField
from .models import Enrollment, StudentGroup
from .models import CourseModule, Enrollment, StudentGroup, StudentModuleGoal
from userprofile.models import UserProfile


Expand Down Expand Up @@ -162,3 +162,12 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
required=False,
label=_('LABEL_ENROLL_FROM_SIS'),
)

class StudentModuleGoalForm(forms.ModelForm):
student = forms.ModelChoiceField(queryset=UserProfile.objects.all())
module = forms.ModelChoiceField(queryset=CourseModule.objects.all())
personalized_points_goal = forms.IntegerField(initial=100)

class Meta:
model = StudentModuleGoal
fields = ['student', 'module', 'personalized_points_goal']
20 changes: 20 additions & 0 deletions course/migrations/0060_coursemodule_personalized_points_goal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 4.2.11 on 2024-06-03 11:25

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("course", "0059_submissiontag"),
]

operations = [
migrations.AddField(
model_name="coursemodule",
name="personalized_points_goal",
field=models.PositiveIntegerField(
default=100, verbose_name="LABEL_PERSONALIZED_POINTS_GOAL"
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.2.11 on 2024-06-03 12:52

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("course", "0060_coursemodule_personalized_points_goal"),
]

operations = [
migrations.RemoveField(
model_name="coursemodule",
name="personalized_points_goal",
),
]
44 changes: 44 additions & 0 deletions course/migrations/0062_studentmodulegoal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generated by Django 4.2.11 on 2024-06-03 13:02

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("userprofile", "0008_delete_studentmodulegoal"),
("course", "0061_remove_coursemodule_personalized_points_goal"),
]

operations = [
migrations.CreateModel(
name="StudentModuleGoal",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("personalized_points_goal", models.IntegerField(default=0)),
(
"module",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="course.coursemodule",
),
),
(
"student",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="userprofile.userprofile",
),
),
],
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.11 on 2024-06-12 10:11

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("course", "0062_studentmodulegoal"),
]

operations = [
migrations.AlterField(
model_name="studentmodulegoal",
name="personalized_points_goal",
field=models.IntegerField(default=100),
),
]
6 changes: 6 additions & 0 deletions course/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1482,6 +1482,12 @@ def number_of_submitters(self):
.filter(submissions__exercise__course_module=self).distinct().count()


class StudentModuleGoal(models.Model):
student = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
module = models.ForeignKey(CourseModule, on_delete=models.CASCADE)
personalized_points_goal = models.IntegerField(default=100)


class LearningObjectCategory(models.Model):
"""
Learning objects may be grouped to different categories.
Expand Down
4 changes: 2 additions & 2 deletions course/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
from lib.helpers import settings_text, remove_query_param_from_url, is_ajax
from lib.viewbase import BaseTemplateView, BaseRedirectMixin, BaseFormView, BaseView, BaseRedirectView
from userprofile.viewbase import UserProfileView
from .forms import GroupsForm, GroupSelectForm
from .models import Course, CourseInstance, CourseModule, Enrollment
from .forms import GroupsForm, GroupSelectForm, StudentModuleGoalForm
from .models import Course, CourseInstance, CourseModule, Enrollment, StudentModuleGoal
from .permissions import EnrollInfoVisiblePermission
from .renders import group_info_context
from .viewbase import CourseModuleBaseView, CourseInstanceMixin, EnrollableViewMixin, CourseMixin
Expand Down
20 changes: 20 additions & 0 deletions exercise/migrations/0051_baseexercise_personalized_points_goal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 4.2.11 on 2024-06-03 10:21

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("exercise", "0050_submissiontagging"),
]

operations = [
migrations.AddField(
model_name="baseexercise",
name="personalized_points_goal",
field=models.PositiveIntegerField(
default=100, verbose_name="LABEL_PERSONALIZED_POINTS_GOAL"
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.2.11 on 2024-06-03 11:26

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("exercise", "0051_baseexercise_personalized_points_goal"),
]

operations = [
migrations.RemoveField(
model_name="baseexercise",
name="personalized_points_goal",
),
]
4 changes: 3 additions & 1 deletion exercise/templates/exercise/_points_progress.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
{% translate "RESULTS_OF_SOME_ASSIGNMENTS_ARE_CURRENTLY_HIDDEN" %}
</p>
{% endif %}
Personalized {{personalized_points_goal}}

<div class="progress" data-toggle="tooltip" data-html="true" data-placement="bottom"
title="{% translate 'POINTS' %}: &lt;span class='text-nowrap'&gt;{{ formatted_points }} / {{ max }}&lt;/span&gt;{% if required %} {% translate 'POINTS_TO_PASS' %}: &lt;span class='text-nowrap'&gt;{{ required }}&lt;/span&gt;{% endif %}">
<div class="progress-bar progress-bar-striped progress-bar-{% if full_score %}success{% elif passed %}warning{% else %}danger{% endif %}"
<div class="progress-bar progress-bar-striped progress-bar-{% if percentage >= personalized_points_goal %}info{% elif full_score %}success{% elif passed %}warning{% else %}danger{% endif %}"
rel="progressbar" aria-valuenow="{{ points }}" aria-valuemin="0" aria-valuemax="{{ max }}"
style="width:{{ percentage }}%;"></div>
{% if required_percentage %}
Expand Down
10 changes: 8 additions & 2 deletions exercise/templates/exercise/_user_results.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@
aria-expanded="{% if accessible and not after_open or open %}true{% else %}false{% endif %}" aria-controls="#module{{ module.id }}">
<h3 class="panel-title">
{% points_badge module "pull-right" %}
{% for goal in personalized_points_goal %}
{% if goal.module.id == module.id %}
PERSONALIZED POINTS GOAL {{ goal.personalized_points_goal }}%
{% endif %}
{% endfor %}
{% if not accessible %}
<span class="badge pull-right">
{% translate "OPENS ON" %} {{ module.opening_time }}
Expand Down Expand Up @@ -103,8 +108,9 @@ <h3 class="panel-title">
{% endblocktranslate %}
{% endif %}
</p>

{% points_progress module %}

Goals {{goal.personalized_points_goal}}
{% points_progress module 50 %}
{{ module.introduction|safe }}
{% if student %}
{% adddeviationsbutton instance module submitters=student %}
Expand Down
5 changes: 5 additions & 0 deletions exercise/templates/exercise/student_module_goal.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Save</button>
</form>
15 changes: 12 additions & 3 deletions exercise/templatetags/exercise.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy as _

from course.models import CourseInstance, CourseModule
from course.models import CourseInstance, CourseModule, StudentModuleGoal
from lib.errors import TagUsageError
from lib.helpers import format_points as _format_points, is_ajax as _is_ajax
from userprofile.models import UserProfile
Expand All @@ -29,7 +29,6 @@

register = template.Library()


def _prepare_now(context):
if 'now' not in context:
context['now'] = timezone.now()
Expand All @@ -54,12 +53,18 @@ def _prepare_context(context: Context, student: Optional[User] = None) -> Cached

def _get_toc(context, student=None):
points = _prepare_context(context, student)
# Add personalized_points_goal to the context
if 'personalized_points_goal' not in context:
context["personalized_points_goal"] = StudentModuleGoal.objects.all
print(StudentModuleGoal.objects.all())

context = context.flatten()
context.update({
'modules': points.modules_flatted(),
'categories': points.categories(),
'total': points.total().as_dict(),
'is_course_staff': context.get('is_course_staff', False),
'personalized_points_goal': context.get('personalized_points_goal', 100),
})
return context

Expand Down Expand Up @@ -110,6 +115,7 @@ def user_results(context: Context, student: Optional[User] = None) -> Dict[str,
.order_by()
)
values['exercise_submitter_counts'] = {row['submissions__exercise_id']: row['count'] for row in counts}
print("StudentModuleGoalObjects", StudentModuleGoal.objects.all())
return values


Expand Down Expand Up @@ -245,8 +251,11 @@ def _points_data(
@register.inclusion_tag("exercise/_points_progress.html")
def points_progress(
obj: Union[CachedPointsData, ModulePoints, CategoryPoints],
personalized_points_goal: int = 100,
) -> Dict[str, Any]:
return _points_data(obj, None)
data = _points_data(obj, None)
data['personalized_points_goal'] = personalized_points_goal
return data


@register.inclusion_tag("exercise/_points_badge.html")
Expand Down
3 changes: 3 additions & 0 deletions exercise/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@
re_path(EDIT_URL_PREFIX + r'results/$',
staff_views.AllResultsView.as_view(),
name="all-results"),
re_path(MODULE_URL_PREFIX + r'student_module_goal/$',
views.StudentModuleGoalView.as_view(),
name="student_module_goal"),
re_path(EDIT_URL_PREFIX + r'analytics/$',
staff_views.AnalyticsView.as_view(),
name="analytics"),
Expand Down
25 changes: 22 additions & 3 deletions exercise/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
from django.db import DatabaseError

from authorization.permissions import ACCESS
from course.models import CourseModule, SubmissionTag
from course.viewbase import CourseInstanceBaseView, EnrollableViewMixin
from course.forms import StudentModuleGoalForm
from course.models import CourseModule, StudentModuleGoal, SubmissionTag
from course.viewbase import CourseInstanceBaseView, CourseModuleBaseView, EnrollableViewMixin
from lib.helpers import query_dict_to_list_of_tuples, safe_file_name, is_ajax
from lib.remote_page import RemotePageNotFound, request_for_response
from lib.viewbase import BaseRedirectMixin, BaseView
from lib.viewbase import BaseFormView, BaseRedirectMixin, BaseView
from userprofile.models import UserProfile
from .cache.points import ExercisePoints
from .models import BaseExercise, LearningObject, LearningObjectDisplay
Expand Down Expand Up @@ -561,3 +562,21 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
self.exercise.set_submission_draft(self.profile, submission_data_list)
# Simple OK response
return HttpResponse()


class StudentModuleGoalView(CourseModuleBaseView, BaseFormView):
access_mode = ACCESS.STUDENT
template_name = "exercise/student_module_goal.html"
form_class = StudentModuleGoalForm

def get_common_objects(self):
super().get_common_objects()

def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
return kwargs

def form_valid(self, form):
form.save()
messages.success(self.request, _('MODULE_GOAL_UPDATED'))
return super().form_valid(form)
36 changes: 36 additions & 0 deletions userprofile/migrations/0007_studentmodulegoal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by Django 4.2.11 on 2024-06-03 12:52

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("userprofile", "0006_auto_20210812_1536"),
]

operations = [
migrations.CreateModel(
name="StudentModuleGoal",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("personalized_points_goal", models.IntegerField(default=0)),
(
"student",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="userprofile.userprofile",
),
),
],
),
]
16 changes: 16 additions & 0 deletions userprofile/migrations/0008_delete_studentmodulegoal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Generated by Django 4.2.11 on 2024-06-03 13:02

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("userprofile", "0007_studentmodulegoal"),
]

operations = [
migrations.DeleteModel(
name="StudentModuleGoal",
),
]
Loading

0 comments on commit 6c70125

Please sign in to comment.