{"action":"created","comment":{"url":"https://api.github.com/repos/edx/edx-proctoring/pulls/comments/35043748","id":35043748,"diff_hunk":"@@ -0,0 +1,558 @@\n+# pylint: disable=unused-argument\n+\n+# remove pylint rule after we implement each method\n+\n+\"\"\"\n+In-Proc API (aka Library) for the edx_proctoring subsystem. This is not to be confused with a HTTP REST\n+API which is in the views.py file, per edX coding standards\n+\"\"\"\n+import pytz\n+import uuid\n+from datetime import datetime, timedelta\n+from django.conf import settings\n+from django.template import Context, loader\n+from django.core.urlresolvers import reverse, NoReverseMatch\n+\n+from edx_proctoring.exceptions import (\n+    ProctoredExamAlreadyExists,\n+    ProctoredExamNotFoundException,\n+    StudentExamAttemptAlreadyExistsException,\n+    StudentExamAttemptDoesNotExistsException,\n+    StudentExamAttemptedAlreadyStarted,\n+)\n+from edx_proctoring.models import (\n+    ProctoredExam,\n+    ProctoredExamStudentAllowance,\n+    ProctoredExamStudentAttempt,\n+    ProctoredExamStudentAttemptStatus,\n+)\n+from edx_proctoring.serializers import (\n+    ProctoredExamSerializer,\n+    ProctoredExamStudentAttemptSerializer,\n+    ProctoredExamStudentAllowanceSerializer,\n+)\n+from edx_proctoring.utils import humanized_time\n+\n+from edx_proctoring.backends import get_backend_provider\n+\n+\n+def is_feature_enabled():\n+    \"\"\"\n+    Returns if this feature has been enabled in our FEATURE flags\n+    \"\"\"\n+\n+    return hasattr(settings, 'FEATURES') and settings.FEATURES.get('ENABLE_PROCTORED_EXAMS', False)\n+\n+\n+def create_exam(course_id, content_id, exam_name, time_limit_mins,\n+                is_proctored=True, external_id=None, is_active=True):\n+    \"\"\"\n+    Creates a new ProctoredExam entity, if the course_id/content_id pair do not already exist.\n+    If that pair already exists, then raise exception.\n+\n+    Returns: id (PK)\n+    \"\"\"\n+    if ProctoredExam.get_exam_by_content_id(course_id, content_id) is not None:\n+        raise ProctoredExamAlreadyExists\n+\n+    proctored_exam = ProctoredExam.objects.create(\n+        course_id=course_id,\n+        content_id=content_id,\n+        external_id=external_id,\n+        exam_name=exam_name,\n+        time_limit_mins=time_limit_mins,\n+        is_proctored=is_proctored,\n+        is_active=is_active\n+    )\n+    return proctored_exam.id\n+\n+\n+def update_exam(exam_id, exam_name=None, time_limit_mins=None,\n+                is_proctored=None, external_id=None, is_active=None):\n+    \"\"\"\n+    Given a Django ORM id, update the existing record, otherwise raise exception if not found.\n+    If an argument is not passed in, then do not change it's current value.\n+\n+    Returns: id\n+    \"\"\"\n+    proctored_exam = ProctoredExam.get_exam_by_id(exam_id)\n+    if proctored_exam is None:\n+        raise ProctoredExamNotFoundException\n+\n+    if exam_name is not None:\n+        proctored_exam.exam_name = exam_name\n+    if time_limit_mins is not None:\n+        proctored_exam.time_limit_mins = time_limit_mins\n+    if is_proctored is not None:\n+        proctored_exam.is_proctored = is_proctored\n+    if external_id is not None:\n+        proctored_exam.external_id = external_id\n+    if is_active is not None:\n+        proctored_exam.is_active = is_active\n+    proctored_exam.save()\n+    return proctored_exam.id\n+\n+\n+def get_exam_by_id(exam_id):\n+    \"\"\"\n+    Looks up exam by the Primary Key. Raises exception if not found.\n+\n+    Returns dictionary version of the Django ORM object\n+    e.g.\n+    {\n+        \"course_id\": \"edX/DemoX/Demo_Course\",\n+        \"content_id\": \"123\",\n+        \"external_id\": \"\",\n+        \"exam_name\": \"Midterm\",\n+        \"time_limit_mins\": 90,\n+        \"is_proctored\": true,\n+        \"is_active\": true\n+    }\n+    \"\"\"\n+    proctored_exam = ProctoredExam.get_exam_by_id(exam_id)\n+    if proctored_exam is None:\n+        raise ProctoredExamNotFoundException\n+\n+    serialized_exam_object = ProctoredExamSerializer(proctored_exam)\n+    return serialized_exam_object.data\n+\n+\n+def get_exam_by_content_id(course_id, content_id):\n+    \"\"\"\n+    Looks up exam by the course_id/content_id pair. Raises exception if not found.\n+\n+    Returns dictionary version of the Django ORM object\n+    e.g.\n+    {\n+        \"course_id\": \"edX/DemoX/Demo_Course\",\n+        \"content_id\": \"123\",\n+        \"external_id\": \"\",\n+        \"exam_name\": \"Midterm\",\n+        \"time_limit_mins\": 90,\n+        \"is_proctored\": true,\n+        \"is_active\": true\n+    }\n+    \"\"\"\n+    proctored_exam = ProctoredExam.get_exam_by_content_id(course_id, content_id)\n+    if proctored_exam is None:\n+        raise ProctoredExamNotFoundException\n+\n+    serialized_exam_object = ProctoredExamSerializer(proctored_exam)\n+    return serialized_exam_object.data\n+\n+\n+def add_allowance_for_user(exam_id, user_info, key, value):\n+    \"\"\"\n+    Adds (or updates) an allowance for a user within a given exam\n+    \"\"\"\n+    ProctoredExamStudentAllowance.add_allowance_for_user(exam_id, user_info, key, value)\n+\n+\n+def get_allowances_for_course(course_id):\n+    \"\"\"\n+    Get all the allowances for the course.\n+    \"\"\"\n+    student_allowances = ProctoredExamStudentAllowance.get_allowances_for_course(course_id)\n+    return [ProctoredExamStudentAllowanceSerializer(allowance).data for allowance in student_allowances]\n+\n+\n+def remove_allowance_for_user(exam_id, user_id, key):\n+    \"\"\"\n+    Deletes an allowance for a user within a given exam.\n+    \"\"\"\n+    student_allowance = ProctoredExamStudentAllowance.get_allowance_for_user(exam_id, user_id, key)\n+    if student_allowance is not None:\n+        student_allowance.delete()\n+\n+\n+def get_exam_attempt(exam_id, user_id):\n+    \"\"\"\n+    Return an existing exam attempt for the given student\n+    \"\"\"\n+    exam_attempt_obj = ProctoredExamStudentAttempt.objects.get_exam_attempt(exam_id, user_id)\n+    serialized_attempt_obj = ProctoredExamStudentAttemptSerializer(exam_attempt_obj)\n+    return serialized_attempt_obj.data if exam_attempt_obj else None\n+\n+\n+def get_exam_attempt_by_id(attempt_id):\n+    \"\"\"\n+    Return an existing exam attempt for the given student\n+    \"\"\"\n+    exam_attempt_obj = ProctoredExamStudentAttempt.objects.get_exam_attempt_by_id(attempt_id)\n+    serialized_attempt_obj = ProctoredExamStudentAttemptSerializer(exam_attempt_obj)\n+    return serialized_attempt_obj.data if exam_attempt_obj else None\n+\n+\n+def get_exam_attempt_by_code(attempt_code):\n+    \"\"\"\n+    Signals the beginning of an exam attempt when we only have\n+    an attempt code\n+    \"\"\"\n+\n+    exam_attempt_obj = ProctoredExamStudentAttempt.objects.get_exam_attempt_by_code(attempt_code)\n+    serialized_attempt_obj = ProctoredExamStudentAttemptSerializer(exam_attempt_obj)\n+    return serialized_attempt_obj.data if exam_attempt_obj else None\n+\n+\n+def create_exam_attempt(exam_id, user_id, taking_as_proctored=False):\n+    \"\"\"\n+    Creates an exam attempt for user_id against exam_id. There should only be\n+    one exam_attempt per user per exam. Multiple attempts by user will be archived\n+    in a separate table\n+    \"\"\"\n+    if ProctoredExamStudentAttempt.objects.get_exam_attempt(exam_id, user_id):\n+        err_msg = (\n+            'Cannot create new exam attempt for exam_id = {exam_id} and '\n+            'user_id = {user_id} because it already exists!'\n+        ).format(exam_id=exam_id, user_id=user_id)","path":"edx_proctoring/api.py","position":207,"original_position":207,"commit_id":"7ab4c45136b586f4202799791f6f12f48272650b","original_commit_id":"7ab4c45136b586f4202799791f6f12f48272650b","user":{"login":"catong","id":9041399,"avatar_url":"https://avatars.githubusercontent.com/u/9041399?v=3","gravatar_id":"","url":"https://api.github.com/users/catong","html_url":"https://github.com/catong","followers_url":"https://api.github.com/users/catong/followers","following_url":"https://api.github.com/users/catong/following{/other_user}","gists_url":"https://api.github.com/users/catong/gists{/gist_id}","starred_url":"https://api.github.com/users/catong/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/catong/subscriptions","organizations_url":"https://api.github.com/users/catong/orgs","repos_url":"https://api.github.com/users/catong/repos","events_url":"https://api.github.com/users/catong/events{/privacy}","received_events_url":"https://api.github.com/users/catong/received_events","type":"User","site_admin":false},"body":"@chrisndodge I think this is not an end user facing error message -- am I right?\r\n","created_at":"2015-07-20T21:02:40Z","updated_at":"2015-07-20T21:02:40Z","html_url":"https://github.com/edx/edx-proctoring/pull/17#discussion_r35043748","pull_request_url":"https://api.github.com/repos/edx/edx-proctoring/pulls/17","_links":{"self":{"href":"https://api.github.com/repos/edx/edx-proctoring/pulls/comments/35043748"},"html":{"href":"https://github.com/edx/edx-proctoring/pull/17#discussion_r35043748"},"pull_request":{"href":"https://api.github.com/repos/edx/edx-proctoring/pulls/17"}}},"pull_request":{"url":"https://api.github.com/repos/edx/edx-proctoring/pulls/17","id":39278871,"html_url":"https://github.com/edx/edx-proctoring/pull/17","diff_url":"https://github.com/edx/edx-proctoring/pull/17.diff","patch_url":"https://github.com/edx/edx-proctoring/pull/17.patch","issue_url":"https://api.github.com/repos/edx/edx-proctoring/issues/17","number":17,"state":"open","locked":false,"title":"Review PR of master","user":{"login":"chrisndodge","id":2362917,"avatar_url":"https://avatars.githubusercontent.com/u/2362917?v=3","gravatar_id":"","url":"https://api.github.com/users/chrisndodge","html_url":"https://github.com/chrisndodge","followers_url":"https://api.github.com/users/chrisndodge/followers","following_url":"https://api.github.com/users/chrisndodge/following{/other_user}","gists_url":"https://api.github.com/users/chrisndodge/gists{/gist_id}","starred_url":"https://api.github.com/users/chrisndodge/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chrisndodge/subscriptions","organizations_url":"https://api.github.com/users/chrisndodge/orgs","repos_url":"https://api.github.com/users/chrisndodge/repos","events_url":"https://api.github.com/users/chrisndodge/events{/privacy}","received_events_url":"https://api.github.com/users/chrisndodge/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-07-06T15:21:30Z","updated_at":"2015-07-20T21:02:40Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2eef4ffb04f9ec34c0c6f3959ef0a922b2ea3230","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/edx/edx-proctoring/pulls/17/commits","review_comments_url":"https://api.github.com/repos/edx/edx-proctoring/pulls/17/comments","review_comment_url":"https://api.github.com/repos/edx/edx-proctoring/pulls/comments{/number}","comments_url":"https://api.github.com/repos/edx/edx-proctoring/issues/17/comments","statuses_url":"https://api.github.com/repos/edx/edx-proctoring/statuses/7ab4c45136b586f4202799791f6f12f48272650b","head":{"label":"edx:master","ref":"master","sha":"7ab4c45136b586f4202799791f6f12f48272650b","user":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"repo":{"id":37069077,"name":"edx-proctoring","full_name":"edx/edx-proctoring","owner":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/edx/edx-proctoring","description":"","fork":false,"url":"https://api.github.com/repos/edx/edx-proctoring","forks_url":"https://api.github.com/repos/edx/edx-proctoring/forks","keys_url":"https://api.github.com/repos/edx/edx-proctoring/keys{/key_id}","collaborators_url":"https://api.github.com/repos/edx/edx-proctoring/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/edx/edx-proctoring/teams","hooks_url":"https://api.github.com/repos/edx/edx-proctoring/hooks","issue_events_url":"https://api.github.com/repos/edx/edx-proctoring/issues/events{/number}","events_url":"https://api.github.com/repos/edx/edx-proctoring/events","assignees_url":"https://api.github.com/repos/edx/edx-proctoring/assignees{/user}","branches_url":"https://api.github.com/repos/edx/edx-proctoring/branches{/branch}","tags_url":"https://api.github.com/repos/edx/edx-proctoring/tags","blobs_url":"https://api.github.com/repos/edx/edx-proctoring/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/edx/edx-proctoring/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/edx/edx-proctoring/git/refs{/sha}","trees_url":"https://api.github.com/repos/edx/edx-proctoring/git/trees{/sha}","statuses_url":"https://api.github.com/repos/edx/edx-proctoring/statuses/{sha}","languages_url":"https://api.github.com/repos/edx/edx-proctoring/languages","stargazers_url":"https://api.github.com/repos/edx/edx-proctoring/stargazers","contributors_url":"https://api.github.com/repos/edx/edx-proctoring/contributors","subscribers_url":"https://api.github.com/repos/edx/edx-proctoring/subscribers","subscription_url":"https://api.github.com/repos/edx/edx-proctoring/subscription","commits_url":"https://api.github.com/repos/edx/edx-proctoring/commits{/sha}","git_commits_url":"https://api.github.com/repos/edx/edx-proctoring/git/commits{/sha}","comments_url":"https://api.github.com/repos/edx/edx-proctoring/comments{/number}","issue_comment_url":"https://api.github.com/repos/edx/edx-proctoring/issues/comments{/number}","contents_url":"https://api.github.com/repos/edx/edx-proctoring/contents/{+path}","compare_url":"https://api.github.com/repos/edx/edx-proctoring/compare/{base}...{head}","merges_url":"https://api.github.com/repos/edx/edx-proctoring/merges","archive_url":"https://api.github.com/repos/edx/edx-proctoring/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/edx/edx-proctoring/downloads","issues_url":"https://api.github.com/repos/edx/edx-proctoring/issues{/number}","pulls_url":"https://api.github.com/repos/edx/edx-proctoring/pulls{/number}","milestones_url":"https://api.github.com/repos/edx/edx-proctoring/milestones{/number}","notifications_url":"https://api.github.com/repos/edx/edx-proctoring/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/edx/edx-proctoring/labels{/name}","releases_url":"https://api.github.com/repos/edx/edx-proctoring/releases{/id}","created_at":"2015-06-08T13:38:04Z","updated_at":"2015-06-17T19:16:01Z","pushed_at":"2015-07-20T14:29:40Z","git_url":"git://github.com/edx/edx-proctoring.git","ssh_url":"git@github.com:edx/edx-proctoring.git","clone_url":"https://github.com/edx/edx-proctoring.git","svn_url":"https://github.com/edx/edx-proctoring","homepage":null,"size":956,"stargazers_count":1,"watchers_count":1,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":2,"forks":1,"open_issues":2,"watchers":1,"default_branch":"master"}},"base":{"label":"edx:empty","ref":"empty","sha":"6d458e57a10315a442780f04c4686ba0ec13314a","user":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"repo":{"id":37069077,"name":"edx-proctoring","full_name":"edx/edx-proctoring","owner":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/edx/edx-proctoring","description":"","fork":false,"url":"https://api.github.com/repos/edx/edx-proctoring","forks_url":"https://api.github.com/repos/edx/edx-proctoring/forks","keys_url":"https://api.github.com/repos/edx/edx-proctoring/keys{/key_id}","collaborators_url":"https://api.github.com/repos/edx/edx-proctoring/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/edx/edx-proctoring/teams","hooks_url":"https://api.github.com/repos/edx/edx-proctoring/hooks","issue_events_url":"https://api.github.com/repos/edx/edx-proctoring/issues/events{/number}","events_url":"https://api.github.com/repos/edx/edx-proctoring/events","assignees_url":"https://api.github.com/repos/edx/edx-proctoring/assignees{/user}","branches_url":"https://api.github.com/repos/edx/edx-proctoring/branches{/branch}","tags_url":"https://api.github.com/repos/edx/edx-proctoring/tags","blobs_url":"https://api.github.com/repos/edx/edx-proctoring/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/edx/edx-proctoring/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/edx/edx-proctoring/git/refs{/sha}","trees_url":"https://api.github.com/repos/edx/edx-proctoring/git/trees{/sha}","statuses_url":"https://api.github.com/repos/edx/edx-proctoring/statuses/{sha}","languages_url":"https://api.github.com/repos/edx/edx-proctoring/languages","stargazers_url":"https://api.github.com/repos/edx/edx-proctoring/stargazers","contributors_url":"https://api.github.com/repos/edx/edx-proctoring/contributors","subscribers_url":"https://api.github.com/repos/edx/edx-proctoring/subscribers","subscription_url":"https://api.github.com/repos/edx/edx-proctoring/subscription","commits_url":"https://api.github.com/repos/edx/edx-proctoring/commits{/sha}","git_commits_url":"https://api.github.com/repos/edx/edx-proctoring/git/commits{/sha}","comments_url":"https://api.github.com/repos/edx/edx-proctoring/comments{/number}","issue_comment_url":"https://api.github.com/repos/edx/edx-proctoring/issues/comments{/number}","contents_url":"https://api.github.com/repos/edx/edx-proctoring/contents/{+path}","compare_url":"https://api.github.com/repos/edx/edx-proctoring/compare/{base}...{head}","merges_url":"https://api.github.com/repos/edx/edx-proctoring/merges","archive_url":"https://api.github.com/repos/edx/edx-proctoring/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/edx/edx-proctoring/downloads","issues_url":"https://api.github.com/repos/edx/edx-proctoring/issues{/number}","pulls_url":"https://api.github.com/repos/edx/edx-proctoring/pulls{/number}","milestones_url":"https://api.github.com/repos/edx/edx-proctoring/milestones{/number}","notifications_url":"https://api.github.com/repos/edx/edx-proctoring/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/edx/edx-proctoring/labels{/name}","releases_url":"https://api.github.com/repos/edx/edx-proctoring/releases{/id}","created_at":"2015-06-08T13:38:04Z","updated_at":"2015-06-17T19:16:01Z","pushed_at":"2015-07-20T14:29:40Z","git_url":"git://github.com/edx/edx-proctoring.git","ssh_url":"git@github.com:edx/edx-proctoring.git","clone_url":"https://github.com/edx/edx-proctoring.git","svn_url":"https://github.com/edx/edx-proctoring","homepage":null,"size":956,"stargazers_count":1,"watchers_count":1,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":2,"forks":1,"open_issues":2,"watchers":1,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/edx/edx-proctoring/pulls/17"},"html":{"href":"https://github.com/edx/edx-proctoring/pull/17"},"issue":{"href":"https://api.github.com/repos/edx/edx-proctoring/issues/17"},"comments":{"href":"https://api.github.com/repos/edx/edx-proctoring/issues/17/comments"},"review_comments":{"href":"https://api.github.com/repos/edx/edx-proctoring/pulls/17/comments"},"review_comment":{"href":"https://api.github.com/repos/edx/edx-proctoring/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/edx/edx-proctoring/pulls/17/commits"},"statuses":{"href":"https://api.github.com/repos/edx/edx-proctoring/statuses/7ab4c45136b586f4202799791f6f12f48272650b"}}}}