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
015c4c23
Commit
015c4c23
authored
Jun 25, 2015
by
Afzal Wali Naushahi
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #7 from edx/afzaledx/phx-39
Afzaledx/phx 39
parents
54591dd5
26532648
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
280 additions
and
14 deletions
+280
-14
edx_proctoring/models.py
+7
-7
edx_proctoring/urls.py
+18
-3
edx_proctoring/views.py
+255
-4
No files found.
edx_proctoring/models.py
View file @
015c4c23
...
@@ -23,16 +23,16 @@ class ProctoredExam(TimeStampedModel):
...
@@ -23,16 +23,16 @@ class ProctoredExam(TimeStampedModel):
# This will be a integration specific ID - say to SoftwareSecure.
# This will be a integration specific ID - say to SoftwareSecure.
external_id
=
models
.
TextField
(
null
=
True
,
db_index
=
True
)
external_id
=
models
.
TextField
(
null
=
True
,
db_index
=
True
)
# This is the display name of the
course
# This is the display name of the
Exam (Midterm etc).
exam_name
=
models
.
TextField
()
exam_name
=
models
.
TextField
()
# Time limit (in minutes) that a student can finish this exam
# Time limit (in minutes) that a student can finish this exam
.
time_limit_mins
=
models
.
IntegerField
()
time_limit_mins
=
models
.
IntegerField
()
# Whether this exam actually is proctored or not
# Whether this exam actually is proctored or not
.
is_proctored
=
models
.
BooleanField
()
is_proctored
=
models
.
BooleanField
()
#
This will be a integration specific ID - say to SoftwareSecur
e.
#
Whether this exam will be activ
e.
is_active
=
models
.
BooleanField
()
is_active
=
models
.
BooleanField
()
class
Meta
:
class
Meta
:
...
@@ -119,7 +119,7 @@ class ProctoredExamStudentAttempt(TimeStampedModel):
...
@@ -119,7 +119,7 @@ class ProctoredExamStudentAttempt(TimeStampedModel):
class
QuerySetWithUpdateOverride
(
models
.
query
.
QuerySet
):
class
QuerySetWithUpdateOverride
(
models
.
query
.
QuerySet
):
"""
"""
Custom QuerySet class to
send the POST_UPDATE_SIGNAL
Custom QuerySet class to
make an archive copy
every time the object is updated.
every time the object is updated.
"""
"""
def
update
(
self
,
**
kwargs
):
def
update
(
self
,
**
kwargs
):
...
@@ -130,7 +130,7 @@ class QuerySetWithUpdateOverride(models.query.QuerySet):
...
@@ -130,7 +130,7 @@ class QuerySetWithUpdateOverride(models.query.QuerySet):
class
ProctoredExamStudentAllowanceManager
(
models
.
Manager
):
class
ProctoredExamStudentAllowanceManager
(
models
.
Manager
):
"""
"""
Custom manager to override with the custom queryset
Custom manager to override with the custom queryset
to enable
the POST_UPDATE_SIGNAL
to enable
archiving on Allowance updation.
"""
"""
def
get_query_set
(
self
):
def
get_query_set
(
self
):
return
QuerySetWithUpdateOverride
(
self
.
model
,
using
=
self
.
_db
)
return
QuerySetWithUpdateOverride
(
self
.
model
,
using
=
self
.
_db
)
...
@@ -197,7 +197,7 @@ class ProctoredExamStudentAllowanceHistory(TimeStampedModel):
...
@@ -197,7 +197,7 @@ class ProctoredExamStudentAllowanceHistory(TimeStampedModel):
value
=
models
.
CharField
(
max_length
=
255
)
value
=
models
.
CharField
(
max_length
=
255
)
# Hook up the
custom POST_UPDATE_SIGNAL signal to record upd
ations in the ProctoredExamStudentAllowanceHistory table.
# Hook up the
post_save signal to record cre
ations in the ProctoredExamStudentAllowanceHistory table.
@receiver
(
post_save
,
sender
=
ProctoredExamStudentAllowance
)
@receiver
(
post_save
,
sender
=
ProctoredExamStudentAllowance
)
def
on_allowance_saved
(
sender
,
instance
,
created
,
**
kwargs
):
# pylint: disable=unused-argument
def
on_allowance_saved
(
sender
,
instance
,
created
,
**
kwargs
):
# pylint: disable=unused-argument
"""
"""
...
...
edx_proctoring/urls.py
View file @
015c4c23
...
@@ -8,9 +8,24 @@ from django.conf.urls import patterns, url, include
...
@@ -8,9 +8,24 @@ from django.conf.urls import patterns, url, include
urlpatterns
=
patterns
(
# pylint: disable=invalid-name
urlpatterns
=
patterns
(
# pylint: disable=invalid-name
''
,
''
,
url
(
url
(
r'edx_proctoring/v1/proctored_exam/status$'
,
r'edx_proctoring/v1/proctored_exam/exam'
,
views
.
StudentProctoredExamStatus
.
as_view
(),
views
.
ProctoredExamView
.
as_view
(),
name
=
'edx_proctoring.proctored_exam.status'
name
=
'edx_proctoring.proctored_exam.exam'
),
url
(
r'edx_proctoring/v1/proctored_exam/attempt$'
,
views
.
StudentProctoredExamAttempt
.
as_view
(),
name
=
'edx_proctoring.proctored_exam.attempt'
),
url
(
r'edx_proctoring/v1/proctored_exam/allowance'
,
views
.
ExamAllowanceView
.
as_view
(),
name
=
'edx_proctoring.proctored_exam.allowance'
),
url
(
r'edx_proctoring/v1/proctored_exam/active_exams_for_user'
,
views
.
ActiveExamsForUserView
.
as_view
(),
name
=
'edx_proctoring.proctored_exam.active_exams_for_user'
),
),
url
(
r'^'
,
include
(
'rest_framework.urls'
,
namespace
=
'rest_framework'
))
url
(
r'^'
,
include
(
'rest_framework.urls'
,
namespace
=
'rest_framework'
))
)
)
edx_proctoring/views.py
View file @
015c4c23
...
@@ -3,23 +3,177 @@ Proctored Exams HTTP-based API endpoints
...
@@ -3,23 +3,177 @@ Proctored Exams HTTP-based API endpoints
"""
"""
import
logging
import
logging
from
django.db
import
IntegrityError
from
django.db.models
import
Model
from
rest_framework
import
status
from
rest_framework
import
status
from
rest_framework.response
import
Response
from
rest_framework.response
import
Response
from
edx_proctoring.api
import
create_exam
,
update_exam
,
get_exam_by_id
,
get_exam_by_content_id
,
start_exam_attempt
,
\
stop_exam_attempt
,
add_allowance_for_user
,
remove_allowance_for_user
,
get_active_exams_for_user
from
.utils
import
AuthenticatedAPIView
from
.utils
import
AuthenticatedAPIView
LOG
=
logging
.
getLogger
(
"edx_proctoring_views"
)
LOG
=
logging
.
getLogger
(
"edx_proctoring_views"
)
class
StudentProctoredExamStatus
(
AuthenticatedAPIView
):
class
ProctoredExamView
(
AuthenticatedAPIView
):
"""
"""
Returns the status of the proctored exam.
Endpoint for the Proctored Exams
/edx_proctoring/v1/proctored_exam/exam
Supports:
HTTP POST: Creates a new Exam.
HTTP PUT: Updates an existing Exam.
HTTP GET: Returns an existing exam (by id or by content id)
HTTP POST
Creates a new Exam.
Expected POST data: {
"course_id": "edX/DemoX/Demo_Course",
"content_id": 123,
"exam_name": "Midterm",
"time_limit_mins": "90",
"is_proctored": true,
"external_id": "",
"is_active": true,
}
**POST data Parameters**
* course_id: The unique identifier for the course.
* content_id: This will be the pointer to the id of the piece of course_ware which is the proctored exam.
* exam_name: This is the display name of the Exam (Midterm etc).
* time_limit_mins: Time limit (in minutes) that a student can finish this exam.
* is_proctored: Whether this exam actually is proctored or not.
* external_id: This will be a integration specific ID - say to SoftwareSecure.
* is_active: Whether this exam will be active.
**Response Values**
* {'exam_id': ##}, The exam_id of the created Proctored Exam.
**Exceptions**
* HTTP_400_BAD_REQUEST, data={"message": "Trying to create a duplicate exam."}
HTTP PUT
Updates an existing Exam.
PUT data : {
"exam_id": 533,
"exam_name": "Final",
"time_limit_mins": 120,
"is_proctored": true,
"external_id": 235
"is_active": true
}
**PUT data Parameters**
see the POST data parameters
**Response Values**
* {'exam_id': ##}, The exam_id of the created Proctored Exam.
HTTP GET
** Scenarios **
?exam_id=533
returns an existing exam object matching the exam_id
?course_id=edX/DemoX/Demo_Course&content_id=123
returns an existing exam object matching the course_id and the content_id
"""
def
post
(
self
,
request
):
"""
Http POST handler. Creates an exam.
"""
try
:
exam_id
=
create_exam
(
course_id
=
request
.
DATA
.
get
(
'course_id'
,
""
),
content_id
=
request
.
DATA
.
get
(
'content_id'
,
""
),
exam_name
=
request
.
DATA
.
get
(
'exam_name'
,
""
),
time_limit_mins
=
request
.
DATA
.
get
(
'time_limit_mins'
,
""
),
is_proctored
=
True
if
request
.
DATA
.
get
(
'is_proctored'
,
"False"
)
.
lower
()
==
'true'
else
False
,
external_id
=
request
.
DATA
.
get
(
'external_id'
,
""
),
is_active
=
True
if
request
.
DATA
.
get
(
'is_active'
,
""
)
.
lower
()
==
'true'
else
False
,
)
return
Response
({
'exam_id'
:
exam_id
})
except
IntegrityError
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"detail"
:
"Trying to create a duplicate exam."
}
)
def
put
(
self
,
request
):
"""
HTTP PUT handler. To update an exam.
calls the update_exam
"""
try
:
exam_id
=
update_exam
(
exam_id
=
request
.
DATA
.
get
(
'exam_id'
,
""
),
exam_name
=
request
.
DATA
.
get
(
'exam_name'
,
""
),
time_limit_mins
=
request
.
DATA
.
get
(
'time_limit_mins'
,
""
),
is_proctored
=
True
if
request
.
DATA
.
get
(
'is_proctored'
,
"False"
)
.
lower
()
==
'true'
else
False
,
external_id
=
request
.
DATA
.
get
(
'external_id'
,
""
),
is_active
=
True
if
request
.
DATA
.
get
(
'is_active'
,
""
)
.
lower
()
==
'true'
else
False
,
)
return
Response
({
'exam_id'
:
exam_id
})
except
Model
.
DoesNotExist
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"detail"
:
"The exam_id does not exist."
}
)
def
get
(
self
,
request
):
"""
HTTP GET handler.
Scenarios:
by exam_id: calls get_exam_by_id()
by course_id, content_id: get_exam_by_content_id()
"""
course_id
=
request
.
QUERY_PARAMS
.
get
(
'course_id'
,
None
)
content_id
=
request
.
QUERY_PARAMS
.
get
(
'content_id'
,
None
)
exam_id
=
request
.
QUERY_PARAMS
.
get
(
'exam_id'
,
None
)
if
exam_id
:
try
:
return
Response
(
data
=
get_exam_by_id
(
exam_id
),
status
=
status
.
HTTP_200_OK
)
except
Model
.
DoesNotExist
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"detail"
:
"The exam_id does not exist."
}
)
elif
course_id
is
not
None
and
content_id
is
not
None
:
try
:
return
Response
(
data
=
get_exam_by_content_id
(
course_id
,
content_id
),
status
=
status
.
HTTP_200_OK
)
except
Model
.
DoesNotExist
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"detail"
:
"The exam with course_id, content_id does not exist."
}
)
else
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"detail"
:
"Bad input data."
}
)
class
StudentProctoredExamAttempt
(
AuthenticatedAPIView
):
"""
Endpoint for the StudentProctoredExamAttempt
/edx_proctoring/v1/proctored_exam/exam
"""
"""
def
get
(
self
,
request
):
# pylint: disable=unused-argument
def
get
(
self
,
request
):
# pylint: disable=unused-argument
"""
"""
HTTP GET Handler
HTTP GET Handler
. Returns the status of the exam attempt.
"""
"""
response_dict
=
{
response_dict
=
{
...
@@ -32,4 +186,101 @@ class StudentProctoredExamStatus(AuthenticatedAPIView):
...
@@ -32,4 +186,101 @@ class StudentProctoredExamStatus(AuthenticatedAPIView):
'critically_low_threshold'
:
15
,
'critically_low_threshold'
:
15
,
}
}
return
Response
(
response_dict
,
status
=
status
.
HTTP_200_OK
)
return
Response
(
data
=
response_dict
,
status
=
status
.
HTTP_200_OK
)
def
post
(
self
,
request
):
"""
HTTP POST handler. To start an exam.
"""
try
:
exam_attempt_id
=
start_exam_attempt
(
exam_id
=
request
.
DATA
.
get
(
'exam_id'
,
""
),
user_id
=
request
.
DATA
.
get
(
'user_id'
,
""
),
external_id
=
request
.
DATA
.
get
(
'external_id'
,
""
)
)
return
Response
({
'exam_attempt_id'
:
exam_attempt_id
})
except
Exception
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"detail"
:
"Exam already started."
}
)
def
put
(
self
,
request
):
"""
HTTP POST handler. To stop an exam.
"""
try
:
exam_attempt_id
=
stop_exam_attempt
(
exam_id
=
request
.
DATA
.
get
(
'exam_id'
,
""
),
user
=
request
.
DATA
.
get
(
'user_id'
,
""
)
)
return
Response
()
except
Exception
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"detail"
:
"Exam Not in progress."
}
)
class
ExamAllowanceView
(
AuthenticatedAPIView
):
"""
"""
def
put
(
self
,
request
):
"""
HTTP GET handler. Adds or updates Allowance
"""
try
:
return
Response
(
add_allowance_for_user
(
exam_id
=
request
.
DATA
.
get
(
'exam_id'
,
""
),
user_id
=
request
.
DATA
.
get
(
'user_id'
,
""
),
key
=
request
.
DATA
.
get
(
'key'
,
""
),
value
=
request
.
DATA
.
get
(
'value'
,
""
)
))
except
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"detail"
:
"Could not add Allowance."
}
)
def
delete
(
self
,
request
):
"""
HTTP DELETE handler. Removes Allowance.
"""
try
:
return
Response
(
remove_allowance_for_user
(
exam_id
=
request
.
DATA
.
get
(
'exam_id'
,
""
),
user_id
=
request
.
DATA
.
get
(
'user_id'
,
""
),
key
=
request
.
DATA
.
get
(
'key'
,
""
)
))
except
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"detail"
:
"Could not remove Allowance."
}
)
class
ActiveExamsForUserView
(
AuthenticatedAPIView
):
"""
"""
def
get
(
self
,
request
):
"""
returns the get_active_exams_for_user
"""
try
:
return
Response
(
get_active_exams_for_user
(
user_id
=
request
.
DATA
.
get
(
'user_id'
,
""
),
course_id
=
request
.
DATA
.
get
(
'course_id'
,
""
)
))
except
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"detail"
:
"Error."
}
)
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