-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add patch endpoint to update exams (#16)
- Loading branch information
Showing
24 changed files
with
828 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
""" Permissions for edx-exams API""" | ||
from rest_framework.permissions import BasePermission | ||
|
||
|
||
class StaffUserPermissions(BasePermission): | ||
""" Permission class to check if user is staff """ | ||
|
||
def has_permission(self, request, view): | ||
return request.user.is_staff |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,43 @@ | ||
# Serializers that can be shared across multiple versions of the API | ||
# should be created here. As the API evolves, serializers may become more | ||
# specific to a particular version of the API. In this case, the serializers | ||
# in question should be moved to versioned sub-package. | ||
""" | ||
Serializers for the edx-exams API | ||
""" | ||
from rest_framework import serializers | ||
|
||
from edx_exams.apps.core.exam_types import EXAM_TYPES | ||
from edx_exams.apps.core.models import Exam | ||
|
||
|
||
class ExamSerializer(serializers.ModelSerializer): | ||
""" | ||
Serializer for the Exam Model | ||
""" | ||
|
||
exam_name = serializers.CharField(required=True) | ||
course_id = serializers.CharField(required=False) | ||
content_id = serializers.CharField(required=True) | ||
time_limit_mins = serializers.IntegerField(required=True) | ||
due_date = serializers.DateTimeField(required=False, format=None) | ||
exam_type = serializers.CharField(required=True) | ||
hide_after_due = serializers.BooleanField(required=True) | ||
is_active = serializers.BooleanField(required=True) | ||
|
||
class Meta: | ||
""" | ||
Meta Class | ||
""" | ||
|
||
model = Exam | ||
|
||
fields = ( | ||
"id", "exam_name", "course_id", "content_id", "time_limit_mins", "due_date", "exam_type", | ||
"hide_after_due", "is_active", | ||
) | ||
|
||
def validate_exam_type(self, value): | ||
""" | ||
Validate that exam_type is one of the predefined choices | ||
""" | ||
valid_exam_types = [exam_type.name for exam_type in EXAM_TYPES] | ||
if value not in valid_exam_types: | ||
raise serializers.ValidationError("Must be a valid exam type.") | ||
return value |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
""" | ||
Test Utilities | ||
""" | ||
|
||
from rest_framework.test import APIClient, APITestCase | ||
|
||
from edx_exams.apps.api.test_utils.factories import UserFactory | ||
from edx_exams.apps.api.test_utils.mixins import JwtMixin | ||
from edx_exams.apps.core.models import ProctoringProvider | ||
|
||
TEST_USERNAME = 'api_worker' | ||
TEST_EMAIL = '[email protected]' | ||
TEST_PASSWORD = 'QWERTY' | ||
|
||
|
||
class ExamsAPITestCase(JwtMixin, APITestCase): | ||
""" | ||
Base class for API Tests | ||
""" | ||
|
||
def setUp(self): | ||
""" | ||
Perform operations common to all tests. | ||
""" | ||
super().setUp() | ||
self.create_user(username=TEST_USERNAME, email=TEST_EMAIL, password=TEST_PASSWORD, is_staff=True) | ||
self.client = APIClient() | ||
self.client.login(username=TEST_USERNAME, password=TEST_PASSWORD) | ||
|
||
self.test_provider = ProctoringProvider.objects.create( | ||
name='test_provider', | ||
verbose_name='testing_provider', | ||
lti_configuration_id='123456789' | ||
) | ||
|
||
def tearDown(self): | ||
""" | ||
Perform common tear down operations to all tests. | ||
""" | ||
# Remove client authentication credentials | ||
self.client.logout() | ||
super().tearDown() | ||
|
||
def create_user(self, username=TEST_USERNAME, password=TEST_PASSWORD, is_staff=False, **kwargs): | ||
""" | ||
Create a test user and set its password. | ||
""" | ||
self.user = UserFactory(username=username, is_active=True, is_staff=is_staff, **kwargs) | ||
self.user.set_password(password) | ||
self.user.save() | ||
|
||
def build_jwt_headers(self, user): | ||
""" | ||
Set jwt token in cookies. | ||
""" | ||
jwt_payload = self.default_payload(user) | ||
jwt_token = self.generate_token(jwt_payload) | ||
headers = {"HTTP_AUTHORIZATION": "JWT " + jwt_token} | ||
return headers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
""" | ||
Factories for exams tests | ||
""" | ||
|
||
import factory | ||
from django.contrib.auth import get_user_model | ||
from factory.django import DjangoModelFactory | ||
|
||
|
||
class UserFactory(DjangoModelFactory): | ||
""" | ||
Factory to create users to be used in other unit tests | ||
""" | ||
|
||
class Meta: | ||
model = get_user_model() | ||
django_get_or_create = ( | ||
"email", | ||
"username", | ||
) | ||
|
||
_DEFAULT_PASSWORD = "test" | ||
|
||
username = factory.Sequence("user{}".format) | ||
email = factory.Sequence("user+test+{}@edx.org".format) | ||
password = factory.PostGenerationMethodCall("set_password", _DEFAULT_PASSWORD) | ||
first_name = factory.Sequence("User{}".format) | ||
last_name = "Test" | ||
is_superuser = False | ||
is_staff = False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
""" | ||
Mixins for edx-exams API tests. | ||
""" | ||
from time import time | ||
|
||
import jwt | ||
from django.conf import settings | ||
|
||
JWT_AUTH = "JWT_AUTH" | ||
|
||
|
||
class JwtMixin: | ||
""" | ||
Mixin with JWT-related helper functions | ||
""" | ||
|
||
JWT_SECRET_KEY = getattr(settings, JWT_AUTH)["JWT_SECRET_KEY"] | ||
JWT_ISSUER = getattr(settings, JWT_AUTH)["JWT_ISSUER"] | ||
JWT_AUDIENCE = getattr(settings, JWT_AUTH)["JWT_AUDIENCE"] | ||
|
||
def generate_token(self, payload, secret=None): | ||
""" | ||
Generate a JWT token with the provided payload | ||
""" | ||
secret = secret or self.JWT_SECRET_KEY | ||
token = jwt.encode(payload, secret) | ||
return token | ||
|
||
def default_payload(self, user, ttl=1): | ||
""" | ||
Generate a bare payload, in case tests need to manipulate | ||
it directly before encoding | ||
""" | ||
now = int(time()) | ||
|
||
return { | ||
"iss": self.JWT_ISSUER, | ||
"sub": user.pk, | ||
"aud": self.JWT_AUDIENCE, | ||
"nonce": "dummy-nonce", | ||
"exp": now + ttl, | ||
"iat": now, | ||
"preferred_username": user.username, | ||
"administrator": user.is_staff, | ||
"email": user.email, | ||
"locale": "en", | ||
"name": user.full_name, | ||
"given_name": "", | ||
"family_name": "", | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,5 +10,5 @@ | |
|
||
app_name = 'api' | ||
urlpatterns = [ | ||
path(r'^v1/', include(v1_urls)), | ||
path('v1/', include(v1_urls)), | ||
] |
Oops, something went wrong.