Skip to content

Commit

Permalink
Personalized points goal improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
mikaelGusse committed Aug 1, 2024
1 parent 5ffda9a commit 9b61e00
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 28 deletions.
4 changes: 2 additions & 2 deletions course/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
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)
personalized_points_goal_percentage = forms.IntegerField(initial=100)

class Meta:
model = StudentModuleGoal
fields = ['student', 'module', 'personalized_points_goal']
fields = ['student', 'module', 'personalized_points_goal_percentage']
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.11 on 2024-08-01 13:04

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("course", "0063_alter_studentmodulegoal_personalized_points_goal"),
]

operations = [
migrations.AlterField(
model_name="studentmodulegoal",
name="personalized_points_goal",
field=models.FloatField(default=100.0),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.11 on 2024-08-01 13:18

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("course", "0064_alter_studentmodulegoal_personalized_points_goal"),
]

operations = [
migrations.RenameField(
model_name="studentmodulegoal",
old_name="personalized_points_goal",
new_name="personalized_points_goal_percentage",
),
]
2 changes: 1 addition & 1 deletion course/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1485,7 +1485,7 @@ def number_of_submitters(self):
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)
personalized_points_goal_percentage = models.FloatField(default=100.0)


class LearningObjectCategory(models.Model):
Expand Down
23 changes: 20 additions & 3 deletions exercise/cache/points.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from django.db.models.signals import post_save, post_delete, pre_delete, m2m_changed
from django.utils import timezone

from course.models import CourseInstance, CourseModule, StudentGroup
from course.models import CourseInstance, CourseModule, StudentGroup, StudentModuleGoal
from deviations.models import DeadlineRuleDeviation, MaxSubmissionsRuleDeviation
from lib.cache.cached import DBDataManager, Dependencies, ProxyManager, resolve_proxies
from lib.helpers import format_points
Expand Down Expand Up @@ -865,6 +865,8 @@ class ModulePoints(DifficultyStats, ModuleEntryBase[LearningObjectPoints]):
_children_unconfirmed: bool
is_model_answer_revealed: bool
confirmable_children: bool
personalized_points_module_goal: Optional[int]
personalized_points_module_goal_points: Optional[int]

children_unconfirmed = RevealableAttribute[bool]()

Expand Down Expand Up @@ -919,7 +921,8 @@ def _generate_data(
self._points_by_difficulty = {}
self._true_unconfirmed_points_by_difficulty = {}
self._unconfirmed_points_by_difficulty = {}

self.personalized_points_module_goal = None
self.personalized_points_module_goal_points = None
self.instance = precreated.get_or_create_proxy(
CachedPointsData, *self.instance._params, user_id, modifiers=self._modifiers
)
Expand All @@ -940,6 +943,16 @@ def _generate_data(
elif entry.submission_count > 0:
self.confirmable_children = True

def update_personalized_points():
print("Called update_personalized_points")
try:
self.personalized_points_module_goal = StudentModuleGoal.objects.get(module_id=module_id, student_id=user_id).personalized_points_goal_percentage
self.personalized_points_module_goal_points = ((self.personalized_points_module_goal*0.01) * self.max_points)
print("Module Goal: " + str(self.personalized_points_module_goal))
except StudentModuleGoal.DoesNotExist:
print("Module Goal: None")
self.personalized_points_module_goal = None

def add_points(children):
for entry in children:
if not entry.confirm_the_level and isinstance(entry, ExercisePoints) and entry.is_visible():
Expand All @@ -948,7 +961,8 @@ def add_points(children):
add_points(entry.children)

add_points(self.children)

print("Called add_points")
update_personalized_points()
self._true_passed = self._true_passed and self._true_points >= self.points_to_pass
self._passed = self._passed and self._points >= self.points_to_pass

Expand All @@ -966,6 +980,9 @@ def add_points(children):
ModuleEntryBase: [self._params[:1]],
LearningObjectPoints: [proxy._params for proxy in self.children],
}

def update_personalized_points(self):
self._generate_data()


CachedPointsDataType = TypeVar("CachedPointsDataType", bound="CachedPointsData")
Expand Down
5 changes: 5 additions & 0 deletions exercise/static/exercise/css/goal_points.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.progress .goal-points {
position: absolute;
height: 20px;
border-left: 1px solid blue;
}
11 changes: 7 additions & 4 deletions exercise/templates/exercise/_points_progress.html
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
{% load i18n %}
{% load static %}
{% if not feedback_revealed %}
<p class="small">
<i class="glyphicon glyphicon-info-sign"></i>
{% 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 percentage >= personalized_points_goal %}info{% elif full_score %}success{% elif passed %}warning{% else %}danger{% endif %}"
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 %}{% if personalized_points_module_goal %} {% translate 'PERSONALIZED_POINTS_GOAL' %}: &lt;span class='text-nowrap'&gt;{{ formatted_points }} / {{ personalized_points_module_goal_points|floatformat:"0" }}&lt;/span&gt;{% endif %}">
<div class="progress-bar progress-bar-striped progress-bar-{% if full_score %}success{% elif percentage >= personalized_points_module_goal %}primary{% elif passed %}warning{% else %}danger{% endif %}"
rel="progressbar" aria-valuenow="{{ points }}" aria-valuemin="0" aria-valuemax="{{ max }}"
style="width:{{ percentage }}%;"></div>
{% if required_percentage %}
<div class="required-points" style="left:{{ required_percentage }}%"></div>
{% endif %}
{% if personalized_points_module_goal %}
<link rel="stylesheet" href="{% static 'exercise/css/goal_points.css' %}" />
<div class="goal-points" style="left:{{ personalized_points_module_goal|floatformat:0 }}%"></div>
{% endif %}
</div>
31 changes: 23 additions & 8 deletions exercise/templates/exercise/_user_results.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{% load static %}
{% load course %}
{% load exercise %}

{% include 'exercise/points_goal_modal.html' %}
<script src="{% static 'exercise/user_results.js' %}"></script>

{% if categories|len_listed > 1 %}
Expand Down Expand Up @@ -45,11 +45,6 @@
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 @@ -109,8 +104,15 @@ <h3 class="panel-title">
{% endif %}
</p>

Goals {{goal.personalized_points_goal}}
{% points_progress module 50 %}
{% with module_id=module.id student_id=user.id %}
{% get_goal module_id student_id as goal %}
<div style="width: 100%;">
<button type="button" class="aplus-button--secondary aplus-button--xs" style="float: right;" data-toggle="modal" data-target="#pointsGoalModal" data-module-id="{{ module.id }}">
<span class="glyphicon glyphicon-screenshot" aria-hidden="true"></span> Points goal
</button>
{% points_progress module %}
{% endwith %}

{{ module.introduction|safe }}
{% if student %}
{% adddeviationsbutton instance module submitters=student %}
Expand Down Expand Up @@ -282,3 +284,16 @@ <h3 class="panel-title">
startListen("{{course|safe}}");
}, false);
</script>

<script>
document.addEventListener('DOMContentLoaded', function() {
$('#pointsGoalModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); // Button that triggered the modal
var moduleId = button.data('module-id'); // Extract info from data-* attributes

// Use the moduleId as needed, for example, set it in a hidden input field in the modal
var modal = $(this);
modal.find('.modal-body input#module').val(moduleId);
});
});
</script>
28 changes: 28 additions & 0 deletions exercise/templates/exercise/points_goal_modal.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!-- points_goal_modal.html -->
<div id="pointsGoalModal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="pointsGoalModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="pointsGoalModalLabel">Set points goal for module {{module.name}}</h4>
</div>
<div class="modal-body">
<form method="post" action="{% url 'save_points_goal' %}">
{% csrf_token %}
<div class="form-group">
<label for="points-goal" class="control-label">Points Goal:</label>
<input type="text" class="form-control" id="points-goal" name="points_goal" placeholder="Enter points or percentage (e.g., 50 or 50%)">
</div>
<!-- Hidden fields to automatically send more information -->
<input type="hidden" name="student" value="{{ user.id }}">
<input type="hidden" id="module" name="module-id" value="">

<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Save changes</button>
</div>
</form>
</div>
</div>
</div>
</div>
21 changes: 12 additions & 9 deletions exercise/templatetags/exercise.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,25 @@ 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


@register.simple_tag
def get_goal(module, student):
try:
goal = StudentModuleGoal.objects.get(module_id=module, student_id=student)
return goal
except StudentModuleGoal.DoesNotExist:
return None

def _is_accessible(context, entry, t):
if t and t > _prepare_now(context):
return False
Expand Down Expand Up @@ -115,7 +118,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())
#print("StudentModuleGoalObjects", StudentModuleGoal.objects.all())
return values


Expand Down Expand Up @@ -221,6 +224,8 @@ def _points_data(
'unofficial_submission_type': getattr(obj, 'unofficial_submission_type', None),
'confirmable_points': getattr(obj, 'confirmable_points', False),
'feedback_revealed': getattr(obj, 'feedback_revealed', True),
'personalized_points_module_goal': getattr(obj, 'personalized_points_module_goal', None),
'personalized_points_module_goal_points': getattr(obj, 'personalized_points_module_goal_points', None),
}
reveal_time = getattr(obj, 'feedback_reveal_time', None)

Expand Down Expand Up @@ -259,10 +264,8 @@ 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]:
data = _points_data(obj, None)
data['personalized_points_goal'] = personalized_points_goal
return data


Expand Down
3 changes: 3 additions & 0 deletions exercise/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@
re_path(MODULE_URL_PREFIX + r'student_module_goal/$',
views.StudentModuleGoalView.as_view(),
name="student_module_goal"),
re_path(r'save_points_goal/$',
views.StudentModuleGoalView.save_points_goal,
name="save_points_goal"),
re_path(EDIT_URL_PREFIX + r'analytics/$',
staff_views.AnalyticsView.as_view(),
name="analytics"),
Expand Down
34 changes: 33 additions & 1 deletion exercise/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from lib.remote_page import RemotePageNotFound, request_for_response
from lib.viewbase import BaseFormView, BaseRedirectMixin, BaseView
from userprofile.models import UserProfile
from .cache.points import ExercisePoints
from .cache.points import CachedPoints, ExercisePoints, ModulePoints
from .models import BaseExercise, LearningObject, LearningObjectDisplay
from .protocol.exercise_page import ExercisePage
from .submission_models import SubmittedFile, Submission, SubmissionTagging
Expand Down Expand Up @@ -614,6 +614,38 @@ class StudentModuleGoalView(CourseModuleBaseView, BaseFormView):
template_name = "exercise/student_module_goal.html"
form_class = StudentModuleGoalForm

@csrf_exempt
def save_points_goal(request):
if request.method == 'POST':
points_goal: float = request.POST.get('points_goal')
user_id = request.POST.get('student')
module_id = request.POST.get('module-id')

student = UserProfile.objects.get(id=user_id)
module = CourseModule.objects.get(id=module_id)

cached_points = CachedPoints(module.course_instance, student, True)
cached_module, _, _, _ = cached_points.find(module)
print(cached_module.max_points)
# Points goal can either be an integer or percentage. If the the points goal is given as a percentage, give it to the points goal directly as an int
# Otherwise calculate the points goal by taking the integer and calculating how much that is of the module's max points
if '%' in points_goal:
points_goal = float(points_goal.replace('%', ''))
elif cached_module.max_points > 0:
points_goal = float(float(points_goal) / float(cached_module.max_points)) * 100.0
elif cached_module.max_points == 0:
points_goal = 0
print("POINTS GOAL SAVING", points_goal)
if points_goal > 100:
points_goal = 100
elif points_goal < 0:
points_goal = 0
StudentModuleGoal.objects.update_or_create(student=student, module=module, defaults={'personalized_points_goal_percentage': points_goal * 1.0})
print("STUDENT MODULE GOAL VALUE: ", StudentModuleGoal.objects.get(student=user_id, module=module_id).personalized_points_goal_percentage)
cached_points.invalidate(module.course_instance, student)
return redirect(request.META.get('HTTP_REFERER', '/'))
return HttpResponse('Invalid request', status=400)

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

Expand Down

0 comments on commit 9b61e00

Please sign in to comment.