diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index fb8ce7ffd08e..0d6f33fcaf99 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -141,7 +141,9 @@ class CapaFields(object): student_answers = Dict(help="Dictionary with the current student responses", scope=Scope.user_state) done = Boolean(help="Whether the student has answered the problem", scope=Scope.user_state) seed = Integer(help="Random seed for this student", scope=Scope.user_state) - + minutes_allowed = Integer(help="EXPERIMENTAL FEATURE: DO NOT USE. Number of minutes allowed to finish this assessment. Set 0 for no time-limit", + default=0, scope=Scope.settings) + time_started = Date(help="time student started this assessment", scope=Scope.user_state) last_submission_time = Date(help="Last submission time", scope=Scope.user_state) submission_wait_seconds = Integer( display_name="Timer Between Attempts", @@ -311,6 +313,12 @@ def set_state_from_lcp(self): self.student_answers = lcp_state['student_answers'] self.seed = lcp_state['seed'] + def set_time_started(self): + """ + Sets the time when the student started the module. + """ + self.time_started = datetime.datetime.now(UTC()) + def set_last_submission_time(self): """ Set the module's last submission time (when the problem was checked) @@ -551,6 +559,20 @@ def get_problem_html(self, encapsulate=True): else: check_button = False + problem_is_timed = self.minutes_allowed > 0 + + if problem_is_timed and not self.time_started: + self.set_time_started() + + end_time_to_display = (self.time_started + datetime.timedelta(minutes=self.minutes_allowed) + if problem_is_timed + else None) + + # because we use self.due and not self.close_date below, this is not the actual end_time, but the + # end_time we want to display to the user + if self.due and end_time_to_display: + end_time_to_display = min(self.due, end_time_to_display) + content = {'name': self.display_name_with_default, 'html': html, 'weight': self.weight, @@ -558,6 +580,9 @@ def get_problem_html(self, encapsulate=True): context = {'problem': content, 'id': self.id, + 'problem_is_timed': problem_is_timed, + 'start_time': self.time_started, + 'end_time_to_display': end_time_to_display, 'check_button': check_button, 'reset_button': self.should_show_reset_button(), 'save_button': self.should_show_save_button(), @@ -642,6 +667,17 @@ def handle_ajax(self, dispatch, data): return json.dumps(result, cls=ComplexEncoder) + def exceeded_time_limit(self): + """ + Has student used up allotted time, if set + """ + if self.minutes_allowed <= 0 or not self.time_started: + return False + now = datetime.datetime.now(UTC()) + # built in hardcoded grace period of 5 min + time_limit_end = self.time_started + datetime.timedelta(minutes=(self.minutes_allowed + 5)) + return now > time_limit_end + def is_past_due(self): """ Is it now past this problem's due date, including grace period? @@ -657,7 +693,8 @@ def closed(self): return True if self.is_past_due(): return True - + if self.exceeded_time_limit(): + return True return False def is_submitted(self): diff --git a/lms/templates/problem.html b/lms/templates/problem.html index b8156cb14f48..7c580bca90fa 100644 --- a/lms/templates/problem.html +++ b/lms/templates/problem.html @@ -1,4 +1,8 @@ -<%! from django.utils.translation import ugettext as _ %> +<%! +from django.utils.translation import ugettext as _ +from xmodule.util.date_utils import get_time_display +from django.conf import settings +%> <%namespace name='static' file='static_content.html'/>

@@ -8,6 +12,20 @@

+% if problem_is_timed: +
+

+ ${_("At {start_time}, you started this exam.").format( + start_time=get_time_display(start_time, coerce_tz=settings.TIME_ZONE))} +
+ + ${_("By {end_time}, you must manually click the 'Final Submit' button below!").format( + end_time=get_time_display(end_time_to_display, coerce_tz=settings.TIME_ZONE))} + +

+
+% endif +
${ problem['html'] }