Skip to content

Commit

Permalink
Merge pull request #566 from edx/dcs/no-update-due-date
Browse files Browse the repository at this point in the history
Use edx-when to handle due date extensions
  • Loading branch information
Dave St.Germain authored May 1, 2019
2 parents 815bdf2 + a71691d commit b730a19
Show file tree
Hide file tree
Showing 13 changed files with 195 additions and 165 deletions.
2 changes: 1 addition & 1 deletion edx_proctoring/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
from __future__ import absolute_import

# Be sure to update the version number in edx_proctoring/package.json
__version__ = '1.6.2'
__version__ = '1.7.1'

default_app_config = 'edx_proctoring.apps.EdxProctoringConfig' # pylint: disable=invalid-name
106 changes: 50 additions & 56 deletions edx_proctoring/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,56 +6,38 @@
"""
from __future__ import absolute_import

from datetime import datetime, timedelta
import logging
import uuid
from datetime import datetime, timedelta

import pytz
import six

from django.utils.translation import ugettext as _, ugettext_noop
from django.conf import settings
from django.contrib.auth import get_user_model
from django.template import loader
from django.urls import reverse, NoReverseMatch
from django.core.mail.message import EmailMessage
from django.template import loader
from django.urls import NoReverseMatch, reverse
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_noop

from edx_proctoring import constants
from edx_proctoring.exceptions import (
ProctoredExamAlreadyExists,
ProctoredExamNotFoundException,
StudentExamAttemptAlreadyExistsException,
StudentExamAttemptDoesNotExistsException,
StudentExamAttemptedAlreadyStarted,
ProctoredExamIllegalStatusTransition,
ProctoredExamPermissionDenied,
ProctoredExamNotActiveException,
ProctoredExamReviewPolicyNotFoundException,
ProctoredExamReviewPolicyAlreadyExists,
BackendProviderOnboardingException,
)
from edx_proctoring.models import (
ProctoredExam,
ProctoredExamStudentAllowance,
ProctoredExamStudentAttempt,
ProctoredExamReviewPolicy,
ProctoredExamSoftwareSecureReview,
)
from edx_proctoring.serializers import (
ProctoredExamSerializer,
ProctoredExamStudentAttemptSerializer,
ProctoredExamStudentAllowanceSerializer,
ProctoredExamReviewPolicySerializer
)
from edx_proctoring.statuses import ProctoredExamStudentAttemptStatus

from edx_proctoring.utils import (
humanized_time,
emit_event,
obscured_user_id,
)

from edx_proctoring.backends import get_backend_provider
from edx_proctoring.exceptions import (BackendProviderOnboardingException, ProctoredExamAlreadyExists,
ProctoredExamIllegalStatusTransition, ProctoredExamNotActiveException,
ProctoredExamNotFoundException, ProctoredExamPermissionDenied,
ProctoredExamReviewPolicyAlreadyExists,
ProctoredExamReviewPolicyNotFoundException,
StudentExamAttemptAlreadyExistsException,
StudentExamAttemptDoesNotExistsException, StudentExamAttemptedAlreadyStarted)
from edx_proctoring.models import (ProctoredExam, ProctoredExamReviewPolicy, ProctoredExamSoftwareSecureReview,
ProctoredExamStudentAllowance, ProctoredExamStudentAttempt)
from edx_proctoring.runtime import get_runtime_service
from edx_proctoring.serializers import (ProctoredExamReviewPolicySerializer, ProctoredExamSerializer,
ProctoredExamStudentAllowanceSerializer, ProctoredExamStudentAttemptSerializer)
from edx_proctoring.statuses import ProctoredExamStudentAttemptStatus
from edx_proctoring.utils import emit_event, humanized_time, obscured_user_id
from edx_when import api as when_api

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -540,11 +522,28 @@ def has_due_date_passed(due_datetime):
return False


def _was_review_status_acknowledged(is_status_acknowledged, due_datetime):
def get_exam_due_date(exam, user=None):
"""
Return the due date for the exam.
Uses edx_when to lookup the date for the subsection.
"""
due_date = when_api.get_date_for_block(exam['course_id'], exam['content_id'], 'due', user=user)
return due_date or exam['due_date']


def is_exam_passed_due(exam, user=None):
"""
Return whether the due date has passed.
Uses edx_when to lookup the date for the subsection.
"""
return has_due_date_passed(get_exam_due_date(exam, user=user))


def _was_review_status_acknowledged(is_status_acknowledged, exam):
"""
return True if review status has been acknowledged and due date has been passed
"""
return is_status_acknowledged and has_due_date_passed(due_datetime)
return is_status_acknowledged and is_exam_passed_due(exam)


def _create_and_decline_attempt(exam_id, user_id):
Expand Down Expand Up @@ -600,7 +599,7 @@ def create_exam_attempt(exam_id, user_id, taking_as_proctored=False):
review_policy_exception = ProctoredExamStudentAllowance.get_review_policy_exception(exam_id, user_id)
force_status = None

if not has_due_date_passed(exam['due_date']) and taking_as_proctored:
if not is_exam_passed_due(exam, user=user_id) and taking_as_proctored:
scheme = 'https' if getattr(settings, 'HTTPS', 'on') == 'on' else 'http'
lms_host = '{scheme}://{hostname}'.format(scheme=scheme, hostname=settings.SITE_NAME)

Expand Down Expand Up @@ -1607,7 +1606,7 @@ def _get_timed_exam_view(exam, context, exam_id, user_id, course_id):
attempt_status = attempt['status'] if attempt else None
has_due_date = True if exam['due_date'] is not None else False
if not attempt_status:
if has_due_date_passed(exam['due_date']):
if is_exam_passed_due(exam, user=user_id):
student_view_template = 'timed_exam/expired.html'
else:
student_view_template = 'timed_exam/entrance.html'
Expand All @@ -1620,7 +1619,7 @@ def _get_timed_exam_view(exam, context, exam_id, user_id, course_id):
# If we are not hiding the exam after the due_date has passed,
# check if the exam's due_date has passed. If so, return None
# so that the user can see their exam answers in read only mode.
if not exam['hide_after_due'] and has_due_date_passed(exam['due_date']):
if not exam['hide_after_due'] and is_exam_passed_due(exam, user=user_id):
return None

student_view_template = 'timed_exam/submitted.html'
Expand Down Expand Up @@ -1680,7 +1679,7 @@ def _calculate_allowed_mins(exam, user_id):
"""
Returns the allowed minutes w.r.t due date
"""
due_datetime = exam['due_date']
due_datetime = get_exam_due_date(exam, user_id)
allowed_time_limit_mins = exam['time_limit_mins']

# add in the allowed additional time
Expand Down Expand Up @@ -1724,7 +1723,7 @@ def _get_proctored_exam_context(exam, attempt, user_id, course_id, is_practice_e
'progress_page_url': progress_page_url,
'is_sample_attempt': is_practice_exam,
'has_due_date': has_due_date,
'has_due_date_passed': has_due_date_passed(exam['due_date']),
'has_due_date_passed': is_exam_passed_due(exam, user=user_id),
'does_time_remain': _does_time_remain(attempt),
'enter_exam_endpoint': reverse('edx_proctoring:proctored_exam.attempt.collection'),
'exam_started_poll_url': reverse(
Expand Down Expand Up @@ -1887,7 +1886,7 @@ def _get_proctored_exam_view(exam, context, exam_id, user_id, course_id):
})

# if exam due date has passed, then we can't take the exam
if has_due_date_passed(exam['due_date']):
if is_exam_passed_due(exam, user_id):
student_view_template = 'proctored_exam/expired.html'
elif not prerequisite_status['are_prerequisites_satisifed']:
# do we have any declined prerequisites, if so, then we
Expand Down Expand Up @@ -1938,24 +1937,24 @@ def _get_proctored_exam_view(exam, context, exam_id, user_id, course_id):
elif attempt_status == ProctoredExamStudentAttemptStatus.submitted:
student_view_template = None if _was_review_status_acknowledged(
attempt['is_status_acknowledged'],
exam['due_date']
exam
) else 'proctored_exam/submitted.html'
elif attempt_status == ProctoredExamStudentAttemptStatus.second_review_required:
# the student should still see a 'submitted'
# rendering even if the review needs a 2nd review
student_view_template = None if _was_review_status_acknowledged(
attempt['is_status_acknowledged'],
exam['due_date']
exam
) else 'proctored_exam/submitted.html'
elif attempt_status == ProctoredExamStudentAttemptStatus.verified:
student_view_template = None if _was_review_status_acknowledged(
attempt['is_status_acknowledged'],
exam['due_date']
exam
) else 'proctored_exam/verified.html'
elif attempt_status == ProctoredExamStudentAttemptStatus.rejected:
student_view_template = None if _was_review_status_acknowledged(
attempt['is_status_acknowledged'],
exam['due_date']
exam
) else 'proctored_exam/rejected.html'
elif attempt_status == ProctoredExamStudentAttemptStatus.ready_to_submit:
student_view_template = 'proctored_exam/ready_to_submit.html'
Expand Down Expand Up @@ -2003,13 +2002,8 @@ def get_student_view(user_id, course_id, content_id,
return None

# Just in case the due date has been changed because of the
# self-paced courses, use the due date from the context and
# update the local exam object if necessary.
# self-paced courses, use the due date from the context
if exam['due_date'] != context.get('due_date', None):
update_exam(
exam_id=exam['id'],
due_date=context.get('due_date', None)
)
exam['due_date'] = context.get('due_date', None)

exam_id = exam['id']
Expand Down
4 changes: 2 additions & 2 deletions edx_proctoring/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
remove_exam_attempt,
update_attempt_status,
update_exam_attempt,
has_due_date_passed,
is_exam_passed_due,
get_backend_provider,
mark_exam_attempt_as_ready,
)
Expand Down Expand Up @@ -591,7 +591,7 @@ def post(self, request):

# Bypassing the due date check for practice exam
# because student can attempt the practice after the due date
if not exam.get("is_practice_exam") and has_due_date_passed(exam.get('due_date')):
if not exam.get("is_practice_exam") and is_exam_passed_due(exam, request.user):
raise ProctoredExamPermissionDenied(
'Attempted to access expired exam with exam_id {exam_id}'.format(exam_id=exam_id)
)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@edx/edx-proctoring",
"//": "Be sure to update the version number in edx_proctoring/__init__.py",
"//": "Note that the version format is slightly different than that of the Python version when using prereleases.",
"version": "1.6.2",
"version": "1.7.1",
"main": "edx_proctoring/static/index.js",
"repository": {
"type": "git",
Expand Down
1 change: 1 addition & 0 deletions requirements/base.in
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ event-tracking>=0.2.5
edx-opaque-keys>=0.4
edx-drf-extensions
edx-rest-api-client>=1.9.2
edx-when>=0.1.3
44 changes: 28 additions & 16 deletions requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,52 @@
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --output-file requirements/base.txt requirements/base.in
# pip-compile --upgrade -o requirements/base.txt requirements/base.in
#
certifi==2018.11.29 # via requests
appdirs==1.4.3 # via fs
backports.os==0.1.1 # via fs
certifi==2019.3.9 # via requests
chardet==3.0.4 # via requests
django-crum==0.7.3
django-ipware==2.1.0
django-model-utils==3.1.2
django-waffle==0.15.1
django-waffle==0.16.0
django-webpack-loader==0.6.0
django==1.11.18
django==1.11.20
djangorestframework-jwt==1.11.0 # via edx-drf-extensions
djangorestframework==3.6.4
edx-django-utils==1.0.3 # via edx-drf-extensions
edx-drf-extensions==2.0.1
edx-drf-extensions==2.2.0
edx-opaque-keys==0.4.4
edx-rest-api-client==1.9.2
event-tracking==0.2.7
future==0.17.1 # via pyjwkest
edx-when==0.1.3
enum34==1.1.6 # via fs
event-tracking==0.2.8
fs==2.4.4 # via xblock
future==0.17.1 # via backports.os, pyjwkest
idna==2.8 # via requests
jsonfield==2.0.2
newrelic==4.12.0.113 # via edx-django-utils
pbr==5.1.2 # via stevedore
lxml==4.3.3 # via xblock
markupsafe==1.1.1 # via xblock
newrelic==4.18.0.118 # via edx-django-utils
pbr==5.2.0 # via stevedore
psutil==1.2.1 # via edx-django-utils, edx-drf-extensions
pycryptodomex==3.7.3
pycryptodomex==3.8.1
pyjwkest==1.3.2 # via edx-drf-extensions
pyjwt==1.7.1 # via djangorestframework-jwt, edx-rest-api-client
pymongo==3.7.2 # via edx-opaque-keys, event-tracking
python-dateutil==2.7.5
pytz==2018.9
pymongo==3.8.0 # via edx-opaque-keys, event-tracking
python-dateutil==2.8.0
pytz==2019.1
pyyaml==5.1 # via xblock
requests==2.21.0 # via edx-drf-extensions, edx-rest-api-client, pyjwkest, slumber
rest-condition==1.0.3 # via edx-drf-extensions
rules==2.0.1
semantic-version==2.6.0 # via edx-drf-extensions
six==1.12.0 # via edx-drf-extensions, edx-opaque-keys, pyjwkest, python-dateutil, stevedore
six==1.12.0 # via edx-drf-extensions, edx-opaque-keys, event-tracking, fs, pyjwkest, python-dateutil, stevedore, xblock
slumber==0.7.1 # via edx-rest-api-client
stevedore==1.30.0 # via edx-opaque-keys
urllib3==1.24.1 # via requests
stevedore==1.30.1 # via edx-opaque-keys
typing==3.6.6 # via fs
urllib3==1.24.2 # via requests
web-fragments==0.3.0 # via xblock
webob==1.8.5 # via xblock
xblock==1.2.2 # via edx-when
2 changes: 1 addition & 1 deletion requirements/dev.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ tox # virtualenv management for tests
tox-battery # Makes tox aware of requirements file changes
twine # Utility for PyPI package uploads
wheel # For generation of wheels for PyPI

path.py==11.5.0 # pinned because of python 3 incompatibility
django>=1.11,<2.0
Loading

0 comments on commit b730a19

Please sign in to comment.