Skip to content

Commit

Permalink
feat: add view for allowance creation
Browse files Browse the repository at this point in the history
  • Loading branch information
alangsto committed Jul 22, 2024
1 parent 4cf5d52 commit 6c8c32e
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 6 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,5 @@ docs/_build/

# Temp reports file
reports/

requirements/private.txt
50 changes: 46 additions & 4 deletions edx_exams/apps/api/v1/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@
from edx_exams.apps.api.test_utils import ExamsAPITestCase
from edx_exams.apps.core.exam_types import get_exam_type
from edx_exams.apps.core.exceptions import ExamAttemptOnPastDueExam, ExamIllegalStatusTransition
from edx_exams.apps.core.models import CourseExamConfiguration, CourseStaffRole, Exam, ExamAttempt, ProctoringProvider
from edx_exams.apps.core.models import (
CourseExamConfiguration,
CourseStaffRole,
Exam,
ExamAttempt,
ProctoringProvider,
StudentAllowance
)
from edx_exams.apps.core.statuses import ExamAttemptStatus
from edx_exams.apps.core.test_utils.factories import (
AssessmentControlResultFactory,
Expand Down Expand Up @@ -1768,18 +1775,21 @@ def setUp(self):
course_id=self.course_id,
)

def request_api(self, method, user, course_id):
def request_api(self, method, user, course_id, data=None):
"""
Helper function to make API request
"""
assert method in ['get']
assert method in ['get', 'post']
headers = self.build_jwt_headers(user)
url = reverse(
'api:v1:course-allowances',
kwargs={'course_id': course_id}
)

return getattr(self.client, method)(url, **headers)
if data:
return getattr(self.client, method)(url, json.dumps(data), **headers, content_type='application/json')
else:
return getattr(self.client, method)(url, **headers)

def test_auth_required(self):
"""
Expand Down Expand Up @@ -1851,3 +1861,35 @@ def test_get_empty_response(self):
response = self.request_api('get', self.user, 'course-v1:edx+no+allowances')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data, [])

def test_post_allowances(self):
"""
Test that the endpoint creates allowances for the given request data
"""
other_exam_in_course = ExamFactory.create(course_id=self.exam.course_id)
other_user = UserFactory()
StudentAllowanceFactory.create(
exam=self.exam,
user=self.user,
extra_time_mins=30,
)
StudentAllowanceFactory.create(
exam=other_exam_in_course,
user=self.user,
extra_time_mins=30,
)

request_data = [
{'exam_id': self.exam.id, 'username': self.user.username, 'extra_time_mins': 45},
{'exam_id': other_exam_in_course.id, 'username': self.user.username, 'extra_time_mins': 45},
{'exam_id': other_exam_in_course.id, 'username': other_user.username, 'extra_time_mins': 45},
]

response = self.request_api('post', self.user, self.exam.course_id, data=request_data)
self.assertEqual(response.status_code, 200)

course_allowances = StudentAllowance.objects.all()
self.assertEqual(len(course_allowances), 3)

self.assertEqual(len(StudentAllowance.objects.filter(user_id=self.user.id)), 2)
self.assertEqual(len(StudentAllowance.objects.filter(extra_time_mins=45)), 3)
45 changes: 43 additions & 2 deletions edx_exams/apps/api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,14 @@
update_attempt_status
)
from edx_exams.apps.core.exam_types import get_exam_type
from edx_exams.apps.core.models import CourseExamConfiguration, Exam, ExamAttempt, ProctoringProvider, StudentAllowance
from edx_exams.apps.core.models import (
CourseExamConfiguration,
Exam,
ExamAttempt,
ProctoringProvider,
StudentAllowance,
User
)
from edx_exams.apps.core.statuses import ExamAttemptStatus
from edx_exams.apps.router.interop import get_active_exam_attempt

Expand Down Expand Up @@ -784,13 +791,25 @@ def get(self, request, course_id, exam_id):

class AllowanceView(ExamsAPIView):
"""
Endpoint for getting allowances in a course
Endpoint for the StudentAllowance
/exams/course_id/{course_id}/allowances
Supports:
HTTP GET:
Returns a list of allowances for a course.
HTTP POST:
Create one or more allowances
Expected POST data: [{
"username": "test_user",
"exam_id": 1234,
"extra_time_mins": 30,
}]
**POST data Parameters**
* username: Username for which to create or update an allowance.
* exam_id: ID of the exam for which to create or update an allowance
* extra_time_mins: Extra time (in minutes) that a student is allotted for an exam.
"""

authentication_classes = (JwtAuthentication,)
Expand All @@ -802,3 +821,25 @@ def get(self, request, course_id):
"""
allowances = StudentAllowance.get_allowances_for_course(course_id)
return Response(AllowanceSerializer(allowances, many=True).data)

def post(self, request, course_id): # pylint: disable=unused-argument
"""
HTTP POST handler. Creates allowances based on the given list.
"""
allowances = request.data
allowance_objects = [
StudentAllowance(
user=User.objects.get(username=allowance['username']),
exam=Exam.objects.get(id=allowance['exam_id']),
extra_time_mins=allowance['extra_time_mins']
)
for allowance in allowances
]
StudentAllowance.objects.bulk_create(
allowance_objects,
update_conflicts=True,
unique_fields=['user', 'exam'],
update_fields=['extra_time_mins']
)

return Response(status=status.HTTP_200_OK)

0 comments on commit 6c8c32e

Please sign in to comment.