Commit 6cde76c7 by chrisndodge

Merge pull request #107 from edx/cdodge/performance-improvement

Do the pagination before doing serialization, also refactor out the A…
parents 61ade7f9 6b1f5801
......@@ -142,3 +142,27 @@ class ProctoredExamStudentAttemptTests(LoggedInTestCase):
attempt_history = ProctoredExamStudentAttemptHistory.objects.filter(user_id=1)
self.assertEqual(len(attempt_history), 1)
def test_get_exam_attempts(self):
"""
Test to get all the exam attempts for a course
"""
# Create an exam.
proctored_exam = ProctoredExam.objects.create(
course_id='a/b/c',
content_id='test_content',
exam_name='Test Exam',
external_id='123aXqe3',
time_limit_mins=90
)
# create number of exam attempts
for i in range(90):
ProctoredExamStudentAttempt.create_exam_attempt(
proctored_exam.id, i, 'test_name{0}'.format(i), i + 1,
'test_attempt_code{0}'.format(i), True, False, 'test_external_id{0}'.format(i)
)
with self.assertNumQueries(1):
exam_attempts = ProctoredExamStudentAttempt.objects.get_all_exam_attempts('a/b/c')
self.assertEqual(len(exam_attempts), 90)
......@@ -793,20 +793,23 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
)
attempt_data = {
'exam_id': proctored_exam.id,
'user_id': self.student_taking_exam.id,
'external_id': proctored_exam.external_id
}
response = self.client.post(
reverse('edx_proctoring.proctored_exam.attempt.collection'),
attempt_data
)
url = reverse('edx_proctoring.proctored_exam.attempt', kwargs={'course_id': proctored_exam.course_id})
url = reverse('edx_proctoring.proctored_exam.attempts.course', kwargs={'course_id': proctored_exam.course_id})
self.assertEqual(response.status_code, 200)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
self.assertEqual(len(response_data['proctored_exam_attempts']), 1)
attempt = response_data['proctored_exam_attempts'][0]
self.assertEqual(attempt['proctored_exam']['id'], proctored_exam.id)
self.assertEqual(attempt['user']['id'], self.user.id)
url = '{url}?page={invalid_page_no}'.format(url=url, invalid_page_no=9999)
# url with the invalid page # still gives us the first page result.
response = self.client.get(url)
......@@ -835,7 +838,7 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
reverse('edx_proctoring.proctored_exam.attempt.collection'),
attempt_data
)
url = reverse('edx_proctoring.proctored_exam.attempt', kwargs={'course_id': proctored_exam.course_id})
url = reverse('edx_proctoring.proctored_exam.attempts.course', kwargs={'course_id': proctored_exam.course_id})
self.user.is_staff = False
self.user.save()
......@@ -878,7 +881,7 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
self.client.login_user(self.user)
response = self.client.get(
reverse(
'edx_proctoring.proctored_exam.attempt.search',
'edx_proctoring.proctored_exam.attempts.search',
kwargs={
'course_id': proctored_exam.course_id,
'search_by': 'tester'
......@@ -889,6 +892,50 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
response_data = json.loads(response.content)
self.assertEqual(len(response_data['proctored_exam_attempts']), 2)
attempt = response_data['proctored_exam_attempts'][0]
self.assertEqual(attempt['proctored_exam']['id'], proctored_exam.id)
self.assertEqual(attempt['user']['id'], self.second_user.id)
attempt = response_data['proctored_exam_attempts'][1]
self.assertEqual(attempt['proctored_exam']['id'], proctored_exam.id)
self.assertEqual(attempt['user']['id'], self.user.id)
def test_paginated_exam_attempts(self):
"""
Test to get the paginated exam attempts in a course.
"""
# Create an exam.
proctored_exam = ProctoredExam.objects.create(
course_id='a/b/c',
content_id='test_content',
exam_name='Test Exam',
external_id='123aXqe3',
time_limit_mins=90
)
# create number of exam attempts
for i in range(90):
ProctoredExamStudentAttempt.create_exam_attempt(
proctored_exam.id, i, 'test_name{0}'.format(i), i + 1,
'test_attempt_code{0}'.format(i), True, False, 'test_external_id{0}'.format(i)
)
self.client.login_user(self.user)
response = self.client.get(
reverse(
'edx_proctoring.proctored_exam.attempts.course',
kwargs={
'course_id': proctored_exam.course_id
}
)
)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
self.assertEqual(len(response_data['proctored_exam_attempts']), 25)
self.assertTrue(response_data['pagination_info']['has_next'])
self.assertEqual(response_data['pagination_info']['total_pages'], 4)
self.assertEqual(response_data['pagination_info']['current_page'], 1)
def test_stop_others_attempt(self):
"""
......
......@@ -37,14 +37,14 @@ urlpatterns = patterns( # pylint: disable=invalid-name
),
url(
r'edx_proctoring/v1/proctored_exam/attempt/course_id/{}$'.format(settings.COURSE_ID_PATTERN),
views.StudentProctoredExamAttemptCollection.as_view(),
name='edx_proctoring.proctored_exam.attempt'
views.StudentProctoredExamAttemptsByCourse.as_view(),
name='edx_proctoring.proctored_exam.attempts.course'
),
url(
r'edx_proctoring/v1/proctored_exam/attempt/course_id/{}/search/(?P<search_by>.+)$'.format(
settings.COURSE_ID_PATTERN),
views.StudentProctoredExamAttemptCollection.as_view(),
name='edx_proctoring.proctored_exam.attempt.search'
views.StudentProctoredExamAttemptsByCourse.as_view(),
name='edx_proctoring.proctored_exam.attempts.search'
),
url(
r'edx_proctoring/v1/proctored_exam/attempt$',
......
......@@ -27,9 +27,7 @@ from edx_proctoring.api import (
get_allowances_for_course,
get_all_exams_for_course,
get_exam_attempt_by_id,
get_all_exam_attempts,
remove_exam_attempt,
get_filtered_exam_attempts,
update_attempt_status
)
from edx_proctoring.exceptions import (
......@@ -39,8 +37,8 @@ from edx_proctoring.exceptions import (
ProctoredExamPermissionDenied,
StudentExamAttemptDoesNotExistsException,
)
from edx_proctoring.serializers import ProctoredExamSerializer
from edx_proctoring.models import ProctoredExamStudentAttemptStatus
from edx_proctoring.serializers import ProctoredExamSerializer, ProctoredExamStudentAttemptSerializer
from edx_proctoring.models import ProctoredExamStudentAttemptStatus, ProctoredExamStudentAttempt
from .utils import AuthenticatedAPIView
......@@ -438,53 +436,10 @@ class StudentProctoredExamAttemptCollection(AuthenticatedAPIView):
return the status of the exam attempt
"""
def get(self, request, course_id=None, search_by=None): # pylint: disable=unused-argument
def get(self, request): # pylint: disable=unused-argument
"""
HTTP GET Handler. Returns the status of the exam attempt.
"""
if course_id is not None:
#
# This code path is only for authenticated global staff users
#
if not request.user.is_staff:
return Response(
status=status.HTTP_403_FORBIDDEN,
data={"detail": "Must be a Staff User to Perform this request."}
)
if search_by is not None:
exam_attempts = get_filtered_exam_attempts(course_id, search_by)
attempt_url = reverse('edx_proctoring.proctored_exam.attempt.search', args=[course_id, search_by])
else:
exam_attempts = get_all_exam_attempts(course_id)
attempt_url = reverse('edx_proctoring.proctored_exam.attempt', args=[course_id])
paginator = Paginator(exam_attempts, ATTEMPTS_PER_PAGE)
page = request.GET.get('page')
try:
exam_attempts_page = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
exam_attempts_page = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
exam_attempts_page = paginator.page(paginator.num_pages)
data = {
'proctored_exam_attempts': exam_attempts_page.object_list,
'pagination_info': {
'has_previous': exam_attempts_page.has_previous(),
'has_next': exam_attempts_page.has_next(),
'current_page': exam_attempts_page.number,
'total_pages': exam_attempts_page.paginator.num_pages,
},
'attempt_url': attempt_url
}
return Response(
data=data,
status=status.HTTP_200_OK
)
exams = get_active_exams_for_user(request.user.id)
......@@ -583,6 +538,57 @@ class StudentProctoredExamAttemptCollection(AuthenticatedAPIView):
)
class StudentProctoredExamAttemptsByCourse(AuthenticatedAPIView):
"""
This endpoint is called by the Instructor Dashboard to get
paginated attempts in a course
A search parameter is optional
"""
@method_decorator(require_staff)
def get(self, request, course_id, search_by=None): # pylint: disable=unused-argument
"""
HTTP GET Handler. Returns the status of the exam attempt.
"""
if search_by is not None:
exam_attempts = ProctoredExamStudentAttempt.objects.get_filtered_exam_attempts(course_id, search_by)
attempt_url = reverse('edx_proctoring.proctored_exam.attempts.search', args=[course_id, search_by])
else:
exam_attempts = ProctoredExamStudentAttempt.objects.get_all_exam_attempts(course_id)
attempt_url = reverse('edx_proctoring.proctored_exam.attempts.course', args=[course_id])
paginator = Paginator(exam_attempts, ATTEMPTS_PER_PAGE)
page = request.GET.get('page')
try:
exam_attempts_page = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
exam_attempts_page = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
exam_attempts_page = paginator.page(paginator.num_pages)
data = {
'proctored_exam_attempts': [
ProctoredExamStudentAttemptSerializer(attempt).data for
attempt in exam_attempts_page.object_list
],
'pagination_info': {
'has_previous': exam_attempts_page.has_previous(),
'has_next': exam_attempts_page.has_next(),
'current_page': exam_attempts_page.number,
'total_pages': exam_attempts_page.paginator.num_pages,
},
'attempt_url': attempt_url
}
return Response(
data=data,
status=status.HTTP_200_OK
)
class ExamAllowanceView(AuthenticatedAPIView):
"""
Endpoint for the Exam Allowance
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment