diff --git a/exercise/cache/points.py b/exercise/cache/points.py index dfe1dc469..1d7639b9d 100644 --- a/exercise/cache/points.py +++ b/exercise/cache/points.py @@ -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 @@ -865,6 +865,7 @@ class ModulePoints(DifficultyStats, ModuleEntryBase[LearningObjectPoints]): _children_unconfirmed: bool is_model_answer_revealed: bool confirmable_children: bool + personalized_points_module_goal: Optional[int] children_unconfirmed = RevealableAttribute[bool]() @@ -919,7 +920,7 @@ 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.instance = precreated.get_or_create_proxy( CachedPointsData, *self.instance._params, user_id, modifiers=self._modifiers ) @@ -940,6 +941,15 @@ 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 + 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(): @@ -948,7 +958,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 @@ -966,6 +977,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") diff --git a/exercise/static/exercise/css/goal_points.css b/exercise/static/exercise/css/goal_points.css new file mode 100644 index 000000000..bd97741ce --- /dev/null +++ b/exercise/static/exercise/css/goal_points.css @@ -0,0 +1,5 @@ +.progress .goal-points { + position: absolute; + height: 20px; + border-left: 1px solid blue; +} diff --git a/exercise/templates/exercise/_points_progress.html b/exercise/templates/exercise/_points_progress.html index 77121b29b..24f6eea32 100644 --- a/exercise/templates/exercise/_points_progress.html +++ b/exercise/templates/exercise/_points_progress.html @@ -1,18 +1,21 @@ {% load i18n %} +{% load static %} {% if not feedback_revealed %}

{% translate "RESULTS_OF_SOME_ASSIGNMENTS_ARE_CURRENTLY_HIDDEN" %}

{% endif %} -Personalized {{personalized_points_goal}} -
-
{% if required_percentage %}
{% endif %} + {% if personalized_points_module_goal %} + +
+ {% endif %}
diff --git a/exercise/templates/exercise/_user_results.html b/exercise/templates/exercise/_user_results.html index 8085d1051..eab75d0b7 100644 --- a/exercise/templates/exercise/_user_results.html +++ b/exercise/templates/exercise/_user_results.html @@ -2,7 +2,7 @@ {% load static %} {% load course %} {% load exercise %} - +{% include 'exercise/points_goal_modal.html' %} {% if categories|len_listed > 1 %} @@ -45,11 +45,6 @@ aria-expanded="{% if accessible and not after_open or open %}true{% else %}false{% endif %}" aria-controls="#module{{ module.id }}">

{% 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 %} {% translate "OPENS ON" %} {{ module.opening_time }} @@ -109,8 +104,15 @@

{% endif %}

- 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 %} +
+ + {% points_progress module %} + {% endwith %} + {{ module.introduction|safe }} {% if student %} {% adddeviationsbutton instance module submitters=student %} @@ -282,3 +284,16 @@

startListen("{{course|safe}}"); }, false); + + \ No newline at end of file diff --git a/exercise/templates/exercise/points_goal_modal.html b/exercise/templates/exercise/points_goal_modal.html new file mode 100644 index 000000000..8b87ab9be --- /dev/null +++ b/exercise/templates/exercise/points_goal_modal.html @@ -0,0 +1,28 @@ + + \ No newline at end of file diff --git a/exercise/templatetags/exercise.py b/exercise/templatetags/exercise.py index 7158e0587..1def0e3fc 100644 --- a/exercise/templatetags/exercise.py +++ b/exercise/templatetags/exercise.py @@ -53,11 +53,15 @@ 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 + + # 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()) - + course_module = context.get('course_module') + if course_module: + context["personalized_points_goal"] = StudentModuleGoal.objects.filter(module=course_module) + else: + context["personalized_points_goal"] = StudentModuleGoal.objects.none() + context = context.flatten() context.update({ 'modules': points.modules_flatted(), @@ -69,6 +73,14 @@ def _get_toc(context, student=None): 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 @@ -115,7 +127,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 @@ -221,6 +233,7 @@ 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), } reveal_time = getattr(obj, 'feedback_reveal_time', None) @@ -259,10 +272,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 diff --git a/exercise/urls.py b/exercise/urls.py index 8cd74f05b..75b7e7471 100644 --- a/exercise/urls.py +++ b/exercise/urls.py @@ -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"), diff --git a/exercise/views.py b/exercise/views.py index afb97a319..b88e5dfd5 100644 --- a/exercise/views.py +++ b/exercise/views.py @@ -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 @@ -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 = 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 = int(points_goal.replace('%', '')) + elif cached_module.max_points > 0: + points_goal = float(float(points_goal) / float(cached_module.max_points)) * 100 + elif cached_module.max_points == 0: + points_goal = 0 + + 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': points_goal}) + print(StudentModuleGoal.objects.get(student=user_id, module=module_id).personalized_points_goal) + 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()