Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-proctoring
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenEdx
edx-proctoring
Commits
54591dd5
Commit
54591dd5
authored
Jun 25, 2015
by
Muhammad Shoaib
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
PHX-38 added the api level functionality
parent
e577b8a6
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
354 additions
and
1 deletions
+354
-1
edx_proctoring/api.py
+64
-1
edx_proctoring/exceptions.py
+24
-0
edx_proctoring/models.py
+77
-0
edx_proctoring/tests/test_api.py
+189
-0
No files found.
edx_proctoring/api.py
View file @
54591dd5
...
@@ -6,6 +6,14 @@
...
@@ -6,6 +6,14 @@
In-Proc API (aka Library) for the edx_proctoring subsystem. This is not to be confused with a HTTP REST
In-Proc API (aka Library) for the edx_proctoring subsystem. This is not to be confused with a HTTP REST
API which is in the views.py file, per edX coding standards
API which is in the views.py file, per edX coding standards
"""
"""
import
pytz
from
datetime
import
datetime
from
edx_proctoring.exceptions
import
(
ProctoredExamAlreadyExist
,
ProctoredExamNotFoundException
,
StudentExamAttemptAlreadyExistException
)
from
edx_proctoring.models
import
(
ProctoredExam
,
ProctoredExamStudentAllowance
,
ProctoredExamStudentAttempt
)
def
create_exam
(
course_id
,
content_id
,
exam_name
,
time_limit_mins
,
def
create_exam
(
course_id
,
content_id
,
exam_name
,
time_limit_mins
,
...
@@ -16,6 +24,19 @@ def create_exam(course_id, content_id, exam_name, time_limit_mins,
...
@@ -16,6 +24,19 @@ def create_exam(course_id, content_id, exam_name, time_limit_mins,
Returns: id (PK)
Returns: id (PK)
"""
"""
if
ProctoredExam
.
get_exam_by_content_id
(
course_id
,
content_id
)
is
not
None
:
raise
ProctoredExamAlreadyExist
proctored_exam
=
ProctoredExam
.
objects
.
create
(
course_id
=
course_id
,
content_id
=
content_id
,
external_id
=
external_id
,
exam_name
=
exam_name
,
time_limit_mins
=
time_limit_mins
,
is_proctored
=
is_proctored
,
is_active
=
is_active
)
return
proctored_exam
def
update_exam
(
exam_id
,
exam_name
=
None
,
time_limit_mins
=
None
,
def
update_exam
(
exam_id
,
exam_name
=
None
,
time_limit_mins
=
None
,
...
@@ -26,6 +47,22 @@ def update_exam(exam_id, exam_name=None, time_limit_mins=None,
...
@@ -26,6 +47,22 @@ def update_exam(exam_id, exam_name=None, time_limit_mins=None,
Returns: id
Returns: id
"""
"""
proctored_exam
=
ProctoredExam
.
get_exam_by_id
(
exam_id
)
if
proctored_exam
is
None
:
raise
ProctoredExamNotFoundException
if
exam_name
is
not
None
:
proctored_exam
.
exam_name
=
exam_name
if
time_limit_mins
is
not
None
:
proctored_exam
.
time_limit_mins
=
time_limit_mins
if
is_proctored
is
not
None
:
proctored_exam
.
is_proctored
=
is_proctored
if
external_id
is
not
None
:
proctored_exam
.
external_id
=
external_id
if
is_active
is
not
None
:
proctored_exam
.
is_active
=
is_active
proctored_exam
.
save
()
return
proctored_exam
def
get_exam_by_id
(
exam_id
):
def
get_exam_by_id
(
exam_id
):
...
@@ -34,6 +71,11 @@ def get_exam_by_id(exam_id):
...
@@ -34,6 +71,11 @@ def get_exam_by_id(exam_id):
Returns dictionary version of the Django ORM object
Returns dictionary version of the Django ORM object
"""
"""
proctored_exam
=
ProctoredExam
.
get_exam_by_id
(
exam_id
)
if
proctored_exam
is
None
:
raise
ProctoredExamNotFoundException
return
proctored_exam
.
__dict__
def
get_exam_by_content_id
(
course_id
,
content_id
):
def
get_exam_by_content_id
(
course_id
,
content_id
):
...
@@ -42,18 +84,27 @@ def get_exam_by_content_id(course_id, content_id):
...
@@ -42,18 +84,27 @@ def get_exam_by_content_id(course_id, content_id):
Returns dictionary version of the Django ORM object
Returns dictionary version of the Django ORM object
"""
"""
proctored_exam
=
ProctoredExam
.
get_exam_by_content_id
(
course_id
,
content_id
)
if
proctored_exam
is
None
:
raise
ProctoredExamNotFoundException
return
proctored_exam
.
__dict__
def
add_allowance_for_user
(
exam_id
,
user_id
,
key
,
value
):
def
add_allowance_for_user
(
exam_id
,
user_id
,
key
,
value
):
"""
"""
Adds (or updates) an allowance for a user within a given exam
Adds (or updates) an allowance for a user within a given exam
"""
"""
ProctoredExamStudentAllowance
.
add_allowance_for_user
(
exam_id
,
user_id
,
key
,
value
)
def
remove_allowance_for_user
(
exam_id
,
user_id
,
key
):
def
remove_allowance_for_user
(
exam_id
,
user_id
,
key
):
"""
"""
Deletes an allowance for a user within a given exam.
Deletes an allowance for a user within a given exam.
"""
"""
student_allowance
=
ProctoredExamStudentAllowance
.
get_allowance_for_user
(
exam_id
,
user_id
,
key
)
if
student_allowance
is
not
None
:
student_allowance
.
delete
()
def
start_exam_attempt
(
exam_id
,
user_id
,
external_id
):
def
start_exam_attempt
(
exam_id
,
user_id
,
external_id
):
...
@@ -63,12 +114,24 @@ def start_exam_attempt(exam_id, user_id, external_id):
...
@@ -63,12 +114,24 @@ def start_exam_attempt(exam_id, user_id, external_id):
Returns: exam_attempt_id (PK)
Returns: exam_attempt_id (PK)
"""
"""
exam_attempt_obj
=
ProctoredExamStudentAttempt
.
start_exam_attempt
(
exam_id
,
user_id
,
external_id
)
if
exam_attempt_obj
is
None
:
raise
StudentExamAttemptAlreadyExistException
else
:
return
exam_attempt_obj
def
stop_exam_attempt
(
exam_id
,
user
):
def
stop_exam_attempt
(
exam_id
,
user
_id
):
"""
"""
Marks the exam attempt as completed (sets the completed_at field and updates the record)
Marks the exam attempt as completed (sets the completed_at field and updates the record)
"""
"""
exam_attempt_obj
=
ProctoredExamStudentAttempt
.
get_student_exam_attempt
(
exam_id
,
user_id
)
if
exam_attempt_obj
is
None
:
raise
StudentExamAttemptAlreadyExistException
else
:
exam_attempt_obj
.
completed_at
=
datetime
.
now
(
pytz
.
UTC
)
exam_attempt_obj
.
save
()
return
exam_attempt_obj
def
get_active_exams_for_user
(
user_id
,
course_id
=
None
):
def
get_active_exams_for_user
(
user_id
,
course_id
=
None
):
...
...
edx_proctoring/exceptions.py
0 → 100644
View file @
54591dd5
"""
Specialized exceptions for the Notification subsystem
"""
class
ProctoredExamAlreadyExist
(
Exception
):
"""
Generic exception when a look up fails. Since we are abstracting away the backends
we need to catch any native exceptions and re-throw as a generic exception
"""
class
ProctoredExamNotFoundException
(
Exception
):
"""
Generic exception when a look up fails. Since we are abstracting away the backends
we need to catch any native exceptions and re-throw as a generic exception
"""
class
StudentExamAttemptAlreadyExistException
(
Exception
):
"""
Generic exception when a look up fails. Since we are abstracting away the backends
we need to catch any native exceptions and re-throw as a generic exception
"""
edx_proctoring/models.py
View file @
54591dd5
"""
"""
Data models for the proctoring subsystem
Data models for the proctoring subsystem
"""
"""
import
pytz
from
datetime
import
datetime
from
django.db
import
models
from
django.db
import
models
from
django.db.models.signals
import
post_save
,
pre_delete
from
django.db.models.signals
import
post_save
,
pre_delete
from
django.dispatch
import
receiver
from
django.dispatch
import
receiver
...
@@ -37,6 +39,30 @@ class ProctoredExam(TimeStampedModel):
...
@@ -37,6 +39,30 @@ class ProctoredExam(TimeStampedModel):
""" Meta class for this Django model """
""" Meta class for this Django model """
unique_together
=
((
'course_id'
,
'content_id'
),)
unique_together
=
((
'course_id'
,
'content_id'
),)
@classmethod
def
get_exam_by_content_id
(
cls
,
course_id
,
content_id
):
"""
Returns the Proctored Exam if found else returns None,
Given course_id and content_id
"""
try
:
proctored_exam
=
cls
.
objects
.
get
(
course_id
=
course_id
,
content_id
=
content_id
)
except
cls
.
DoesNotExist
:
proctored_exam
=
None
return
proctored_exam
@classmethod
def
get_exam_by_id
(
cls
,
exam_id
):
"""
Returns the Proctored Exam if found else returns None,
Given exam_id (PK)
"""
try
:
proctored_exam
=
cls
.
objects
.
get
(
id
=
exam_id
)
except
cls
.
DoesNotExist
:
proctored_exam
=
None
return
proctored_exam
class
ProctoredExamStudentAttempt
(
TimeStampedModel
):
class
ProctoredExamStudentAttempt
(
TimeStampedModel
):
"""
"""
...
@@ -62,6 +88,34 @@ class ProctoredExamStudentAttempt(TimeStampedModel):
...
@@ -62,6 +88,34 @@ class ProctoredExamStudentAttempt(TimeStampedModel):
""" returns boolean if this attempt is considered active """
""" returns boolean if this attempt is considered active """
return
self
.
started_at
and
not
self
.
completed_at
return
self
.
started_at
and
not
self
.
completed_at
@classmethod
def
start_exam_attempt
(
cls
,
exam_id
,
user_id
,
external_id
):
"""
create and return an exam attempt entry for a given
exam_id. If one already exists, then returns None.
"""
if
cls
.
get_student_exam_attempt
(
exam_id
,
user_id
)
is
None
:
return
cls
.
objects
.
create
(
proctored_exam
=
exam_id
,
user_id
=
user_id
,
external_id
=
external_id
,
started_at
=
datetime
.
now
(
pytz
.
UTC
)
)
else
:
return
None
@classmethod
def
get_student_exam_attempt
(
cls
,
exam_id
,
user_id
):
"""
Returns the Student Exam Attempt object if found
else Returns None.
"""
try
:
exam_attempt_obj
=
cls
.
objects
.
get
(
proctored_exam
=
exam_id
,
user_id
=
user_id
)
except
cls
.
DoesNotExist
:
exam_attempt_obj
=
None
return
exam_attempt_obj
class
QuerySetWithUpdateOverride
(
models
.
query
.
QuerySet
):
class
QuerySetWithUpdateOverride
(
models
.
query
.
QuerySet
):
"""
"""
...
@@ -101,6 +155,29 @@ class ProctoredExamStudentAllowance(TimeStampedModel):
...
@@ -101,6 +155,29 @@ class ProctoredExamStudentAllowance(TimeStampedModel):
""" Meta class for this Django model """
""" Meta class for this Django model """
unique_together
=
((
'user_id'
,
'proctored_exam'
,
'key'
),)
unique_together
=
((
'user_id'
,
'proctored_exam'
,
'key'
),)
@classmethod
def
get_allowance_for_user
(
cls
,
exam_id
,
user_id
,
key
):
"""
Returns an allowance for a user within a given exam
"""
try
:
student_allowance
=
cls
.
objects
.
get
(
proctored_exam
=
exam_id
,
user_id
=
user_id
,
key
=
key
)
except
cls
.
DoesNotExist
:
student_allowance
=
None
return
student_allowance
@classmethod
def
add_allowance_for_user
(
cls
,
exam_id
,
user_id
,
key
,
value
):
"""
Add or (Update) an allowance for a user within a given exam
"""
try
:
student_allowance
=
cls
.
objects
.
get
(
proctored_exam
=
exam_id
,
user_id
=
user_id
,
key
=
key
)
student_allowance
.
value
=
value
student_allowance
.
save
()
except
cls
.
DoesNotExist
:
cls
.
objects
.
create
(
proctored_exam
=
exam_id
,
user_id
=
user_id
,
key
=
key
,
value
=
value
)
class
ProctoredExamStudentAllowanceHistory
(
TimeStampedModel
):
class
ProctoredExamStudentAllowanceHistory
(
TimeStampedModel
):
"""
"""
...
...
edx_proctoring/tests/test_api.py
0 → 100644
View file @
54591dd5
"""
All tests for the models.py
"""
from
datetime
import
datetime
import
pytz
from
edx_proctoring.api
import
create_exam
,
update_exam
,
get_exam_by_id
,
get_exam_by_content_id
,
add_allowance_for_user
,
\
remove_allowance_for_user
,
start_exam_attempt
,
stop_exam_attempt
from
edx_proctoring.exceptions
import
ProctoredExamAlreadyExist
,
ProctoredExamNotFoundException
,
\
StudentExamAttemptAlreadyExistException
from
edx_proctoring.models
import
ProctoredExam
,
ProctoredExamStudentAllowance
,
ProctoredExamStudentAllowanceHistory
,
\
ProctoredExamStudentAttempt
from
.utils
import
(
LoggedInTestCase
)
class
ProctoredExamApiTests
(
LoggedInTestCase
):
"""
All tests for the models.py
"""
def
setUp
(
self
):
"""
Build out test harnessing
"""
super
(
ProctoredExamApiTests
,
self
)
.
setUp
()
self
.
default_time_limit
=
21
self
.
course_id
=
'test_course'
self
.
content_id
=
'test_content_id'
self
.
exam_name
=
'Test Exam'
self
.
user_id
=
1
self
.
key
=
'Test Key'
self
.
value
=
'Test Value'
self
.
external_id
=
'test_external_id'
def
_create_proctored_exam
(
self
):
return
create_exam
(
course_id
=
self
.
course_id
,
content_id
=
self
.
content_id
,
exam_name
=
self
.
exam_name
,
time_limit_mins
=
self
.
default_time_limit
)
def
_create_student_exam_attempt_entry
(
self
):
proctored_exam
=
self
.
_create_proctored_exam
()
return
ProctoredExamStudentAttempt
.
objects
.
create
(
proctored_exam
=
proctored_exam
,
user_id
=
self
.
user_id
,
external_id
=
self
.
external_id
,
started_at
=
datetime
.
now
(
pytz
.
UTC
)
)
def
_add_allowance_for_user
(
self
):
proctored_exam
=
self
.
_create_proctored_exam
()
return
ProctoredExamStudentAllowance
.
objects
.
create
(
proctored_exam
=
proctored_exam
,
user_id
=
self
.
user_id
,
key
=
self
.
key
,
value
=
self
.
value
)
def
test_create_exam
(
self
):
"""
Test to create a proctored exam.
"""
proctored_exam
=
self
.
_create_proctored_exam
()
self
.
assertIsNotNone
(
proctored_exam
)
def
test_create_already_existing_exam_throws_exception
(
self
):
"""
Test to create a proctored exam that has already exist in the
database and will throw an exception ProctoredExamAlreadyExist.
"""
ProctoredExam
.
objects
.
create
(
course_id
=
'test_course'
,
content_id
=
'test_content_id'
,
external_id
=
'test_external_id'
,
exam_name
=
'Test Exam'
,
time_limit_mins
=
21
,
is_proctored
=
True
,
is_active
=
True
)
with
self
.
assertRaises
(
ProctoredExamAlreadyExist
):
self
.
_create_proctored_exam
()
def
test_update_proctored_exam
(
self
):
"""
test update the existing proctored exam
"""
proctored_exam
=
self
.
_create_proctored_exam
()
update_proctored_exam
=
update_exam
(
proctored_exam
.
id
,
exam_name
=
'Updated Exam Name'
,
time_limit_mins
=
30
,
is_proctored
=
True
,
external_id
=
'external_id'
,
is_active
=
True
)
# only those fields were updated, whose
# values are passed.
self
.
assertEqual
(
update_proctored_exam
.
exam_name
,
'Updated Exam Name'
)
self
.
assertEqual
(
update_proctored_exam
.
time_limit_mins
,
30
)
self
.
assertEqual
(
update_proctored_exam
.
course_id
,
'test_course'
)
self
.
assertEqual
(
update_proctored_exam
.
content_id
,
'test_content_id'
)
def
test_update_non_existing_proctored_exam
(
self
):
"""
test to update the non-existing proctored exam
which will throw the exception
"""
with
self
.
assertRaises
(
ProctoredExamNotFoundException
):
update_exam
(
1
,
exam_name
=
'Updated Exam Name'
,
time_limit_mins
=
30
)
def
test_get_proctored_exam
(
self
):
"""
test to get the exam by the exam_id and
then compare their values.
"""
proctored_exam
=
self
.
_create_proctored_exam
()
proctored_exam
=
get_exam_by_id
(
proctored_exam
.
id
)
self
.
assertEqual
(
proctored_exam
[
'course_id'
],
self
.
course_id
)
self
.
assertEqual
(
proctored_exam
[
'content_id'
],
self
.
content_id
)
self
.
assertEqual
(
proctored_exam
[
'exam_name'
],
self
.
exam_name
)
proctored_exam
=
get_exam_by_content_id
(
self
.
course_id
,
self
.
content_id
)
self
.
assertEqual
(
proctored_exam
[
'course_id'
],
self
.
course_id
)
self
.
assertEqual
(
proctored_exam
[
'content_id'
],
self
.
content_id
)
self
.
assertEqual
(
proctored_exam
[
'exam_name'
],
self
.
exam_name
)
def
test_get_invalid_proctored_exam
(
self
):
"""
test to get the exam by the invalid exam_id which will
raises exception
"""
with
self
.
assertRaises
(
ProctoredExamNotFoundException
):
get_exam_by_id
(
1
)
with
self
.
assertRaises
(
ProctoredExamNotFoundException
):
get_exam_by_content_id
(
'teasd'
,
'tewasda'
)
def
test_add_allowance_for_user
(
self
):
proctored_exam
=
self
.
_create_proctored_exam
()
add_allowance_for_user
(
proctored_exam
,
self
.
user_id
,
self
.
key
,
self
.
value
)
student_allowance
=
ProctoredExamStudentAllowance
.
get_allowance_for_user
(
proctored_exam
.
id
,
self
.
user_id
,
self
.
key
)
self
.
assertIsNotNone
(
student_allowance
)
def
test_allowance_for_user_already_exists
(
self
):
student_allowance
=
self
.
_add_allowance_for_user
()
add_allowance_for_user
(
student_allowance
.
proctored_exam
,
self
.
user_id
,
self
.
key
,
'new_value'
)
student_allowance
=
ProctoredExamStudentAllowance
.
get_allowance_for_user
(
student_allowance
.
proctored_exam
.
id
,
self
.
user_id
,
self
.
key
)
self
.
assertIsNotNone
(
student_allowance
)
self
.
assertEqual
(
student_allowance
.
value
,
'new_value'
)
def
test_get_allowance_for_user_does_not_exist
(
self
):
proctored_exam
=
self
.
_create_proctored_exam
()
student_allowance
=
ProctoredExamStudentAllowance
.
get_allowance_for_user
(
proctored_exam
.
id
,
self
.
user_id
,
self
.
key
)
self
.
assertIsNone
(
student_allowance
)
def
test_remove_allowance_for_user
(
self
):
student_allowance
=
self
.
_add_allowance_for_user
()
self
.
assertEqual
(
len
(
ProctoredExamStudentAllowance
.
objects
.
filter
()),
1
)
remove_allowance_for_user
(
student_allowance
.
proctored_exam
.
id
,
self
.
user_id
,
self
.
key
)
self
.
assertEqual
(
len
(
ProctoredExamStudentAllowance
.
objects
.
filter
()),
0
)
def
test_student_exam_attempt_entry_already_exists
(
self
):
proctored_exam
=
self
.
_create_proctored_exam
()
start_exam_attempt
(
proctored_exam
,
self
.
user_id
,
self
.
external_id
)
self
.
assertIsNotNone
(
start_exam_attempt
)
def
test_create_student_exam_attempt_entry
(
self
):
proctored_exam_student_attempt
=
self
.
_create_student_exam_attempt_entry
()
with
self
.
assertRaises
(
StudentExamAttemptAlreadyExistException
):
start_exam_attempt
(
proctored_exam_student_attempt
.
proctored_exam
,
self
.
user_id
,
self
.
external_id
)
def
test_stop_exam_attempt
(
self
):
proctored_exam_student_attempt
=
self
.
_create_student_exam_attempt_entry
()
self
.
assertIsNone
(
proctored_exam_student_attempt
.
completed_at
)
proctored_exam_student_attempt
=
stop_exam_attempt
(
proctored_exam_student_attempt
.
proctored_exam
,
self
.
user_id
)
self
.
assertIsNotNone
(
proctored_exam_student_attempt
.
completed_at
)
def
test_stop_invalid_exam_attempt_raises_exception
(
self
):
proctored_exam
=
self
.
_create_proctored_exam
()
with
self
.
assertRaises
(
StudentExamAttemptAlreadyExistException
):
stop_exam_attempt
(
proctored_exam
,
self
.
user_id
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment