-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Expand file tree
/
Copy pathproctoring.py
More file actions
150 lines (127 loc) · 5.5 KB
/
Copy pathproctoring.py
File metadata and controls
150 lines (127 loc) · 5.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
"""
Code related to the handling of Proctored Exams in Studio
using the edx-proctoring plugin.
Courses using the exam IDA are handled by ./exams.py
"""
import logging
from django.conf import settings
from edx_proctoring.api import (
create_exam,
create_exam_review_policy,
get_all_exams_for_course,
get_exam_by_content_id,
remove_review_policy,
update_exam,
update_review_policy,
)
from edx_proctoring.exceptions import ProctoredExamNotFoundException, ProctoredExamReviewPolicyNotFoundException
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
from .helpers import is_item_in_course_tree
log = logging.getLogger(__name__)
def register_special_exams(course_key):
"""
This is typically called on a course published signal. The course is examined for sequences
that are marked as timed exams. Then these are registered with the edx-proctoring
subsystem. Likewise, if formerly registered exams are unmarked, then those
registered exams are marked as inactive
"""
if not getattr(settings, 'ENABLE_SPECIAL_EXAMS', False):
# if feature is not enabled then do a quick exit
return
course = modulestore().get_course(course_key)
if course is None:
raise ItemNotFoundError("Course {} does not exist", str(course_key)) # pylint: disable=raising-format-tuple
if not course.enable_proctored_exams and not course.enable_timed_exams:
# likewise if course does not have these features turned on
# then quickly exit
return
# get all sequences, since they can be marked as timed/proctored exams
_timed_exams = modulestore().get_items(
course_key,
qualifiers={
'category': 'sequential',
},
settings={
'is_time_limited': True,
}
)
# filter out any potential dangling sequences
timed_exams = [
timed_exam
for timed_exam in _timed_exams
if is_item_in_course_tree(timed_exam)
]
# enumerate over list of sequences which are time-limited and
# add/update any exam entries in edx-proctoring
for timed_exam in timed_exams:
msg = (
'Found {location} as a timed-exam in course structure. Inspecting...'.format( # noqa: UP032
location=str(timed_exam.location)
)
)
log.info(msg)
exam_metadata = {
'exam_name': timed_exam.display_name,
'time_limit_mins': timed_exam.default_time_limit_minutes,
'due_date': timed_exam.due if not course.self_paced else None,
'is_proctored': timed_exam.is_proctored_exam,
# backends that support onboarding exams will treat onboarding exams as practice
'is_practice_exam': timed_exam.is_practice_exam or timed_exam.is_onboarding_exam,
'is_active': True,
'hide_after_due': timed_exam.hide_after_due,
'backend': course.proctoring_provider,
}
try:
exam = get_exam_by_content_id(str(course_key), str(timed_exam.location))
# update case, make sure everything is synced
exam_metadata['exam_id'] = exam['id']
exam_id = update_exam(**exam_metadata)
msg = 'Updated timed exam {exam_id}'.format(exam_id=exam['id'])
log.info(msg)
except ProctoredExamNotFoundException:
exam_metadata['course_id'] = str(course_key)
exam_metadata['content_id'] = str(timed_exam.location)
exam_id = create_exam(**exam_metadata)
msg = f'Created new timed exam {exam_id}'
log.info(msg)
exam_review_policy_metadata = {
'exam_id': exam_id,
'set_by_user_id': timed_exam.edited_by,
'review_policy': timed_exam.exam_review_rules,
}
# only create/update exam policy for the proctored exams
if timed_exam.is_proctored_exam and not timed_exam.is_practice_exam and not timed_exam.is_onboarding_exam:
try:
update_review_policy(**exam_review_policy_metadata)
except ProctoredExamReviewPolicyNotFoundException:
if timed_exam.exam_review_rules: # won't save an empty rule.
create_exam_review_policy(**exam_review_policy_metadata)
msg = f'Created new exam review policy with exam_id {exam_id}'
log.info(msg)
else:
try:
# remove any associated review policy
remove_review_policy(exam_id=exam_id)
except ProctoredExamReviewPolicyNotFoundException:
pass
# then see which exams we have in edx-proctoring that are not in
# our current list. That means the the user has disabled it
exams = get_all_exams_for_course(course_key)
for exam in exams:
if exam['is_active']:
# try to look up the content_id in the sequences location
search = [
timed_exam for timed_exam in timed_exams if
str(timed_exam.location) == exam['content_id']
]
if not search:
# This means it was turned off in Studio, we need to mark
# the exam as inactive (we don't delete!)
msg = 'Disabling timed exam {exam_id}'.format(exam_id=exam['id'])
log.info(msg)
update_exam(
exam_id=exam['id'],
is_proctored=False,
is_active=False,
)