diff --git a/.gitignore b/.gitignore
index ec4a476cdaf..458d5286f9e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@
# Installation artifacts
src
+requirements/*.txt
# Sphinx docs:
build
@@ -47,10 +48,13 @@ Desktop.ini
# Test Artifacts
.coverage
+coverage.xml
nosetests.xml
reports/
coverage/
htmlcov/
+.cache/
+.tox/
acceptance_tests/*.png
node_modules/
diff --git a/.travis.yml b/.travis.yml
index 6ef468602e8..d6be780d2d0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,28 +1,20 @@
language: python
+cache: pip
python:
- - "2.7"
+ - 2.7
env:
- - DJANGO_SETTINGS_MODULE=settings
-
+ - DJANGO_SETTINGS_MODULE=test_settings
before_install:
- - "export DISPLAY=:99.0"
- - "sh -e /etc/init.d/xvfb start"
-
-# Set Travis builds to use docker container
-sudo: false
-
+ - export DISPLAY=:99.0
+ - sh -e /etc/init.d/xvfb start
+ - pip install --upgrade pip # pip-tools requires pip >= 6.1
install:
- - npm install
- - "pip install -r local_requirements.txt"
- - "pip install -r requirements.txt"
- - "pip install -r test_requirements.txt"
- - "pip install coveralls"
-
+ - pip install setuptools==32.3.1 # need newer version than Travis default
+ - make install
script:
- - coverage run ./manage.py test edx_proctoring
- - coverage report -m
- - gulp test
- - pep8 edx_proctoring
- - pylint edx_proctoring --report=no
-
-after_success: coveralls
+ - make test-all
+ - gulp test
+after_success:
+ - coverage xml
+ - bash <(curl -s https://codecov.io/bash) -cF python -f coverage.xml
+ - bash <(curl -s https://codecov.io/bash) -cF javascript -f build/coverage-js/coverage.xml
diff --git a/.tx/config b/.tx/config
new file mode 100644
index 00000000000..06a42dad678
--- /dev/null
+++ b/.tx/config
@@ -0,0 +1,14 @@
+[main]
+host = https://www.transifex.com
+
+[edx-platform.edx-proctoring]
+file_filter = edx_proctoring/locale//LC_MESSAGES/django.po
+source_file = edx_proctoring/locale/en/LC_MESSAGES/django.po
+source_lang = en
+type = PO
+
+[edx-platform.edx-proctoring-js]
+file_filter = edx_proctoring/locale//LC_MESSAGES/djangojs.po
+source_file = edx_proctoring/locale/en/LC_MESSAGES/django.po
+source_lang = en
+type = PO
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
new file mode 100644
index 00000000000..173bfb640b8
--- /dev/null
+++ b/CHANGELOG.rst
@@ -0,0 +1,4 @@
+We don't maintain a detailed changelog. For details of changes, please see
+either the `GitHub commit history`_.
+
+.. _GitHub commit history: https://github.com/edx/edx-proctoring/commits/master
diff --git a/LICENSE b/LICENSE.txt
similarity index 99%
rename from LICENSE
rename to LICENSE.txt
index 1eb391f3883..ec8483f5a08 100644
--- a/LICENSE
+++ b/LICENSE.txt
@@ -1,4 +1,5 @@
- GNU AFFERO GENERAL PUBLIC LICENSE
+
+GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc.
@@ -669,3 +670,5 @@ of the license. "Corresponding Source" of this work, or works based on this
work, as defined by the terms of this license do not include source code
files for programs used solely to provide those public, independently
available web services.
+
+
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 00000000000..e5e494aabdb
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,8 @@
+include AUTHORS
+include CHANGELOG.rst
+include CONTRIBUTING.rst
+include LICENSE.txt
+include README.rst
+graft edx_proctoring
+prune edx_proctoring/static/proctoring/spec
+global-exclude *.pyc
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000000..d83d6050481
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,95 @@
+.PHONY: help upgrade requirements clean quality requirements docs \
+ test test-all coverage \
+ compile_translations dummy_translations extract_translations \
+ fake_translations pull_translations push_translations
+
+.DEFAULT_GOAL := help
+
+define BROWSER_PYSCRIPT
+import os, webbrowser, sys
+try:
+ from urllib import pathname2url
+except:
+ from urllib.request import pathname2url
+
+webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1])))
+endef
+export BROWSER_PYSCRIPT
+BROWSER := python -c "$$BROWSER_PYSCRIPT"
+
+help: ## display this help message
+ @echo "Please use \`make ' where is one of"
+ @perl -nle'print $& if m{^[a-zA-Z_-]+:.*?## .*$$}' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m %-25s\033[0m %s\n", $$1, $$2}'
+
+clean: ## remove generated byte code, coverage reports, and build artifacts
+ find . -name '*.pyc' -exec rm -f {} +
+ find . -name '*.pyo' -exec rm -f {} +
+ find . -name '*~' -exec rm -f {} +
+ rm -fr build/
+ rm -fr dist/
+ rm -fr *.egg-info
+
+upgrade: ## update the requirements/*.txt files with the latest packages satisfying requirements/*.in
+ pip install -q pip-tools
+ pip-compile --upgrade -o requirements/base.txt requirements/base.in
+ pip-compile --upgrade -o requirements/dev.txt requirements/dev.in requirements/quality.in
+ pip-compile --upgrade -o requirements/doc.txt requirements/base.in requirements/doc.in
+ pip-compile --upgrade -o requirements/quality.txt requirements/quality.in
+ pip-compile --upgrade -o requirements/test.txt requirements/base.in requirements/test.in
+ # Let tox control the Django version for tests
+ sed '/django==/d' requirements/test.txt > requirements/test.tmp
+ mv requirements/test.tmp requirements/test.txt
+
+requirements: ## install development environment requirements
+ pip install -qr requirements/dev.txt --exists-action w
+ pip-sync requirements/*.txt requirements/private.*
+
+install: upgrade requirements
+ ./manage.py syncdb --noinput --settings=test_settings
+ ./manage.py migrate --settings=test_settings
+ npm install
+
+coverage: clean ## generate and view HTML coverage report
+ py.test --cov-report html
+ $(BROWSER) htmlcov/index.html
+
+docs: ## generate Sphinx HTML documentation, including API docs
+ tox -e docs
+ $(BROWSER) docs/_build/html/index.html
+
+compile_translations: ## compile translation files, outputting .po files for each supported language
+ ./manage.py compilemessages
+
+dummy_translations: ## generate dummy translation (.po) files
+ i18n_tool dummy --config ./edx_proctoring/locale/config.yaml
+
+extract_translations: ## extract strings to be translated, outputting .mo files
+ rm -rf docs/_build
+ ./manage.py makemessages -l en -v1 -d django --ignore="edx-proctoring/*"
+ ./manage.py makemessages -l en -v1 -d djangojs --ignore="edx-proctoring/*"
+
+fake_translations: extract_translations dummy_translations compile_translations ## generate and compile dummy translation files
+
+pull_translations: ## pull translations from Transifex
+ tx pull -a
+
+push_translations: ## push source translation files (.po) from Transifex
+ tx push -s
+
+validate_translations: fake_translations detect_changed_source_translations
+
+quality: ## check coding style with pycodestyle and pylint
+ tox -e quality
+
+test-python: clean ## run tests in the current virtualenv
+ ./manage.py test edx_proctoring --verbosity=3
+
+test-js:
+ gulp test
+
+test-all: ## run tests on every supported Python/Django combination
+ tox -e quality
+ tox
+
+diff_cover: test
+ diff-cover coverage.xml
diff --git a/README.md b/README.md
deleted file mode 100644
index 5f68b3961bc..00000000000
--- a/README.md
+++ /dev/null
@@ -1,59 +0,0 @@
-edx-proctoring [![Build Status](https://travis-ci.org/edx/edx-proctoring.svg?branch=master)](https://travis-ci.org/edx/edx-proctoring) [![Coverage Status](https://coveralls.io/repos/edx/edx-proctoring/badge.svg?branch=master&service=github)](https://coveralls.io/github/edx/edx-proctoring?branch=master)
-
-========================
-
-This is the Exam Proctoring subsystem for the Open edX platform.
-
-
-While technical and developer documentation is forthcoming, here are basic instructions on how to use this
-in an Open edX installation.
-
-NOTE: Proctoring will not be available in the Open edX named releases until Dogwood. However, you can use this if you use a copy of edx-platform (master) after 8/20/2015.
-
-In order to use edx-proctoring, you must obtain an account (and secret configuration - see below) with SoftwareSecure, which provides the proctoring review services that edx-proctoring integrates with.
-
-
-CONFIGURATION:
-
-You will need to turn on the ENABLE_SPECIAL_EXAMS in lms.env.json and cms.env.json FEATURES dictionary:
-
-```
-:
-"FEATURES": {
- :
- "ENABLE_SPECIAL_EXAMS": true,
- :
-}
-```
-
-Also in your lms.env.json and cms.env.json file please add the following:
-
-```
- "PROCTORING_SETTINGS": {
- "LINK_URLS": {
- "contact_us": "{add link here}",
- "faq": "{add link here}",
- "online_proctoring_rules": "{add link here}",
- "tech_requirements": "{add link here}"
- }
- },
-```
-
-In your lms.auth.json file, please add the following *secure* information:
-
-```
- "PROCTORING_BACKEND_PROVIDER": {
- "class": "edx_proctoring.backends.software_secure.SoftwareSecureBackendProvider",
- "options": {
- "crypto_key": "{add SoftwareSecure crypto key here}",
- "exam_register_endpoint": "{add enpoint to SoftwareSecure}",
- "exam_sponsor": "{add SoftwareSecure sponsor}",
- "organization": "{add SoftwareSecure organization}",
- "secret_key": "{add SoftwareSecure secret key}",
- "secret_key_id": "{add SoftwareSecure secret key id}",
- "software_download_url": "{add SoftwareSecure download url}"
- }
- },
-```
-
-You will need to restart services after these configuration changes for them to take effect.
diff --git a/README.rst b/README.rst
new file mode 100644
index 00000000000..87314107fa2
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,133 @@
+django-component-views
+=============================
+
+.. image:: https://img.shields.io/pypi/v/edx-proctoring.svg
+ :target: https://pypi.python.org/pypi/edx-proctoring/
+ :alt: PyPI
+
+.. image:: https://travis-ci.org/edx/edx-proctoring.svg?branch=master
+ :target: https://travis-ci.org/edx/edx-proctoring
+ :alt: Travis
+
+.. image:: https://codecov.io/gh/edx/edx-proctoring/branch/master/graph/badge.svg
+ :target: https://codecov.io/gh/edx/edx-proctoring
+ :alt: Codecov
+
+.. image:: https://img.shields.io/pypi/pyversions/edx-proctoring.svg
+ :target: https://pypi.python.org/pypi/edx-proctoring/
+ :alt: Supported Python versions
+
+.. image:: https://img.shields.io/github/license/edx/django-component-views.svg
+ :target: https://github.com/edx/edx-proctoring/blob/master/LICENSE.txt
+ :alt: License
+
+This is the exam proctoring subsystem for the Open edX platform.
+
+Overview
+--------
+
+Proctored exams are exams with time limits that learners complete while online
+proctoring software monitors their computers and behavior for activity that
+might be evidence of cheating. This Python library provides the proctoring
+implementation used by Open edX.
+
+Documentation
+-------------
+
+For documentation about taking a proctored exam, see `Taking a Proctored Exam`_.
+
+For authoring documentation, see `Including Proctored Exams In Your Course`_.
+
+Installation
+------------
+
+To install edx-proctoring:
+
+ mkvirtualenv edx-proctoring
+ make install
+
+To run the tests:
+
+ make test-all
+
+For a full list of Make targets:
+
+ make help
+
+Configuration
+-------------
+
+In order to use edx-proctoring, you must obtain an account (and secret
+configuration - see below) with SoftwareSecure, which provides the proctoring
+review services that edx-proctoring integrates with.
+
+You will need to turn on the ENABLE_SPECIAL_EXAMS in lms.env.json and
+cms.env.json FEATURES dictionary::
+
+ "FEATURES": {
+ :
+ "ENABLE_SPECIAL_EXAMS": true,
+ :
+ }
+
+Also in your lms.env.json and cms.env.json file please add the following::
+
+
+ "PROCTORING_SETTINGS": {
+ "LINK_URLS": {
+ "contact_us": "{add link here}",
+ "faq": "{add link here}",
+ "online_proctoring_rules": "{add link here}",
+ "tech_requirements": "{add link here}"
+ }
+ },
+
+In your lms.auth.json file, please add the following *secure* information::
+
+ "PROCTORING_BACKEND_PROVIDER": {
+ "class": "edx_proctoring.backends.software_secure.SoftwareSecureBackendProvider",
+ "options": {
+ "crypto_key": "{add SoftwareSecure crypto key here}",
+ "exam_register_endpoint": "{add enpoint to SoftwareSecure}",
+ "exam_sponsor": "{add SoftwareSecure sponsor}",
+ "organization": "{add SoftwareSecure organization}",
+ "secret_key": "{add SoftwareSecure secret key}",
+ "secret_key_id": "{add SoftwareSecure secret key id}",
+ "software_download_url": "{add SoftwareSecure download url}"
+ }
+ },
+
+You will need to restart services after these configuration changes for them to
+take effect.
+
+License
+-------
+
+The code in this repository is licensed under the AGPL 3.0 unless
+otherwise noted.
+
+Please see ``LICENSE.txt`` for details.
+
+How To Contribute
+-----------------
+
+Contributions are very welcome.
+
+Please read `How To Contribute `_ for details.
+
+Even though they were written with ``edx-platform`` in mind, the guidelines
+should be followed for Open edX code in general.
+
+Reporting Security Issues
+-------------------------
+
+Please do not report security issues in public. Please email security@edx.org.
+
+Getting Help
+------------
+
+Have a question about this repository, or about Open edX in general? Please
+refer to this `list of resources`_ if you need any assistance.
+
+.. _list of resources: https://open.edx.org/getting-help
+.. _Including Proctored Exams In Your Course: http://edx.readthedocs.io/projects/edx-partner-course-staff/en/latest/course_features/credit_courses/proctored_exams.html
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 00000000000..4da47686e7a
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,12 @@
+coverage:
+ status:
+ project:
+ default:
+ enabled: yes
+ target: auto
+ patch:
+ default:
+ enabled: yes
+ target: 100%
+
+comment: false
diff --git a/edx_proctoring/__init__.py b/edx_proctoring/__init__.py
index f5951960d60..70b445c77d1 100644
--- a/edx_proctoring/__init__.py
+++ b/edx_proctoring/__init__.py
@@ -1,3 +1,9 @@
"""
-Init file for the top level edx_proctoring directory
+The exam proctoring subsystem for the Open edX platform.
"""
+
+from __future__ import absolute_import
+
+__version__ = '0.17.0'
+
+default_app_config = 'edx_proctoring.apps.EdxProctoringConfig' # pylint: disable=invalid-name
diff --git a/edx_proctoring/admin.py b/edx_proctoring/admin.py
index 0072f81ed59..bbae0f405cc 100644
--- a/edx_proctoring/admin.py
+++ b/edx_proctoring/admin.py
@@ -3,8 +3,10 @@
"""
# pylint: disable=no-self-argument, no-member
-import pytz
+from __future__ import absolute_import
+
from datetime import datetime, timedelta
+import pytz
from django import forms
from django.db.models import Q
@@ -476,6 +478,7 @@ def prettify_course_id(course_id):
"""
return course_id.replace('+', ' ').replace('/', ' ').replace('course-v1:', '')
+
admin.site.register(ProctoredExamStudentAttempt, ProctoredExamStudentAttemptAdmin)
admin.site.register(ProctoredExamReviewPolicy, ProctoredExamReviewPolicyAdmin)
admin.site.register(ProctoredExamSoftwareSecureReview, ProctoredExamSoftwareSecureReviewAdmin)
diff --git a/edx_proctoring/api.py b/edx_proctoring/api.py
index 2a83e42353a..5954eb9921d 100644
--- a/edx_proctoring/api.py
+++ b/edx_proctoring/api.py
@@ -4,13 +4,14 @@
In-Proc API (aka Library) for the edx_proctoring subsystem. This is not to be confused with a HTTP REST
API which is in the views.py file, per edX coding standards
"""
-import pytz
-import uuid
-import logging
+from __future__ import absolute_import
from datetime import datetime, timedelta
+import logging
+import uuid
+import pytz
-from django.utils.translation import ugettext as _
+from django.utils.translation import ugettext as _, ugettext_noop
from django.conf import settings
from django.template import Context, loader
from django.core.urlresolvers import reverse, NoReverseMatch
@@ -1280,47 +1281,47 @@ def _resolve_prerequisite_links(exam, prerequisites):
STATUS_SUMMARY_MAP = {
'_default': {
- 'short_description': _('Taking As Proctored Exam'),
+ 'short_description': ugettext_noop('Taking As Proctored Exam'),
'suggested_icon': 'fa-pencil-square-o',
'in_completed_state': False
},
ProctoredExamStudentAttemptStatus.eligible: {
- 'short_description': _('Proctored Option Available'),
+ 'short_description': ugettext_noop('Proctored Option Available'),
'suggested_icon': 'fa-pencil-square-o',
'in_completed_state': False
},
ProctoredExamStudentAttemptStatus.declined: {
- 'short_description': _('Taking As Open Exam'),
+ 'short_description': ugettext_noop('Taking As Open Exam'),
'suggested_icon': 'fa-pencil-square-o',
'in_completed_state': False
},
ProctoredExamStudentAttemptStatus.submitted: {
- 'short_description': _('Pending Session Review'),
+ 'short_description': ugettext_noop('Pending Session Review'),
'suggested_icon': 'fa-spinner fa-spin',
'in_completed_state': True
},
ProctoredExamStudentAttemptStatus.second_review_required: {
- 'short_description': _('Pending Session Review'),
+ 'short_description': ugettext_noop('Pending Session Review'),
'suggested_icon': 'fa-spinner fa-spin',
'in_completed_state': True
},
ProctoredExamStudentAttemptStatus.verified: {
- 'short_description': _('Passed Proctoring'),
+ 'short_description': ugettext_noop('Passed Proctoring'),
'suggested_icon': 'fa-check',
'in_completed_state': True
},
ProctoredExamStudentAttemptStatus.rejected: {
- 'short_description': _('Failed Proctoring'),
+ 'short_description': ugettext_noop('Failed Proctoring'),
'suggested_icon': 'fa-exclamation-triangle',
'in_completed_state': True
},
ProctoredExamStudentAttemptStatus.error: {
- 'short_description': _('Failed Proctoring'),
+ 'short_description': ugettext_noop('Failed Proctoring'),
'suggested_icon': 'fa-exclamation-triangle',
'in_completed_state': True
},
ProctoredExamStudentAttemptStatus.expired: {
- 'short_description': _('Proctored Option No Longer Available'),
+ 'short_description': ugettext_noop('Proctored Option No Longer Available'),
'suggested_icon': 'fa-times-circle',
'in_completed_state': False
}
@@ -1329,17 +1330,17 @@ def _resolve_prerequisite_links(exam, prerequisites):
PRACTICE_STATUS_SUMMARY_MAP = {
'_default': {
- 'short_description': _('Ungraded Practice Exam'),
+ 'short_description': ugettext_noop('Ungraded Practice Exam'),
'suggested_icon': '',
'in_completed_state': False
},
ProctoredExamStudentAttemptStatus.submitted: {
- 'short_description': _('Practice Exam Completed'),
+ 'short_description': ugettext_noop('Practice Exam Completed'),
'suggested_icon': 'fa-check',
'in_completed_state': True
},
ProctoredExamStudentAttemptStatus.error: {
- 'short_description': _('Practice Exam Failed'),
+ 'short_description': ugettext_noop('Practice Exam Failed'),
'suggested_icon': 'fa-exclamation-triangle',
'in_completed_state': True
}
@@ -1347,7 +1348,7 @@ def _resolve_prerequisite_links(exam, prerequisites):
TIMED_EXAM_STATUS_SUMMARY_MAP = {
'_default': {
- 'short_description': _('Timed Exam'),
+ 'short_description': ugettext_noop('Timed Exam'),
'suggested_icon': 'fa-clock-o',
'in_completed_state': False
}
@@ -1382,7 +1383,13 @@ def get_attempt_status_summary(user_id, course_id, content_id):
# check if the exam is not proctored
if not exam['is_proctored']:
- return TIMED_EXAM_STATUS_SUMMARY_MAP['_default']
+ summary = {}
+ summary.update(TIMED_EXAM_STATUS_SUMMARY_MAP['_default'])
+ # Note: translate the short description as it was stored unlocalized
+ summary.update({
+ 'short_description': _(summary['short_description']) # pylint: disable=translation-of-non-string
+ })
+ return summary
# let's check credit eligibility
credit_service = get_runtime_service('credit')
@@ -1403,13 +1410,17 @@ def get_attempt_status_summary(user_id, course_id, content_id):
status_map = STATUS_SUMMARY_MAP if not exam['is_practice_exam'] else PRACTICE_STATUS_SUMMARY_MAP
- summary = None
+ summary = {}
if status in status_map:
- summary = status_map[status]
+ summary.update(status_map[status])
else:
- summary = status_map['_default']
+ summary.update(status_map['_default'])
- summary.update({"status": status})
+ # Note: translate the short description as it was stored unlocalized
+ summary.update({
+ 'status': status,
+ 'short_description': _(summary['short_description']) # pylint: disable=translation-of-non-string
+ })
return summary
diff --git a/edx_proctoring/apps.py b/edx_proctoring/apps.py
new file mode 100644
index 00000000000..0e2a6b42283
--- /dev/null
+++ b/edx_proctoring/apps.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+"""
+edx_proctoring Django application initialization.
+"""
+
+from __future__ import absolute_import
+
+from django.apps import AppConfig
+
+
+class EdxProctoringConfig(AppConfig):
+ """
+ Configuration for the edx_proctoring Django application.
+ """
+
+ name = 'edx_proctoring'
diff --git a/edx_proctoring/backends/__init__.py b/edx_proctoring/backends/__init__.py
index 78b3f9d6005..ed680c2b946 100644
--- a/edx_proctoring/backends/__init__.py
+++ b/edx_proctoring/backends/__init__.py
@@ -2,6 +2,8 @@
All supporting Proctoring backends
"""
+from __future__ import absolute_import
+
from importlib import import_module
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
@@ -21,7 +23,7 @@ def get_backend_provider(emphemeral=False):
provider = _BACKEND_PROVIDER
if not _BACKEND_PROVIDER or emphemeral:
- config = getattr(settings, 'PROCTORING_BACKEND_PROVIDER')
+ config = getattr(settings, 'PROCTORING_BACKEND_PROVIDER') # pylint: disable=literal-used-as-attribute
if not config:
raise ImproperlyConfigured("Settings not configured with PROCTORING_BACKEND_PROVIDER!")
diff --git a/edx_proctoring/backends/backend.py b/edx_proctoring/backends/backend.py
index 970f229fca4..6222b3aace8 100644
--- a/edx_proctoring/backends/backend.py
+++ b/edx_proctoring/backends/backend.py
@@ -2,6 +2,8 @@
Defines the abstract base class that all backends should derive from
"""
+from __future__ import absolute_import
+
import abc
diff --git a/edx_proctoring/backends/mock.py b/edx_proctoring/backends/mock.py
index 704ab7ad464..9b766c415bf 100644
--- a/edx_proctoring/backends/mock.py
+++ b/edx_proctoring/backends/mock.py
@@ -2,7 +2,11 @@
Implements a mock proctoring backend provider to be used for testing,
which doesn't require the setup and configuration of the Software Secure backend provider.
"""
+
+from __future__ import absolute_import
+
import threading
+
from edx_proctoring.callbacks import start_exam_callback
from edx_proctoring.backends.backend import ProctoringBackendProvider
diff --git a/edx_proctoring/backends/null.py b/edx_proctoring/backends/null.py
index 0a1e7bd5d34..cd696514772 100644
--- a/edx_proctoring/backends/null.py
+++ b/edx_proctoring/backends/null.py
@@ -2,6 +2,8 @@
Implementation of a backend provider, which does nothing
"""
+from __future__ import absolute_import
+
from edx_proctoring.backends.backend import ProctoringBackendProvider
diff --git a/edx_proctoring/backends/software_secure.py b/edx_proctoring/backends/software_secure.py
index 1070a864b74..3feaa69120c 100644
--- a/edx_proctoring/backends/software_secure.py
+++ b/edx_proctoring/backends/software_secure.py
@@ -2,19 +2,23 @@
Integration with Software Secure's proctoring system
"""
-from Crypto.Cipher import DES3
+from __future__ import absolute_import
+
import base64
-from hashlib import sha256
-import requests
-import hmac
import binascii
import datetime
+from hashlib import sha256
+import hmac
import json
import logging
import unicodedata
+import requests
+
from django.conf import settings
+from Crypto.Cipher import DES3
+
from edx_proctoring.backends.backend import ProctoringBackendProvider
from edx_proctoring import constants
from edx_proctoring.exceptions import (
diff --git a/edx_proctoring/backends/tests/test_backend.py b/edx_proctoring/backends/tests/test_backend.py
index 21b0e3e422d..03ef2e3caeb 100644
--- a/edx_proctoring/backends/tests/test_backend.py
+++ b/edx_proctoring/backends/tests/test_backend.py
@@ -1,9 +1,14 @@
"""
Tests for backend.py
"""
+
+from __future__ import absolute_import
+
import time
from mock import patch
+
from django.test import TestCase
+
from edx_proctoring.backends.backend import ProctoringBackendProvider
from edx_proctoring.backends.null import NullBackendProvider
from edx_proctoring.backends.mock import MockProctoringBackendProvider
diff --git a/edx_proctoring/backends/tests/test_review_payload.py b/edx_proctoring/backends/tests/test_review_payload.py
index 82702218acc..6b723d45393 100644
--- a/edx_proctoring/backends/tests/test_review_payload.py
+++ b/edx_proctoring/backends/tests/test_review_payload.py
@@ -2,94 +2,101 @@
Some canned data for SoftwareSecure callback testing.
"""
-TEST_REVIEW_PAYLOAD = '''
-{
- "examDate": "Jul 15 2015 1:13AM",
- "examProcessingStatus": "Review Completed",
- "examTakerEmail": "4d07a01a-1502-422e-b943-93ac04dc6ced",
- "examTakerFirstName": "John",
- "examTakerLastName": "Doe",
- "keySetVersion": "",
- "examApiData": {
- "duration": 1,
- "examCode": "4d07a01a-1502-422e-b943-93ac04dc6ced",
- "examName": "edX Exams",
- "examPassword": "hQxvA8iUKKlsqKt0fQVBaXqmAziGug4NfxUChg94eGacYDcFwaIyBA==",
- "examSponsor": "edx LMS",
- "examUrl": "http://localhost:8000/api/edx_proctoring/proctoring_launch_callback/start_exam/4d07a01a-1502-422e-b943-93ac04dc6ced",
- "orgExtra": {
- "courseID": "edX/DemoX/Demo_Course",
- "examEndDate": "Wed, 15 Jul 2015 05:11:31 GMT",
- "examID": 6,
- "examStartDate": "Wed, 15 Jul 2015 05:10:31 GMT",
- "noOfStudents": 1
- },
- "organization": "edx",
- "reviewedExam": true,
- "reviewerNotes": "Closed Book",
- "ssiProduct": "rp-now"
- },
- "overAllComments": ";Candidates should always wear suit and tie for exams.",
- "reviewStatus": "Clean",
- "userPhotoBase64String": "",
- "videoReviewLink": "http://www.remoteproctor.com/AdminSite/Account/Reviewer/DirectLink-Generic.aspx?ID=foo",
- "examMetaData": {
- "examCode": "$attempt_code",
- "examName": "edX Exams",
- "examSponsor": "edx LMS",
- "organization": "edx",
- "reviewedExam": "True",
- "reviewerNotes": "Closed Book",
- "simulatedExam": "False",
- "ssiExamToken": "4E44F7AA-275D-4741-B531-73AE2407ECFB",
- "ssiProduct": "rp-now",
- "ssiRecordLocator": "$external_id"
- },
- "desktopComments": [
- {
- "comments": "Browsing other websites",
- "duration": 88,
- "eventFinish": 88,
- "eventStart": 12,
- "eventStatus": "Suspicious"
- },
- {
- "comments": "Browsing local computer",
- "duration": 88,
- "eventFinish": 88,
- "eventStart": 15,
- "eventStatus": "Rules Violation"
- },
- {
- "comments": "Student never entered the exam.",
- "duration": 88,
- "eventFinish": 88,
- "eventStart": 87,
- "eventStatus": "Clean"
- }
- ],
- "webCamComments": [
- {
- "comments": "Photo ID not provided",
- "duration": 796,
- "eventFinish": 796,
- "eventStart": 0,
- "eventStatus": "Suspicious"
- },
- {
- "comments": "Exam environment not confirmed",
- "duration": 796,
- "eventFinish": 796,
- "eventStart": 10,
- "eventStatus": "Rules Violation"
- },
- {
- "comments": "Looking away from computer",
- "duration": 796,
- "eventFinish": 796,
- "eventStart": 107,
- "eventStatus": "Rules Violation"
- }
- ]
- }
-'''
+import json
+
+MOCK_EXAM_ID = "4d07a01a-1502-422e-b943-93ac04dc6ced"
+
+
+def create_test_review_payload(exam_id=MOCK_EXAM_ID, attempt_code=None, external_id=None):
+ """
+ Returns a test payload for reviews.
+ """
+ return json.dumps({
+ "examDate": "Jul 15 2015 1:13AM",
+ "examProcessingStatus": "Review Completed",
+ "examTakerEmail": "4d07a01a-1502-422e-b943-93ac04dc6ced",
+ "examTakerFirstName": "John",
+ "examTakerLastName": "Doe",
+ "keySetVersion": "",
+ "examApiData": {
+ "duration": 1,
+ "examCode": "4d07a01a-1502-422e-b943-93ac04dc6ced",
+ "examName": "edX Exams",
+ "examPassword": "hQxvA8iUKKlsqKt0fQVBaXqmAziGug4NfxUChg94eGacYDcFwaIyBA==",
+ "examSponsor": "edx LMS",
+ "examUrl": "http://localhost:8000/api/edx_proctoring/proctoring_launch_callback/start_exam/" + exam_id,
+ "orgExtra": {
+ "courseID": "edX/DemoX/Demo_Course",
+ "examEndDate": "Wed, 15 Jul 2015 05:11:31 GMT",
+ "examID": exam_id,
+ "examStartDate": "Wed, 15 Jul 2015 05:10:31 GMT",
+ "noOfStudents": 1
+ },
+ "organization": "edx",
+ "reviewedExam": True,
+ "reviewerNotes": "Closed Book",
+ "ssiProduct": "rp-now"
+ },
+ "overAllComments": ";Candidates should always wear suit and tie for exams.",
+ "reviewStatus": "Clean",
+ "userPhotoBase64String": "",
+ "videoReviewLink": "http://www.remoteproctor.com/AdminSite/Account/Reviewer/DirectLink-Generic.aspx?ID=foo",
+ "examMetaData": {
+ "examCode": attempt_code,
+ "examName": "edX Exams",
+ "examSponsor": "edx LMS",
+ "organization": "edx",
+ "reviewedExam": "True",
+ "reviewerNotes": "Closed Book",
+ "simulatedExam": "False",
+ "ssiExamToken": "4E44F7AA-275D-4741-B531-73AE2407ECFB",
+ "ssiProduct": "rp-now",
+ "ssiRecordLocator": external_id
+ },
+ "desktopComments": [
+ {
+ "comments": "Browsing other websites",
+ "duration": 88,
+ "eventFinish": 88,
+ "eventStart": 12,
+ "eventStatus": "Suspicious"
+ },
+ {
+ "comments": "Browsing local computer",
+ "duration": 88,
+ "eventFinish": 88,
+ "eventStart": 15,
+ "eventStatus": "Rules Violation"
+ },
+ {
+ "comments": "Student never entered the exam.",
+ "duration": 88,
+ "eventFinish": 88,
+ "eventStart": 87,
+ "eventStatus": "Clean"
+ }
+ ],
+ "webCamComments": [
+ {
+ "comments": "Photo ID not provided",
+ "duration": 796,
+ "eventFinish": 796,
+ "eventStart": 0,
+ "eventStatus": "Suspicious"
+ },
+ {
+ "comments": "Exam environment not confirmed",
+ "duration": 796,
+ "eventFinish": 796,
+ "eventStart": 10,
+ "eventStatus": "Rules Violation"
+ },
+ {
+ "comments": "Looking away from computer",
+ "duration": 796,
+ "eventFinish": 796,
+ "eventStart": 107,
+ "eventStatus": "Rules Violation"
+ }
+ ]
+ })
diff --git a/edx_proctoring/backends/tests/test_software_secure.py b/edx_proctoring/backends/tests/test_software_secure.py
index d27c25c627b..b70280b7ab6 100644
--- a/edx_proctoring/backends/tests/test_software_secure.py
+++ b/edx_proctoring/backends/tests/test_software_secure.py
@@ -4,9 +4,10 @@
Tests for the software_secure module
"""
+from __future__ import absolute_import
+
import json
import ddt
-from string import Template # pylint: disable=deprecated-module
from mock import patch
from httmock import all_requests, HTTMock
@@ -41,10 +42,7 @@
ProctoredExamStudentAttemptHistory,
ProctoredExamStudentAllowance
)
-from edx_proctoring.backends.tests.test_review_payload import (
- TEST_REVIEW_PAYLOAD
-)
-
+from edx_proctoring.backends.tests.test_review_payload import create_test_review_payload
from edx_proctoring.tests.test_services import MockCreditService, MockInstructorService
from edx_proctoring.backends.software_secure import SOFTWARE_SECURE_INVALID_CHARS
@@ -110,6 +108,7 @@ def tearDown(self):
"""
When tests are done
"""
+ super(SoftwareSecureTests, self).tearDown()
set_runtime_service('credit', None)
def test_provider_instance(self):
@@ -229,7 +228,7 @@ def assert_get_payload_mock_no_policy(exam, context):
self.assertNotIn('review_policy', context)
# call into real implementation
- result = get_backend_provider(emphemeral=True)._get_payload(exam, context) # pylint: disable=protected-access
+ result = get_backend_provider(emphemeral=True)._get_payload(exam, context)
# assert that we use the default that is defined in system configuration
self.assertEqual(result['reviewerNotes'], constants.DEFAULT_SOFTWARE_SECURE_REVIEW_POLICY)
@@ -256,7 +255,7 @@ def assert_get_payload_mock_no_policy(exam, context):
# patch the _get_payload method on the backend provider
# so that we can assert that we are called with the review policy
# undefined and that we use the system default
- with patch.object(get_backend_provider(), '_get_payload', assert_get_payload_mock_no_policy): # pylint: disable=protected-access
+ with patch.object(get_backend_provider(), '_get_payload', assert_get_payload_mock_no_policy):
attempt_id = create_exam_attempt(
exam_id,
self.user.id,
@@ -291,7 +290,7 @@ def assert_get_payload_mock_unicode_characters(exam, context):
"""
# call into real implementation
- result = get_backend_provider(emphemeral=True)._get_payload(exam, context) # pylint: disable=protected-access
+ result = get_backend_provider(emphemeral=True)._get_payload(exam, context)
self.assertFalse(isinstance(result['examName'], unicode))
self.assertTrue(is_ascii(result['examName']))
self.assertGreater(len(result['examName']), 0)
@@ -308,7 +307,7 @@ def assert_get_payload_mock_unicode_characters(exam, context):
)
# patch the _get_payload method on the backend provider
- with patch.object(get_backend_provider(), '_get_payload', assert_get_payload_mock_unicode_characters): # pylint: disable=protected-access
+ with patch.object(get_backend_provider(), '_get_payload', assert_get_payload_mock_unicode_characters):
attempt_id = create_exam_attempt(
exam_id,
self.user.id,
@@ -326,7 +325,7 @@ def assert_get_payload_mock_unicode_characters(exam, context):
)
# patch the _get_payload method on the backend provider
- with patch.object(get_backend_provider(), '_get_payload', assert_get_payload_mock_unicode_characters): # pylint: disable=protected-access
+ with patch.object(get_backend_provider(), '_get_payload', assert_get_payload_mock_unicode_characters):
attempt_id = create_exam_attempt(
exam_id,
self.user.id,
@@ -475,7 +474,7 @@ def test_review_callback(self, review_status, credit_requirement_status):
attempt = get_exam_attempt_by_id(attempt_id)
self.assertIsNotNone(attempt['external_id'])
- test_payload = Template(TEST_REVIEW_PAYLOAD).substitute(
+ test_payload = create_test_review_payload(
attempt_code=attempt['attempt_code'],
external_id=attempt['external_id']
)
@@ -515,7 +514,7 @@ def test_review_bad_code(self):
"""
provider = get_backend_provider()
- test_payload = Template(TEST_REVIEW_PAYLOAD).substitute(
+ test_payload = create_test_review_payload(
attempt_code='not-here',
external_id='also-not-here'
)
@@ -530,7 +529,7 @@ def test_review_status_code(self):
"""
provider = get_backend_provider()
- test_payload = Template(TEST_REVIEW_PAYLOAD).substitute(
+ test_payload = create_test_review_payload(
attempt_code='not-here',
external_id='also-not-here'
)
@@ -567,7 +566,7 @@ def test_review_mistmatched_tokens(self):
attempt = get_exam_attempt_by_id(attempt_id)
self.assertIsNotNone(attempt['external_id'])
- test_payload = Template(TEST_REVIEW_PAYLOAD).substitute(
+ test_payload = create_test_review_payload(
attempt_code=attempt['attempt_code'],
external_id='bogus'
)
@@ -604,7 +603,7 @@ def test_allow_simulated_callbacks(self):
attempt = get_exam_attempt_by_id(attempt_id)
self.assertIsNotNone(attempt['external_id'])
- test_payload = Template(TEST_REVIEW_PAYLOAD).substitute(
+ test_payload = create_test_review_payload(
attempt_code=attempt['attempt_code'],
external_id='bogus'
)
@@ -644,7 +643,7 @@ def test_review_on_archived_attempt(self):
attempt = get_exam_attempt_by_id(attempt_id)
self.assertIsNotNone(attempt['external_id'])
- test_payload = Template(TEST_REVIEW_PAYLOAD).substitute(
+ test_payload = create_test_review_payload(
attempt_code=attempt['attempt_code'],
external_id=attempt['external_id']
)
@@ -698,7 +697,7 @@ def test_disallow_review_resubmission(self):
attempt = get_exam_attempt_by_id(attempt_id)
self.assertIsNotNone(attempt['external_id'])
- test_payload = Template(TEST_REVIEW_PAYLOAD).substitute(
+ test_payload = create_test_review_payload(
attempt_code=attempt['attempt_code'],
external_id=attempt['external_id']
)
@@ -736,7 +735,7 @@ def test_allow_review_resubmission(self):
attempt = get_exam_attempt_by_id(attempt_id)
self.assertIsNotNone(attempt['external_id'])
- test_payload = Template(TEST_REVIEW_PAYLOAD).substitute(
+ test_payload = create_test_review_payload(
attempt_code=attempt['attempt_code'],
external_id=attempt['external_id']
)
@@ -801,7 +800,7 @@ def test_failure_submission(self, allow_rejects):
attempt = get_exam_attempt_by_id(attempt_id)
- test_payload = Template(TEST_REVIEW_PAYLOAD).substitute(
+ test_payload = create_test_review_payload(
attempt_code=attempt['attempt_code'],
external_id=attempt['external_id']
)
@@ -859,7 +858,7 @@ def test_update_archived_attempt(self):
attempt = get_exam_attempt_by_id(attempt_id)
self.assertIsNotNone(attempt['external_id'])
- test_payload = Template(TEST_REVIEW_PAYLOAD).substitute(
+ test_payload = create_test_review_payload(
attempt_code=attempt['attempt_code'],
external_id=attempt['external_id']
)
diff --git a/edx_proctoring/constants.py b/edx_proctoring/constants.py
index 60382e82115..6bbcce75ce7 100644
--- a/edx_proctoring/constants.py
+++ b/edx_proctoring/constants.py
@@ -2,9 +2,12 @@
Lists of constants that can be used in the edX proctoring
"""
-from django.conf import settings
+from __future__ import absolute_import
+
import datetime
+from django.conf import settings
+
SITE_NAME = (
settings.PROCTORING_SETTINGS['SITE_NAME'] if
'SITE_NAME' in settings.PROCTORING_SETTINGS else settings.SITE_NAME
diff --git a/edx_proctoring/locale/config.yaml b/edx_proctoring/locale/config.yaml
new file mode 100644
index 00000000000..3c318a3e947
--- /dev/null
+++ b/edx_proctoring/locale/config.yaml
@@ -0,0 +1,84 @@
+# Configuration for i18n workflow.
+
+locales:
+ - en # English - Source Language
+ - ar # Arabic
+ - az # Azerbaijani
+ - bg_BG # Bulgarian (Bulgaria)
+ - bn_BD # Bengali (Bangladesh)
+ - bn_IN # Bengali (India)
+ - bs # Bosnian
+ - ca # Catalan
+ - ca@valencia # Catalan (Valencia)
+ - cs # Czech
+ - cy # Welsh
+ - da # Danish
+ - de_DE # German (Germany)
+ - el # Greek
+ - en@lolcat # LOLCAT English
+ - en@pirate # Pirate English
+ - es_419 # Spanish (Latin America)
+ - es_AR # Spanish (Argentina)
+ - es_EC # Spanish (Ecuador)
+ - es_ES # Spanish (Spain)
+ - es_MX # Spanish (Mexico)
+ - es_PE # Spanish (Peru)
+ - et_EE # Estonian (Estonia)
+ - eu_ES # Basque (Spain)
+ - fa # Persian
+ - fa_IR # Persian (Iran)
+ - fi_FI # Finnish (Finland)
+ - fr # French
+ - gl # Galician
+ - gu # Gujarati
+ - he # Hebrew
+ - hi # Hindi
+ - hr # Croatian
+ - hu # Hungarian
+ - hy_AM # Armenian (Armenia)
+ - id # Indonesian
+ - it_IT # Italian (Italy)
+ - ja_JP # Japanese (Japan)
+ - kk_KZ # Kazakh (Kazakhstan)
+ - km_KH # Khmer (Cambodia)
+ - kn # Kannada
+ - ko_KR # Korean (Korea)
+ - lt_LT # Lithuanian (Lithuania)
+ - ml # Malayalam
+ - mn # Mongolian
+ - ms # Malay
+ - nb # Norwegian Bokmål
+ - ne # Nepali
+ - nl_NL # Dutch (Netherlands)
+ - or # Oriya
+ - pl # Polish
+ - pt_BR # Portuguese (Brazil)
+ - pt_PT # Portuguese (Portugal)
+ - ro # Romanian
+ - ru # Russian
+ - si # Sinhala
+ - sk # Slovak
+ - sl # Slovenian
+ - th # Thai
+ - tr_TR # Turkish (Turkey)
+ - uk # Ukranian
+ - ur # Urdu
+ - vi # Vietnamese
+ - zh_CN # Chinese (China)
+ - zh_TW # Chinese (Taiwan)
+
+
+# The locales used for fake-accented English, for testing.
+dummy_locales:
+ - eo
+
+# Directories we don't search for strings.
+ignore_dirs:
+ - docs
+ - edx-proctoring
+ - logs
+ - node_modules
+ - performance
+ - requirements
+ - scripts
+ - settings
diff --git a/edx_proctoring/locale/en/LC_MESSAGES/django.po b/edx_proctoring/locale/en/LC_MESSAGES/django.po
new file mode 100644
index 00000000000..520de7dae99
--- /dev/null
+++ b/edx_proctoring/locale/en/LC_MESSAGES/django.po
@@ -0,0 +1,963 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-12-28 14:09-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: edx_proctoring/admin.py:88
+msgid "internally reviewed"
+msgstr ""
+
+#: edx_proctoring/admin.py:97
+msgid "All Unreviewed"
+msgstr ""
+
+#: edx_proctoring/admin.py:98
+msgid "All Unreviewed Failures"
+msgstr ""
+
+#: edx_proctoring/admin.py:119
+msgid "active proctored exams"
+msgstr ""
+
+#: edx_proctoring/admin.py:177
+msgid "courses with active proctored exams"
+msgstr ""
+
+#: edx_proctoring/admin.py:342
+msgid "Course Id"
+msgstr ""
+
+#: edx_proctoring/admin.py:380
+msgid "Created"
+msgstr ""
+
+#: edx_proctoring/admin.py:381
+msgid "Download Software Clicked"
+msgstr ""
+
+#: edx_proctoring/admin.py:382
+msgid "Ready To Start"
+msgstr ""
+
+#: edx_proctoring/admin.py:383
+msgid "Started"
+msgstr ""
+
+#: edx_proctoring/admin.py:384
+msgid "Ready To Submit"
+msgstr ""
+
+#: edx_proctoring/admin.py:385
+msgid "Declined"
+msgstr ""
+
+#: edx_proctoring/admin.py:386
+msgid "Timed Out"
+msgstr ""
+
+#: edx_proctoring/admin.py:387
+msgid "Submitted"
+msgstr ""
+
+#: edx_proctoring/admin.py:388
+msgid "Second Review Required"
+msgstr ""
+
+#: edx_proctoring/admin.py:389
+msgid "Verified"
+msgstr ""
+
+#: edx_proctoring/admin.py:390
+msgid "Rejected"
+msgstr ""
+
+#: edx_proctoring/admin.py:391
+msgid "Error"
+msgstr ""
+
+#: edx_proctoring/api.py:902
+msgid "your course"
+msgstr ""
+
+#: edx_proctoring/api.py:962
+#, python-brace-format
+msgid "Proctoring Session Results Update for {course_name} {exam_name}"
+msgstr ""
+
+#: edx_proctoring/api.py:1284
+msgid "Taking As Proctored Exam"
+msgstr ""
+
+#: edx_proctoring/api.py:1289
+msgid "Proctored Option Available"
+msgstr ""
+
+#: edx_proctoring/api.py:1294
+msgid "Taking As Open Exam"
+msgstr ""
+
+#: edx_proctoring/api.py:1299 edx_proctoring/api.py:1304
+msgid "Pending Session Review"
+msgstr ""
+
+#: edx_proctoring/api.py:1309
+msgid "Passed Proctoring"
+msgstr ""
+
+#: edx_proctoring/api.py:1314 edx_proctoring/api.py:1319
+msgid "Failed Proctoring"
+msgstr ""
+
+#: edx_proctoring/api.py:1324
+msgid "Proctored Option No Longer Available"
+msgstr ""
+
+#: edx_proctoring/api.py:1333
+msgid "Ungraded Practice Exam"
+msgstr ""
+
+#: edx_proctoring/api.py:1338
+msgid "Practice Exam Completed"
+msgstr ""
+
+#: edx_proctoring/api.py:1343
+msgid "Practice Exam Failed"
+msgstr ""
+
+#: edx_proctoring/api.py:1351
+msgid "Timed Exam"
+msgstr ""
+
+#: edx_proctoring/models.py:180
+msgid "pending"
+msgstr ""
+
+#: edx_proctoring/models.py:181
+msgid "satisfactory"
+msgstr ""
+
+#: edx_proctoring/models.py:182
+msgid "unsatisfactory"
+msgstr ""
+
+#: edx_proctoring/models.py:482
+msgid "Taking as Proctored"
+msgstr ""
+
+#: edx_proctoring/models.py:486
+msgid "Is Sample Attempt"
+msgstr ""
+
+#: edx_proctoring/models.py:707
+msgid "Additional Time (minutes)"
+msgstr ""
+
+#: edx_proctoring/models.py:708
+msgid "Review Policy Exception"
+msgstr ""
+
+#: edx_proctoring/templates/emails/proctoring_attempt_status_email.html:3
+#, python-format
+msgid ""
+"\n"
+"\n"
+"This email is to let you know that the status of your proctoring session "
+"review for %(exam_name)s in\n"
+"%(course_name)s is %(status)s. If you have "
+"any questions about proctoring,\n"
+"contact %(platform)s support at %(contact_email)s.\n"
+"\n"
+msgstr ""
+
+#: edx_proctoring/templates/practice_exam/entrance.html:4
+msgid ""
+"\n"
+" Try a proctored exam\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/practice_exam/entrance.html:9
+msgid ""
+"\n"
+" Get familiar with proctoring for real exams later in the course. This "
+"practice exam has no impact\n"
+" on your grade in the course.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/practice_exam/entrance.html:15
+msgid "Continue to my practice exam"
+msgstr ""
+
+#: edx_proctoring/templates/practice_exam/entrance.html:18
+msgid ""
+"\n"
+" You will be guided through steps to set up online proctoring "
+"software and to perform various checks.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/practice_exam/error.html:4
+msgid ""
+"\n"
+" There was a problem with your practice proctoring session\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/practice_exam/error.html:10
+msgid ""
+"\n"
+" Your practice proctoring results: Unsatisfactory "
+"\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/practice_exam/error.html:16
+msgid ""
+"\n"
+" Your proctoring session ended before you completed this practice "
+"exam.\n"
+" You can retry this practice exam if you had problems setting up the "
+"online proctoring software.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/practice_exam/error.html:25
+msgid "Try this practice exam again"
+msgstr ""
+
+#: edx_proctoring/templates/practice_exam/submitted.html:4
+msgid ""
+"\n"
+" You have submitted this practice proctored exam\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/practice_exam/submitted.html:10
+msgid ""
+"\n"
+" Practice exams do not affect your grade or your credit eligibility.\n"
+" You have completed this practice exam and can continue with your "
+"course work.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/practice_exam/submitted.html:18
+msgid "You can also retry this practice exam"
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/confirm-decline.html:5
+msgid ""
+"\n"
+" Are you sure you want to take this exam without proctoring?\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/confirm-decline.html:10
+msgid ""
+"\n"
+" If you take this exam without proctoring, you will no "
+"longer be eligible for academic credit. \n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/confirm-decline.html:16
+msgid "Continue Exam Without Proctoring"
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/confirm-decline.html:19
+msgid "Go Back"
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/entrance.html:4
+#: edx_proctoring/templates/proctored_exam/failed-prerequisites.html:4
+#: edx_proctoring/templates/proctored_exam/pending-prerequisites.html:4
+msgid ""
+"\n"
+" This exam is proctored\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/entrance.html:9
+msgid ""
+"\n"
+" To be eligible to earn credit for this course, you must take and pass "
+"the proctoring review for this exam.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/entrance.html:14
+msgid "Continue to my proctored exam. I want to be eligible for credit."
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/entrance.html:17
+msgid ""
+"\n"
+" You will be guided through steps to set up online proctoring "
+"software and to perform various checks.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/error.html:4
+msgid ""
+"\n"
+" Error with proctored exam\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/error.html:10
+#, python-format
+msgid ""
+"\n"
+" A technical error has occurred with your proctored exam. To resolve "
+"this problem, contact\n"
+" technical support. All "
+"exam data, including answers\n"
+" for completed problems, has been lost. When the problem is resolved "
+"you will need to restart\n"
+" the exam and complete all problems again.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/error.html:19
+#: edx_proctoring/templates/proctored_exam/expired.html:15
+#: edx_proctoring/templates/proctored_exam/rejected.html:19
+#: edx_proctoring/templates/proctored_exam/submitted.html:29
+#: edx_proctoring/templates/proctored_exam/verified.html:18
+#, python-format
+msgid ""
+"\n"
+" View your credit eligibility status on your Progress page.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/error.html:26
+#: edx_proctoring/templates/proctored_exam/rejected.html:26
+msgid ""
+"\n"
+" If you have concerns about your proctoring session results, contact "
+"your course team.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/expired.html:4
+#: edx_proctoring/templates/timed_exam/expired.html:4
+msgid ""
+"\n"
+" The due date for this exam has passed\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/expired.html:9
+#: edx_proctoring/templates/timed_exam/expired.html:9
+msgid ""
+"\n"
+" Because the due date has passed, you are no longer able to take this "
+"exam.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/failed-prerequisites.html:9
+#, python-format
+msgid ""
+"\n"
+" You did not satisfy the requirements for taking this exam with "
+"proctoring, and are not eligible for credit. See your Progress page for a list of requirements and "
+"your status for each.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/failed-prerequisites.html:14
+msgid ""
+"\n"
+" You did not satisfy the following prerequisites:\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/failed-prerequisites.html:32
+msgid ""
+"\n"
+" Due to unsatisfied prerequisites, you can only take this exam "
+"without proctoring.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/failed-prerequisites.html:40
+#, python-format
+msgid ""
+"\n"
+" If you have questions about the status of your requirements for course "
+"credit, contact %(platform_name)s Support.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/footer.html:5
+msgid ""
+"\n"
+" About Proctored Exams\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/id_verification.html:6
+msgid ""
+"\n"
+" Complete your verification before starting the proctored "
+"exam.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/id_verification.html:11
+msgid ""
+"\n"
+" You must successfully complete identity verification before "
+"you can start the proctored exam.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/id_verification.html:17
+msgid ""
+"\n"
+" Your verification is pending. Results should be "
+"available 2-3 days after you\n"
+" submit your verification.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/id_verification.html:24
+msgid ""
+"\n"
+" Your verification attempt failed. Please read our "
+"guidelines to make\n"
+" sure you understand the requirements for successfully "
+"completing verification,\n"
+" then try again.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/id_verification.html:32
+msgid "Retry Verification"
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/id_verification.html:37
+msgid ""
+"\n"
+" Your verification has expired. You must successfully "
+"complete a new identity verification\n"
+" before you can start the proctored exam.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/id_verification.html:44
+#: edx_proctoring/templates/proctored_exam/id_verification.html:56
+msgid "Continue to Verification"
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/id_verification.html:49
+msgid ""
+"\n"
+" Make sure you are on a computer with a webcam, and that "
+"you have valid photo identification\n"
+" such as a driver's license or passport, before you "
+"continue.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/instructions.html:6
+msgid ""
+"\n"
+" Follow these steps to set up and start your proctored exam.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/instructions.html:11
+msgid ""
+"\n"
+" 1. Copy this unique exam code. You will be prompted to paste this "
+"code later before you start the exam.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/instructions.html:19
+msgid ""
+"\n"
+" Select the exam code, then copy it using Command+C (Mac) or Control"
+"+C (Windows).\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/instructions.html:24
+msgid ""
+"\n"
+" 2. Follow the link below to set up proctoring.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/instructions.html:29
+msgid "Start System Check"
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/instructions.html:32
+msgid ""
+"\n"
+" A new window will open. You will run a system check before "
+"downloading the proctoring application.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/instructions.html:37
+msgid ""
+"\n"
+" You will be asked to verify your identity as part of the proctoring "
+"exam set up.\n"
+" Make sure you are on a computer with a webcam, and that you have "
+"valid photo identification\n"
+" such as a driver's license or passport, before you continue.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/instructions.html:44
+msgid ""
+"\n"
+" 3. When you have finished setting up proctoring, start the exam.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/instructions.html:50
+msgid "Start Proctored Exam"
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/instructions.html:60
+msgid "Close"
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/instructions.html:65
+msgid "Cannot Start Proctored Exam"
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/instructions.html:85
+#: edx_proctoring/templates/proctored_exam/proctoring_opt_out_button.html:5
+msgid "Take this exam without proctoring."
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/instructions.html:87
+msgid "Doing so means that you are no longer eligible for academic credit."
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/pending-prerequisites.html:9
+#, python-format
+msgid ""
+"\n"
+" You have not completed the prerequisites for this exam. All requirements "
+"must be satisfied before you can take this proctored exam and be eligible "
+"for credit. See your Progress page for "
+"a list of requirements in the order that they must be completed.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/pending-prerequisites.html:14
+msgid ""
+"\n"
+" The following prerequisites are in a pending state and "
+"must be successfully completed before you can proceed:\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/pending-prerequisites.html:30
+#, python-format
+msgid ""
+"\n"
+" You can take this exam with proctoring only when all prerequisites have "
+"been successfully completed. Check your Progress page to see if prerequisite results have been updated. You "
+"can also take this exam now without proctoring, but you will not be eligible "
+"for credit.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/proctoring_launch_callback.html:164
+msgid " Your Proctoring Session Has Started "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/proctoring_launch_callback.html:165
+#, python-format
+msgid ""
+" From this point in time, you must follow the online "
+"proctoring rules to pass the proctoring review for your exam. "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/proctoring_launch_callback.html:168
+msgid ""
+"\n"
+" Do not close this window before you finish your exam. if you "
+"close this window, your proctoring session ends, and you will not "
+"successfully complete the proctored exam.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/proctoring_launch_callback.html:173
+#, python-format
+msgid ""
+"\n"
+" Return to the %(platform_name)s course window to start your "
+"exam. When you have finished your exam and\n"
+" have marked it as complete, you can close this window to end the "
+"proctoring session\n"
+" and upload your proctoring session data for review.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/proctoring_opt_out_button.html:8
+msgid ""
+"\n"
+" I am not interested in academic credit.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/ready_to_start.html:5
+msgid ""
+"\n"
+" Follow these instructions\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/ready_to_start.html:10
+#, python-format
+msgid ""
+"\n"
+" • When you start your exam you will have %(total_time)s to "
+"complete it. \n"
+" • You cannot stop the timer once you start. \n"
+" • If time expires before you finish your exam, your completed "
+"answers will be\n"
+" submitted for review. \n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/ready_to_start.html:19
+msgid ""
+"\n"
+" Start my exam\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/ready_to_submit.html:4
+msgid ""
+"\n"
+" Are you sure you want to end your proctored exam?\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/ready_to_submit.html:9
+#: edx_proctoring/templates/timed_exam/ready_to_submit.html:9
+msgid ""
+"\n"
+" Make sure that you have selected \"Submit\" for each problem before "
+"you submit your exam.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/ready_to_submit.html:14
+msgid ""
+"\n"
+" After you submit your exam, your responses are graded and your "
+"proctoring session is reviewed.\n"
+" You might be eligible to earn academic credit for this course if you "
+"complete all required exams\n"
+" as well as achieve a final grade that meets credit requirements for "
+"the course.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/ready_to_submit.html:21
+msgid ""
+"\n"
+" Yes, end my proctored exam\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/ready_to_submit.html:27
+msgid ""
+"\n"
+" No, I'd like to continue working\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/rejected.html:4
+msgid ""
+"\n"
+" Your proctoring session was reviewed and did not pass requirements\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/rejected.html:10
+#, python-format
+msgid ""
+"\n"
+" You are no longer eligible for academic credit for this course, "
+"regardless of your final grade.\n"
+" If you have questions about the status of your proctored exam results, "
+"contact %(platform_name)s Support.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/submitted.html:4
+msgid ""
+"\n"
+" You have submitted this proctored exam for review\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/submitted.html:9
+msgid ""
+"\n"
+" If the proctoring software window is still open, you can close it now. "
+"Confirm that you want to quit the application when you are prompted.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/submitted.html:14
+msgid ""
+"\n"
+" • After you quit the proctoring session, the recorded data is "
+"uploaded for review. \n"
+" • Proctoring results are usually available within 5 business "
+"days after you submit your exam.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/submitted.html:23
+#, python-format
+msgid ""
+"\n"
+" If you have questions about the status of your proctored exam results, "
+"contact %(platform_name)s Support.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/verified.html:4
+msgid ""
+"\n"
+" Your proctoring session was reviewed and passed all requirements\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/verified.html:10
+msgid ""
+"\n"
+" You are eligible to purchase academic credit for this course if you "
+"complete all required exams\n"
+" and also achieve a final grade that meets the credit requirements for "
+"the course.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/visit_exam_content.html:5
+msgid ""
+"\n"
+" To view your exam questions and responses, select View my "
+"exam. The exam's review status is shown in the left navigation "
+"pane.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/visit_exam_content.html:11
+msgid "View my exam"
+msgstr ""
+
+#: edx_proctoring/templates/proctored_exam/visit_exam_content.html:40
+msgid ""
+"\n"
+" After the due date for this exam has passed, you will be able to "
+"review your answers on this page.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/timed_exam/entrance.html:4
+#, python-format
+msgid ""
+"\n"
+" %(exam_name)s is a Timed Exam (%(total_time)s)\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/timed_exam/entrance.html:9
+msgid "This exam has a time limit associated with it."
+msgstr ""
+
+#: edx_proctoring/templates/timed_exam/entrance.html:11
+msgid "To pass this exam, you must complete the problems in the time allowed."
+msgstr ""
+
+#: edx_proctoring/templates/timed_exam/entrance.html:13
+msgid "After you select "
+msgstr ""
+
+#: edx_proctoring/templates/timed_exam/entrance.html:15
+msgid "I am ready to start this timed exam,"
+msgstr ""
+
+#: edx_proctoring/templates/timed_exam/entrance.html:17
+msgid "you will have "
+msgstr ""
+
+#: edx_proctoring/templates/timed_exam/entrance.html:17
+msgid " to complete and submit the exam."
+msgstr ""
+
+#: edx_proctoring/templates/timed_exam/entrance.html:21
+msgid ""
+"\n"
+" I am ready to start this timed exam.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/timed_exam/footer.html:3
+msgid "Can I request additional time to complete my exam?"
+msgstr ""
+
+#: edx_proctoring/templates/timed_exam/footer.html:4
+msgid ""
+"\n"
+" If you have disabilities,\n"
+" you might be eligible for an additional time allowance on timed "
+"exams.\n"
+" Ask your course team for information about additional time "
+"allowances.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/timed_exam/ready_to_submit.html:4
+msgid ""
+"\n"
+" Are you sure that you want to submit your timed exam?\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/timed_exam/ready_to_submit.html:14
+msgid ""
+"\n"
+" After you submit your exam, your exam will be graded.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/timed_exam/ready_to_submit.html:19
+msgid ""
+"\n"
+" Yes, submit my timed exam.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/timed_exam/ready_to_submit.html:25
+msgid ""
+"\n"
+" No, I want to continue working.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/timed_exam/submitted.html:6
+msgid ""
+"\n"
+" The time allotted for this exam has expired. Your exam has been "
+"submitted and any work you completed will be graded.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/timed_exam/submitted.html:10
+msgid ""
+"\n"
+" You have submitted your timed exam.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/timed_exam/submitted.html:18
+#, python-format
+msgid ""
+"\n"
+" Your grade for this timed exam will be immediately available on the Progress page.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/templates/timed_exam/submitted.html:22
+msgid ""
+"\n"
+" After the due date has passed, you can review the exam, but you "
+"cannot change your answers.\n"
+" "
+msgstr ""
+
+#: edx_proctoring/utils.py:76
+#, python-brace-format
+msgid "{num_of_hours} hour"
+msgstr ""
+
+#: edx_proctoring/utils.py:79
+#, python-brace-format
+msgid "{num_of_hours} hours"
+msgstr ""
+
+#: edx_proctoring/utils.py:87 edx_proctoring/utils.py:97
+#, python-brace-format
+msgid "{num_of_minutes} minutes"
+msgstr ""
+
+#: edx_proctoring/utils.py:90
+#, python-brace-format
+msgid " and {num_of_minutes} minute"
+msgstr ""
+
+#: edx_proctoring/utils.py:92
+#, python-brace-format
+msgid "{num_of_minutes} minute"
+msgstr ""
+
+#: edx_proctoring/utils.py:95
+#, python-brace-format
+msgid " and {num_of_minutes} minutes"
+msgstr ""
+
+#: edx_proctoring/views.py:92
+msgid "could not determine the course_id"
+msgstr ""
+
+#: edx_proctoring/views.py:102
+msgid "Must be a Staff User to Perform this request."
+msgstr ""
+
+#: edx_proctoring/views.py:334 edx_proctoring/views.py:546
+#, python-brace-format
+msgid "you have {remaining_time} remaining"
+msgstr ""
+
+#: edx_proctoring/views.py:340
+msgid "you have less than a minute remaining"
+msgstr ""
+
+#: edx_proctoring/views.py:536
+msgid "timed"
+msgstr ""
+
+#: edx_proctoring/views.py:537
+msgid "practice"
+msgstr ""
+
+#: edx_proctoring/views.py:537
+msgid "proctored"
+msgstr ""
diff --git a/edx_proctoring/locale/en/LC_MESSAGES/djangojs.po b/edx_proctoring/locale/en/LC_MESSAGES/djangojs.po
new file mode 100644
index 00000000000..234d88a5f12
--- /dev/null
+++ b/edx_proctoring/locale/en/LC_MESSAGES/djangojs.po
@@ -0,0 +1,127 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-12-28 14:09-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_add_allowance_view.js:132
+msgid "Required field"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_add_allowance_view.js:171
+msgid "Practice Exam"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_add_allowance_view.js:173
+msgid "Proctored Exam"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_add_allowance_view.js:181
+msgid "Timed Exam"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_add_allowance_view.js:200
+msgid "Additional Time"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_add_allowance_view.js:204
+msgid "Value"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_allowance_view.js:13
+#: edx_proctoring/static/proctoring/spec/proctored_exam_add_allowance_spec.js:79
+msgid "Additional Time (minutes)"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_allowance_view.js:14
+#: edx_proctoring/static/proctoring/spec/proctored_exam_add_allowance_spec.js:80
+msgid "Review Policy Exception"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_attempt_view.js:9
+msgid "Eligible"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_attempt_view.js:10
+msgid "Created"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_attempt_view.js:11
+msgid "Download Software Clicked"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_attempt_view.js:12
+msgid "Ready to start"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_attempt_view.js:13
+msgid "Started"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_attempt_view.js:14
+msgid "Ready to submit"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_attempt_view.js:15
+msgid "Declined"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_attempt_view.js:16
+msgid "Timed out"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_attempt_view.js:17
+msgid "Second Review Required"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_attempt_view.js:18
+msgid "Submitted"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_attempt_view.js:19
+msgid "Verified"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_attempt_view.js:20
+msgid "Rejected"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_attempt_view.js:21
+msgid "Error"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_attempt_view.js:160
+msgid "Practice"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_attempt_view.js:162
+msgid "Proctored"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_attempt_view.js:165
+msgid "Timed"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_attempt_view.js:187
+msgid "Are you sure you want to remove this student's exam attempt?"
+msgstr ""
+
+#: edx_proctoring/static/proctoring/js/views/proctored_exam_view.js:134
+msgid ""
+"Are you sure you want to leave this page? \n"
+"To pass your proctored exam you must also pass the online proctoring session "
+"review."
+msgstr ""
diff --git a/locale/fr/LC_MESSAGES/django.po b/edx_proctoring/locale/fr/LC_MESSAGES/django.po
similarity index 100%
rename from locale/fr/LC_MESSAGES/django.po
rename to edx_proctoring/locale/fr/LC_MESSAGES/django.po
diff --git a/edx_proctoring/management/commands/set_attempt_status.py b/edx_proctoring/management/commands/set_attempt_status.py
index 29bbeca5e04..fe4f5467261 100644
--- a/edx_proctoring/management/commands/set_attempt_status.py
+++ b/edx_proctoring/management/commands/set_attempt_status.py
@@ -2,6 +2,8 @@
Django management command to manually set the attempt status for a user in a proctored exam
"""
+from __future__ import absolute_import
+
from optparse import make_option
from django.core.management.base import BaseCommand
diff --git a/edx_proctoring/management/commands/tests/test_set_attempt_status.py b/edx_proctoring/management/commands/tests/test_set_attempt_status.py
index 87ad14d431f..781cc112495 100644
--- a/edx_proctoring/management/commands/tests/test_set_attempt_status.py
+++ b/edx_proctoring/management/commands/tests/test_set_attempt_status.py
@@ -2,6 +2,8 @@
Tests for the set_attempt_status management command
"""
+from __future__ import absolute_import
+
from datetime import datetime
import pytz
diff --git a/edx_proctoring/models.py b/edx_proctoring/models.py
index 5479d041523..27f7f999171 100644
--- a/edx_proctoring/models.py
+++ b/edx_proctoring/models.py
@@ -2,22 +2,30 @@
"""
Data models for the proctoring subsystem
"""
+
+# pylint: disable=model-missing-unicode
+
+from __future__ import absolute_import
+import six
+
+from django.contrib.auth.models import User
from django.db import models
from django.db.models import Q
+from django.db.models.base import ObjectDoesNotExist
from django.db.models.signals import pre_save, pre_delete
from django.dispatch import receiver
+from django.utils.translation import ugettext as _, ugettext_noop
+
from model_utils.models import TimeStampedModel
-from django.utils.translation import ugettext as _
-from django.contrib.auth.models import User
from edx_proctoring.exceptions import (
UserNotFoundException,
ProctoredExamNotActiveException,
AllowanceValueNotAllowedException
)
-from django.db.models.base import ObjectDoesNotExist
+@six.python_2_unicode_compatible
class ProctoredExam(TimeStampedModel):
"""
Information about the Proctored Exam.
@@ -58,11 +66,8 @@ class Meta:
unique_together = (('course_id', 'content_id'),)
db_table = 'proctoring_proctoredexam'
- def __unicode__(self):
- """
- How to serialize myself as a string
- """
-
+ def __str__(self):
+ # pragma: no cover
return u"{course_id}: {exam_name} ({active})".format(
course_id=self.course_id,
exam_name=self.exam_name,
@@ -173,9 +178,9 @@ class ProctoredExamStudentAttemptStatus(object):
# status alias for sending email
status_alias_mapping = {
- submitted: _('pending'),
- verified: _('satisfactory'),
- rejected: _('unsatisfactory')
+ submitted: ugettext_noop('pending'),
+ verified: ugettext_noop('satisfactory'),
+ rejected: ugettext_noop('unsatisfactory')
}
@classmethod
@@ -233,8 +238,10 @@ def get_status_alias(cls, status):
"""
Returns status alias used in email
"""
+ status_alias = cls.status_alias_mapping.get(status, None)
- return cls.status_alias_mapping.get(status, '')
+ # Note that the alias is localized here as it is untranslated in the model
+ return _(status_alias) if status_alias else '' # pylint: disable=translation-of-non-string
@classmethod
def is_valid_status(cls, status):
@@ -244,6 +251,7 @@ def is_valid_status(cls, status):
return cls.is_completed_status(status) or cls.is_incomplete_status(status)
+@six.python_2_unicode_compatible
class ProctoredExamReviewPolicy(TimeStampedModel):
"""
This is how an instructor can set review policies for a proctored exam
@@ -258,6 +266,13 @@ class ProctoredExamReviewPolicy(TimeStampedModel):
# policy that will be passed to reviewers
review_policy = models.TextField()
+ def __str__(self):
+ # pragma: no cover
+ return u"ProctoredExamReviewPolicy: {set_by_user} ({proctored_exam})".format(
+ set_by_user=self.set_by_user,
+ proctored_exam=self.proctored_exam,
+ )
+
class Meta:
""" Meta class for this Django model """
db_table = 'proctoring_proctoredexamreviewpolicy'
@@ -463,11 +478,11 @@ class ProctoredExamStudentAttempt(TimeStampedModel):
# if the user is attempting this as a proctored exam
# in case there is an option to opt-out
- taking_as_proctored = models.BooleanField(default=False, verbose_name=_("Taking as Proctored"))
+ taking_as_proctored = models.BooleanField(default=False, verbose_name=ugettext_noop("Taking as Proctored"))
# Whether this attempt is considered a sample attempt, e.g. to try out
# the proctoring software
- is_sample_attempt = models.BooleanField(default=False, verbose_name=_("Is Sample Attempt"))
+ is_sample_attempt = models.BooleanField(default=False, verbose_name=ugettext_noop("Is Sample Attempt"))
student_name = models.CharField(max_length=255)
@@ -682,8 +697,8 @@ class ProctoredExamStudentAllowance(TimeStampedModel):
# DONT EDIT THE KEYS - THE FIRST VALUE OF THE TUPLE - AS ARE THEY ARE STORED IN THE DATABASE
# THE SECOND ELEMENT OF THE TUPLE IS A DISPLAY STRING AND CAN BE EDITED
- ADDITIONAL_TIME_GRANTED = ('additional_time_granted', _('Additional Time (minutes)'))
- REVIEW_POLICY_EXCEPTION = ('review_policy_exception', _('Review Policy Exception'))
+ ADDITIONAL_TIME_GRANTED = ('additional_time_granted', ugettext_noop('Additional Time (minutes)'))
+ REVIEW_POLICY_EXCEPTION = ('review_policy_exception', ugettext_noop('Review Policy Exception'))
all_allowances = [
ADDITIONAL_TIME_GRANTED + REVIEW_POLICY_EXCEPTION
diff --git a/edx_proctoring/serializers.py b/edx_proctoring/serializers.py
index 64cb8c870dc..b03c7925e81 100644
--- a/edx_proctoring/serializers.py
+++ b/edx_proctoring/serializers.py
@@ -1,7 +1,12 @@
"""Defines serializers used by the Proctoring API."""
+
+from __future__ import absolute_import
+
+from django.contrib.auth.models import User
+
from rest_framework import serializers
from rest_framework.fields import DateTimeField
-from django.contrib.auth.models import User
+
from edx_proctoring.models import (
ProctoredExam,
ProctoredExamStudentAttempt,
@@ -14,7 +19,7 @@ class ProctoredExamSerializer(serializers.ModelSerializer):
"""
Serializer for the ProctoredExam Model.
"""
- id = serializers.IntegerField(required=False)
+ id = serializers.IntegerField(required=False) # pylint: disable=invalid-name
course_id = serializers.CharField(required=True)
content_id = serializers.CharField(required=True)
external_id = serializers.CharField(required=True)
@@ -44,7 +49,7 @@ class UserSerializer(serializers.ModelSerializer):
"""
Serializer for the User Model.
"""
- id = serializers.IntegerField(required=False)
+ id = serializers.IntegerField(required=False) # pylint: disable=invalid-name
username = serializers.CharField(required=True)
email = serializers.CharField(required=True)
diff --git a/edx_proctoring/services.py b/edx_proctoring/services.py
index cfd6bc42cf0..fec943aca3c 100644
--- a/edx_proctoring/services.py
+++ b/edx_proctoring/services.py
@@ -2,6 +2,8 @@
A wrapper class around all methods exposed in api.py
"""
+from __future__ import absolute_import
+
import types
diff --git a/edx_proctoring/static/__init__.py b/edx_proctoring/static/__init__.py
deleted file mode 100644
index dea4ea702ef..00000000000
--- a/edx_proctoring/static/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-We need python think this is a python module
-"""
diff --git a/edx_proctoring/static/proctoring/__init__.py b/edx_proctoring/static/proctoring/__init__.py
deleted file mode 100644
index dea4ea702ef..00000000000
--- a/edx_proctoring/static/proctoring/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-We need python think this is a python module
-"""
diff --git a/edx_proctoring/static/proctoring/js/__init__.py b/edx_proctoring/static/proctoring/js/__init__.py
deleted file mode 100644
index dea4ea702ef..00000000000
--- a/edx_proctoring/static/proctoring/js/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-We need python think this is a python module
-"""
diff --git a/edx_proctoring/static/proctoring/js/collections/__init__.py b/edx_proctoring/static/proctoring/js/collections/__init__.py
deleted file mode 100644
index dea4ea702ef..00000000000
--- a/edx_proctoring/static/proctoring/js/collections/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-We need python think this is a python module
-"""
diff --git a/edx_proctoring/static/proctoring/js/models/__init__.py b/edx_proctoring/static/proctoring/js/models/__init__.py
deleted file mode 100644
index dea4ea702ef..00000000000
--- a/edx_proctoring/static/proctoring/js/models/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-We need python think this is a python module
-"""
diff --git a/edx_proctoring/static/proctoring/js/vendor/__init__.py b/edx_proctoring/static/proctoring/js/vendor/__init__.py
deleted file mode 100644
index dea4ea702ef..00000000000
--- a/edx_proctoring/static/proctoring/js/vendor/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-We need python think this is a python module
-"""
diff --git a/edx_proctoring/static/proctoring/js/views/__init__.py b/edx_proctoring/static/proctoring/js/views/__init__.py
deleted file mode 100644
index dea4ea702ef..00000000000
--- a/edx_proctoring/static/proctoring/js/views/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-We need python think this is a python module
-"""
diff --git a/edx_proctoring/static/proctoring/spec/__init__.py b/edx_proctoring/static/proctoring/spec/__init__.py
deleted file mode 100644
index dea4ea702ef..00000000000
--- a/edx_proctoring/static/proctoring/spec/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-We need python think this is a python module
-"""
diff --git a/edx_proctoring/static/proctoring/templates/__init__.py b/edx_proctoring/static/proctoring/templates/__init__.py
deleted file mode 100644
index dea4ea702ef..00000000000
--- a/edx_proctoring/static/proctoring/templates/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-We need python think this is a python module
-"""
diff --git a/edx_proctoring/templates/__init__.py b/edx_proctoring/templates/__init__.py
deleted file mode 100644
index dea4ea702ef..00000000000
--- a/edx_proctoring/templates/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-We need python think this is a python module
-"""
diff --git a/edx_proctoring/templates/emails/__init__.py b/edx_proctoring/templates/emails/__init__.py
deleted file mode 100644
index dea4ea702ef..00000000000
--- a/edx_proctoring/templates/emails/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-We need python think this is a python module
-"""
diff --git a/edx_proctoring/templates/practice_exam/__init__.py b/edx_proctoring/templates/practice_exam/__init__.py
deleted file mode 100644
index dea4ea702ef..00000000000
--- a/edx_proctoring/templates/practice_exam/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-We need python think this is a python module
-"""
diff --git a/edx_proctoring/templates/proctored_exam/__init__.py b/edx_proctoring/templates/proctored_exam/__init__.py
deleted file mode 100644
index dea4ea702ef..00000000000
--- a/edx_proctoring/templates/proctored_exam/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-We need python think this is a python module
-"""
diff --git a/edx_proctoring/templates/proctored_exam/instructions.html b/edx_proctoring/templates/proctored_exam/instructions.html
index 3f9db3baeac..ada0e197890 100644
--- a/edx_proctoring/templates/proctored_exam/instructions.html
+++ b/edx_proctoring/templates/proctored_exam/instructions.html
@@ -26,7 +26,7 @@
{% endblocktrans %}
- Start System Check
+ {% trans "Start System Check" %}
{% blocktrans %}
diff --git a/edx_proctoring/templates/timed_exam/__init__.py b/edx_proctoring/templates/timed_exam/__init__.py
deleted file mode 100644
index dea4ea702ef..00000000000
--- a/edx_proctoring/templates/timed_exam/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-We need python think this is a python module
-"""
diff --git a/edx_proctoring/tests/__init__.py b/edx_proctoring/tests/__init__.py
index 79087dfd720..e69de29bb2d 100644
--- a/edx_proctoring/tests/__init__.py
+++ b/edx_proctoring/tests/__init__.py
@@ -1,3 +0,0 @@
-"""
-Init.py file for the tests top level directory
-"""
diff --git a/edx_proctoring/tests/test_api.py b/edx_proctoring/tests/test_api.py
index 2ba01d61c3a..c18f885d803 100644
--- a/edx_proctoring/tests/test_api.py
+++ b/edx_proctoring/tests/test_api.py
@@ -4,11 +4,14 @@
"""
All tests for the api.py
"""
-import ddt
+
+from __future__ import absolute_import
+
from datetime import datetime, timedelta
+import ddt
+from freezegun import freeze_time
from mock import patch
import pytz
-from freezegun import freeze_time
from edx_proctoring.api import (
create_exam,
@@ -62,17 +65,14 @@
ProctoredExamStudentAttemptStatus,
ProctoredExamReviewPolicy,
)
-
-from .utils import (
- ProctoredExamTestCase,
-)
+from edx_proctoring.runtime import set_runtime_service, get_runtime_service
from .test_services import (
MockCreditService,
MockCreditServiceNone,
MockCreditServiceWithCourseEndDate,
)
-from edx_proctoring.runtime import set_runtime_service, get_runtime_service
+from .utils import ProctoredExamTestCase
@ddt.ddt
@@ -427,9 +427,8 @@ def test_create_exam_attempt_with_due_datetime(self):
with freeze_time(reset_time):
attempt_id = create_exam_attempt(exam_id, self.user_id)
attempt = get_exam_attempt_by_id(attempt_id)
- self.assertTrue(
- minutes_before_past_due_date - 1 <= attempt['allowed_time_limit_mins'] <= minutes_before_past_due_date
- )
+ self.assertLessEqual(minutes_before_past_due_date - 1, attempt['allowed_time_limit_mins'])
+ self.assertLessEqual(attempt['allowed_time_limit_mins'], minutes_before_past_due_date)
def test_create_an_exam_attempt(self):
"""
diff --git a/edx_proctoring/tests/test_email.py b/edx_proctoring/tests/test_email.py
index a2ac4f719cf..d205e9e4671 100644
--- a/edx_proctoring/tests/test_email.py
+++ b/edx_proctoring/tests/test_email.py
@@ -3,6 +3,8 @@
All tests for proctored exam emails.
"""
+from __future__ import absolute_import
+
import ddt
from django.core import mail
from mock import patch
@@ -117,7 +119,7 @@ def test_send_email_unicode(self):
ProctoredExamStudentAttemptStatus.timed_out,
ProctoredExamStudentAttemptStatus.error
)
- @patch.dict('settings.PROCTORING_SETTINGS', {'ALLOW_TIMED_OUT_STATE': True})
+ @patch.dict('django.conf.settings.PROCTORING_SETTINGS', {'ALLOW_TIMED_OUT_STATE': True})
def test_not_send_email(self, status):
"""
Assert that email is not sent on the following statuses of proctoring attempt.
diff --git a/edx_proctoring/tests/test_models.py b/edx_proctoring/tests/test_models.py
index 7d8a812254a..35585887676 100644
--- a/edx_proctoring/tests/test_models.py
+++ b/edx_proctoring/tests/test_models.py
@@ -3,6 +3,9 @@
"""
All tests for the models.py
"""
+
+from __future__ import absolute_import
+
from edx_proctoring.models import (
ProctoredExam,
ProctoredExamStudentAllowance,
@@ -44,6 +47,14 @@ def test_unicode(self):
output = unicode(proctored_exam)
self.assertEquals(output, u"test_course: अआईउऊऋऌ अआईउऊऋऌ (inactive)")
+ policy = ProctoredExamReviewPolicy.objects.create(
+ set_by_user_id=self.user.id,
+ proctored_exam=proctored_exam,
+ review_policy='Foo Policy'
+ )
+ output = unicode(policy)
+ self.assertEquals(output, u"ProctoredExamReviewPolicy: tester (test_course: अआईउऊऋऌ अआईउऊऋऌ (inactive))")
+
def test_save_proctored_exam_student_allowance_history(self): # pylint: disable=invalid-name
"""
Test to Save and update the proctored Exam Student Allowance object.
diff --git a/edx_proctoring/tests/test_serializer.py b/edx_proctoring/tests/test_serializer.py
index edbdad15d21..aa51d32d961 100644
--- a/edx_proctoring/tests/test_serializer.py
+++ b/edx_proctoring/tests/test_serializer.py
@@ -2,7 +2,10 @@
Tests for the custom StrictBooleanField serializer used by the ProctoredExamSerializer
"""
+from __future__ import absolute_import
+
import unittest
+
from edx_proctoring.serializers import ProctoredExamSerializer
diff --git a/edx_proctoring/tests/test_services.py b/edx_proctoring/tests/test_services.py
index 0fd2eda0540..65329d76f09 100644
--- a/edx_proctoring/tests/test_services.py
+++ b/edx_proctoring/tests/test_services.py
@@ -4,15 +4,18 @@
Test for the xBlock service
"""
+from __future__ import absolute_import
+
+from datetime import datetime, timedelta
+import types
import unittest
import pytz
-from datetime import datetime, timedelta
+
from edx_proctoring.services import (
ProctoringService
)
from edx_proctoring.exceptions import UserNotFoundException
from edx_proctoring import api as edx_proctoring_api
-import types
class MockCreditService(object):
diff --git a/edx_proctoring/tests/test_student_view.py b/edx_proctoring/tests/test_student_view.py
index 67e5f0a4bad..7a125bb6425 100644
--- a/edx_proctoring/tests/test_student_view.py
+++ b/edx_proctoring/tests/test_student_view.py
@@ -5,11 +5,13 @@
All tests for the api.py
"""
-import ddt
+from __future__ import absolute_import
+
from datetime import datetime, timedelta
+import ddt
+from freezegun import freeze_time
from mock import patch
import pytz
-from freezegun import freeze_time
from edx_proctoring.api import (
update_exam,
diff --git a/edx_proctoring/tests/test_utils.py b/edx_proctoring/tests/test_utils.py
index 82bc6e460aa..13cd92f0b4b 100644
--- a/edx_proctoring/tests/test_utils.py
+++ b/edx_proctoring/tests/test_utils.py
@@ -1,7 +1,11 @@
"""
File that contains tests for the util methods.
"""
+
+from __future__ import absolute_import
+
import unittest
+
from edx_proctoring.utils import humanized_time, _emit_event
diff --git a/edx_proctoring/tests/test_views.py b/edx_proctoring/tests/test_views.py
index 9bbb0b569e0..cc92485dbab 100644
--- a/edx_proctoring/tests/test_views.py
+++ b/edx_proctoring/tests/test_views.py
@@ -2,14 +2,17 @@
"""
All tests for the proctored_exams.py
"""
+
+from __future__ import absolute_import
+
+from datetime import datetime, timedelta
import json
-import pytz
import ddt
-from mock import Mock, patch
from freezegun import freeze_time
from httmock import HTTMock
-from string import Template # pylint: disable=deprecated-module
-from datetime import datetime, timedelta
+from mock import Mock, patch
+import pytz
+
from django.test.client import Client
from django.core.urlresolvers import reverse, NoReverseMatch
from django.contrib.auth.models import User
@@ -32,15 +35,13 @@
_calculate_allowed_mins
)
-from .utils import (
- LoggedInTestCase
-)
-
-from edx_proctoring.urls import urlpatterns
-from edx_proctoring.backends.tests.test_review_payload import TEST_REVIEW_PAYLOAD
+from edx_proctoring.backends.tests.test_review_payload import create_test_review_payload
from edx_proctoring.backends.tests.test_software_secure import mock_response_content
-from edx_proctoring.tests.test_services import MockCreditService, MockInstructorService
from edx_proctoring.runtime import set_runtime_service, get_runtime_service
+from edx_proctoring.urls import urlpatterns
+
+from .test_services import MockCreditService, MockInstructorService
+from .utils import LoggedInTestCase
class ProctoredExamsApiTests(LoggedInTestCase):
@@ -493,7 +494,7 @@ def _test_repeated_start_exam_callbacks(self, attempt):
)
attempt = get_exam_attempt_by_id(attempt_id)
self.assertEqual(attempt['status'], "started")
- self.assertFalse(attempt['status'] == "ready_to_start")
+ self.assertNotEqual(attempt['status'], "ready_to_start")
def test_start_exam_create(self):
"""
@@ -679,7 +680,8 @@ def test_timer_remaining_time(self):
self.assertIsNotNone(response_data['started_at'])
self.assertIsNone(response_data['completed_at'])
# check that we get timer around 30 hours minus some seconds
- self.assertTrue(107990 <= response_data['time_remaining_seconds'] <= 108000)
+ self.assertLessEqual(107990, response_data['time_remaining_seconds'])
+ self.assertLessEqual(response_data['time_remaining_seconds'], 108000)
# check that humanized time
self.assertEqual(response_data['accessibility_time_string'], 'you have 30 hours remaining')
@@ -701,9 +703,10 @@ def test_time_due_date_between_two_days(self):
total_minutes, __ = _calculate_allowed_mins(proctored_exam.due_date, proctored_exam.time_limit_mins)
# Check that timer has > 24 hours
- self.assertTrue(total_minutes / 60 > 24)
+ self.assertGreater(total_minutes / 60, 24)
# Get total_minutes around 27 hours. We are checking range here because while testing some seconds have passed.
- self.assertTrue(expected_total_minutes - 1 <= total_minutes <= expected_total_minutes)
+ self.assertLessEqual(expected_total_minutes - 1, total_minutes)
+ self.assertLessEqual(total_minutes, expected_total_minutes)
def test_attempt_ready_to_start(self):
"""
@@ -1668,7 +1671,7 @@ def test_exam_callback(self):
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
- self.assertTrue('exam_attempt_id' in response_data)
+ self.assertIn('exam_attempt_id', response_data)
attempt_id = response_data['exam_attempt_id']
@@ -1725,7 +1728,7 @@ def test_review_callback(self):
attempt = get_exam_attempt_by_id(attempt_id)
self.assertIsNotNone(attempt['external_id'])
- test_payload = Template(TEST_REVIEW_PAYLOAD).substitute(
+ test_payload = create_test_review_payload(
attempt_code=attempt['attempt_code'],
external_id=attempt['external_id']
)
@@ -1763,7 +1766,7 @@ def test_review_caseinsensitive(self):
attempt = get_exam_attempt_by_id(attempt_id)
self.assertIsNotNone(attempt['external_id'])
- test_payload = Template(TEST_REVIEW_PAYLOAD).substitute(
+ test_payload = create_test_review_payload(
attempt_code=attempt['attempt_code'],
external_id=attempt['external_id'].upper()
)
@@ -1800,7 +1803,7 @@ def test_review_bad_contenttype(self):
attempt = get_exam_attempt_by_id(attempt_id)
self.assertIsNotNone(attempt['external_id'])
- test_payload = Template(TEST_REVIEW_PAYLOAD).substitute(
+ test_payload = create_test_review_payload(
attempt_code=attempt['attempt_code'],
external_id=attempt['external_id'].upper()
)
@@ -1837,7 +1840,7 @@ def test_review_mismatch(self):
attempt = get_exam_attempt_by_id(attempt_id)
self.assertIsNotNone(attempt['external_id'])
- test_payload = Template(TEST_REVIEW_PAYLOAD).substitute(
+ test_payload = create_test_review_payload(
attempt_code=attempt['attempt_code'],
external_id='mismatch'
)
diff --git a/edx_proctoring/tests/utils.py b/edx_proctoring/tests/utils.py
index 3a4891b9ac3..a2fa8841de3 100644
--- a/edx_proctoring/tests/utils.py
+++ b/edx_proctoring/tests/utils.py
@@ -5,6 +5,8 @@
Subclasses Django test client to allow for easy login
"""
+from __future__ import absolute_import
+
from datetime import datetime
from importlib import import_module
import pytz
@@ -80,7 +82,7 @@ def setUp(self):
"""
Setup for tests
"""
-
+ super(LoggedInTestCase, self).setUp()
self.client = TestClient()
self.user = User(username='tester', email='tester@test.com')
self.user.save()
@@ -200,6 +202,7 @@ def tearDown(self):
"""
Cleanup
"""
+ super(ProctoredExamTestCase, self).tearDown()
del TRACKERS['default']
def _create_proctored_exam(self):
diff --git a/edx_proctoring/urls.py b/edx_proctoring/urls.py
index 0264e2cd8ba..ea13843538b 100644
--- a/edx_proctoring/urls.py
+++ b/edx_proctoring/urls.py
@@ -1,11 +1,14 @@
"""
URL mappings for edX Proctoring Server.
"""
-from edx_proctoring import views, callbacks
-from django.conf import settings
+from __future__ import absolute_import
+
+from django.conf import settings
from django.conf.urls import patterns, url, include
+from edx_proctoring import views, callbacks
+
urlpatterns = patterns( # pylint: disable=invalid-name
'',
url(
diff --git a/edx_proctoring/utils.py b/edx_proctoring/utils.py
index f393adb02d7..4f6294369e4 100644
--- a/edx_proctoring/utils.py
+++ b/edx_proctoring/utils.py
@@ -2,25 +2,28 @@
Helpers for the HTTP APIs
"""
-import pytz
-import logging
+from __future__ import absolute_import
+
from datetime import datetime, timedelta
+import logging
+import pytz
from django.utils.translation import ugettext as _
+
+from opaque_keys.edx.keys import CourseKey
+from opaque_keys import InvalidKeyError
+
from rest_framework.views import APIView
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
+from eventtracking import tracker
+
from edx_proctoring.models import (
ProctoredExamStudentAttempt,
ProctoredExamStudentAttemptHistory,
)
-# import dependent libraries (in local_requirements.txt otherwise pick up from running Open edX LMS runtime)
-from eventtracking import tracker
-from opaque_keys.edx.keys import CourseKey
-from opaque_keys import InvalidKeyError
-
log = logging.getLogger(__name__)
diff --git a/edx_proctoring/views.py b/edx_proctoring/views.py
index 50f380afbc7..518bfd0a9a7 100644
--- a/edx_proctoring/views.py
+++ b/edx_proctoring/views.py
@@ -2,16 +2,19 @@
Proctored Exams HTTP-based API endpoints
"""
+from __future__ import absolute_import
+
import logging
-from django.utils.translation import ugettext as _
-from django.utils.decorators import method_decorator
from django.conf import settings
+from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.core.urlresolvers import reverse, NoReverseMatch
+from django.utils.translation import ugettext as _
+from django.utils.decorators import method_decorator
from rest_framework import status
from rest_framework.response import Response
-from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
+
from edx_proctoring.api import (
create_exam,
update_exam,
diff --git a/local_requirements.txt b/local_requirements.txt
deleted file mode 100644
index 88d7862d818..00000000000
--- a/local_requirements.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-# Django/Framework Packages
-django>=1.8,<1.9a
-django-model-utils==2.3.1
-djangorestframework>=3.1,<3.2
-django-ipware==1.1.0
-pytz>=2012h
-pycrypto>=2.6
-python-dateutil==2.1
-
-# edX packages
--e git+https://github.com/edx/event-tracking.git@0.2.1#egg=event-tracking==0.2.1
-edx-opaque-keys==0.3.4
diff --git a/manage.py b/manage.py
index f9726f9e67d..a164f327a58 100755
--- a/manage.py
+++ b/manage.py
@@ -1,10 +1,31 @@
#!/usr/bin/env python
+"""
+Django administration utility.
+"""
+
+from __future__ import absolute_import, unicode_literals
+
import os
import sys
-if __name__ == "__main__":
- os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
-
- from django.core.management import execute_from_command_line
+PWD = os.path.abspath(os.path.dirname(__file__))
+if __name__ == '__main__':
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'test_settings')
+ sys.path.append(PWD)
+ try:
+ from django.core.management import execute_from_command_line # pylint: disable=wrong-import-position
+ except ImportError:
+ # The above import may fail for some other reason. Ensure that the
+ # issue is really that Django is missing to avoid masking other
+ # exceptions on Python 2.
+ try:
+ import django # pylint: disable=unused-import, wrong-import-position
+ except ImportError:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ )
+ raise
execute_from_command_line(sys.argv)
diff --git a/openedx.yaml b/openedx.yaml
index 40f13833aa3..0c4c9e2a245 100644
--- a/openedx.yaml
+++ b/openedx.yaml
@@ -2,6 +2,15 @@
# http://open-edx-proposals.readthedocs.io/en/latest/oeps/oep-0002.html#specification
nick: proc
-oeps: {}
owner: edx/teaching-and-learning
+tags:
+ - lms
+oeps:
+ oep-2: True
+ oep-3:
+ state: False
+ reason: TODO - Implement for this application if appropriate
+ oep-5:
+ state: False
+ reason: TODO - Implement for this application if appropriate
track-pulls: true
diff --git a/pylintrc b/pylintrc
index 572b84cb7c4..4c3a0029d9d 100644
--- a/pylintrc
+++ b/pylintrc
@@ -1,260 +1,183 @@
+# ***************************
+# ** DO NOT EDIT THIS FILE **
+# ***************************
+#
+# This file was generated by edx-lint: http://github.com/edx.edx-lint
+#
+# If you want to change this file, you have two choices, depending on whether
+# you want to make a local change that applies only to this repo, or whether
+# you want to make a central change that applies to all repos using edx-lint.
+#
+# LOCAL CHANGE:
+#
+# 1. Edit the local pylintrc_tweaks file to add changes just to this
+# repo's file.
+#
+# 2. Run:
+#
+# $ edx_lint write pylintrc
+#
+# 3. This will modify the local file. Submit a pull request to get it
+# checked in so that others will benefit.
+#
+#
+# CENTRAL CHANGE:
+#
+# 1. Edit the pylintrc file in the edx-lint repo at
+# https://github.com/edx/edx-lint/blob/master/edx_lint/files/pylintrc
+#
+# 2. Make a new version of edx_lint, which involves the usual steps of
+# incrementing the version number, submitting and reviewing a pull
+# request, and updating the edx-lint version reference in this repo.
+#
+# 3. Install the newer version of edx-lint.
+#
+# 4. Run:
+#
+# $ edx_lint write pylintrc
+#
+# 5. This will modify the local file. Submit a pull request to get it
+# checked in so that others will benefit.
+#
+#
+#
+#
+#
+# STAY AWAY FROM THIS FILE!
+#
+#
+#
+#
+#
+# SERIOUSLY.
+#
+# ------------------------------
[MASTER]
-
-# Specify a configuration file.
-#rcfile=
-
-# Python code to execute, usually for sys.path manipulation such as
-# pygtk.require().
-#init-hook=
-
-# Profiled execution.
-profile=no
-
-# Add files or directories to the blacklist. They should be base names, not
-# paths.
-ignore=migrations
-
-# Pickle collected data for later comparisons.
-persistent=yes
-
-# List of plugins (as comma separated values of python modules names) to load,
-# usually to register additional checkers.
-load-plugins=
-
+ignore = migrations
+persistent = yes
+load-plugins = edx_lint.pylint,pylint_django,pylint_celery
[MESSAGES CONTROL]
-
-# Enable the message, report, category or checker with the given id(s). You can
-# either give multiple identifier separated by comma (,) or put this option
-# multiple time. See also the "--disable" option for examples.
-#enable=
-
-# Disable the message, report, category or checker with the given id(s). You
-# can either give multiple identifiers separated by comma (,) or put this
-# option multiple times (only on the command line, not in the configuration
-# file where it should appear only once).You can also use "--disable=all" to
-# disable everything first and then reenable specific checks. For example, if
-# you want to run only the similarities checker, you can use "--disable=all
-# --enable=similarities". If you want to run only the classes checker, but have
-# no Warning level messages displayed, use"--disable=all --enable=classes
-# --disable=W"
-# I0011 locally-disabled (module-level pylint overrides)
-disable=I0011,W0232,too-few-public-methods,abstract-class-little-used,abstract-class-not-used,too-many-public-methods,no-self-use,too-many-instance-attributes,duplicate-code,too-many-arguments,too-many-locals,old-style-class,no-member
-
+disable =
+ locally-disabled,
+ locally-enabled,
+ too-few-public-methods,
+ bad-builtin,
+ star-args,
+ abstract-class-not-used,
+ abstract-class-little-used,
+ no-init,
+ fixme,
+ logging-format-interpolation,
+ too-many-lines,
+ no-self-use,
+ too-many-ancestors,
+ too-many-instance-attributes,
+ too-few-public-methods,
+ too-many-public-methods,
+ too-many-return-statements,
+ too-many-branches,
+ too-many-arguments,
+ too-many-locals,
+ unused-wildcard-import,
+ duplicate-code
[REPORTS]
-
-# Set the output format. Available formats are text, parseable, colorized, msvs
-# (visual studio) and html. You can also give a reporter class, eg
-# mypackage.mymodule.MyReporterClass.
-output-format=text
-
-
-# Put messages in a separate file for each module / package specified on the
-# command line instead of printing them on stdout. Reports (if any) will be
-# written in a file name "pylint_global.[txt|html]".
-files-output=no
-
-# Tells whether to display a full report or only the messages
-reports=yes
-
-# Python expression which should return a note less than 10 (10 is the highest
-# note). You have access to the variables errors warning, statement which
-# respectively contain the number of errors / warnings messages and the total
-# number of statements analyzed. This is used by the global evaluation report
-# (RP0004).
-evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
-
-# Add a comment according to your evaluation note. This is used by the global
-# evaluation report (RP0004).
-comment=no
-
-
-[MISCELLANEOUS]
-
-# List of note tags to take in consideration, separated by a comma.
-notes=FIXME,XXX,TODO
-
-
-[VARIABLES]
-
-# Tells whether we should check for unused import in __init__ files.
-init-import=no
-
-# A regular expression matching the beginning of the name of dummy variables
-# (i.e. not used).
-dummy-variables-rgx=_|dummy
-
-# List of additional names supposed to be defined in builtins. Remember that
-# you should avoid to define new builtins when possible.
-additional-builtins=
-
-
-[TYPECHECK]
-
-# Tells whether missing members accessed in mixin class should be ignored. A
-# mixin class is detected if its name ends with "mixin" (case insensitive).
-ignore-mixin-members=yes
-
-# List of classes names for which member attributes should not be checked
-# (useful for classes with attributes dynamically set).
-ignored-classes=SQLObject,WSGIRequest
-
-# When zope mode is activated, add a predefined set of Zope acquired attributes
-# to generated-members.
-zope=no
-
-# List of members which are set dynamically and missed by pylint inference
-# system, and so shouldn't trigger E0201 when accessed. Python regular
-# expressions are accepted.
-generated-members=REQUEST,acl_users,aq_parent,objects,_meta,id
-
+output-format = text
+files-output = no
+reports = no
+evaluation = 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
[BASIC]
-
-# Required attributes for module, separated by a comma
-required-attributes=
-
-# List of builtins function names that should not be used, separated by a comma
-bad-functions=map,filter,apply,input
-
-# Regular expression which should only match correct module names
-module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
-
-# Regular expression which should only match correct module level names
-const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
-
-# Regular expression which should only match correct class names
-class-rgx=[A-Z_][a-zA-Z0-9]+$
-
-# Regular expression which should only match correct function names
-function-rgx=[a-z_][a-z0-9_]{2,50}$
-
-# Regular expression which should only match correct method names
-method-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression which should only match correct instance attribute names
-attr-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression which should only match correct argument names
-argument-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression which should only match correct variable names
-variable-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression which should only match correct list comprehension /
-# generator expression variable names
-inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
-
-# Good variable names which should always be accepted, separated by a comma
-good-names=i,j,k,ex,Run,_,id,__,log
-
-# Bad variable names which should always be refused, separated by a comma
-bad-names=foo,bar,baz,toto,tutu,tata
-
-# Regular expression which should only match functions or classes name which do
-# not require a docstring
-no-docstring-rgx=__.*__
-
-
-[SIMILARITIES]
-
-# Minimum lines number of a similarity.
-min-similarity-lines=4
-
-# Ignore comments when computing similarities.
-ignore-comments=yes
-
-# Ignore docstrings when computing similarities.
-ignore-docstrings=yes
-
-# Ignore imports when computing similarities.
-ignore-imports=no
-
+bad-functions = map,filter,apply,input
+module-rgx = (([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+const-rgx = (([A-Z_][A-Z0-9_]*)|(__.*__)|log|urlpatterns)$
+class-rgx = [A-Z_][a-zA-Z0-9]+$
+function-rgx = ([a-z_][a-z0-9_]{2,40}|test_[a-z0-9_]+)$
+method-rgx = ([a-z_][a-z0-9_]{2,40}|setUp|set[Uu]pClass|tearDown|tear[Dd]ownClass|assert[A-Z]\w*|maxDiff|test_[a-z0-9_]+)$
+attr-rgx = [a-z_][a-z0-9_]{2,30}$
+argument-rgx = [a-z_][a-z0-9_]{2,30}$
+variable-rgx = [a-z_][a-z0-9_]{2,30}$
+class-attribute-rgx = ([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+inlinevar-rgx = [A-Za-z_][A-Za-z0-9_]*$
+good-names = f,i,j,k,db,ex,Run,_,__
+bad-names = foo,bar,baz,toto,tutu,tata
+no-docstring-rgx = __.*__$|test_.+|setUp$|setUpClass$|tearDown$|tearDownClass$|Meta$
+docstring-min-length = -1
[FORMAT]
+max-line-length = 120
+ignore-long-lines = ^\s*(# )??$
+single-line-if-stmt = no
+no-space-check = trailing-comma,dict-separator
+max-module-lines = 1000
+indent-string = ' '
-# Maximum number of characters on a single line.
-max-line-length=120
-
-# Maximum number of lines in a module
-max-module-lines=1000
-
-# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
-# tab).
-indent-string=' '
-
-
-[IMPORTS]
-
-# Deprecated modules which should not be used, separated by a comma
-deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
-
-# Create a graph of every (i.e. internal and external) dependencies in the
-# given file (report RP0402 must not be disabled)
-import-graph=
-
-# Create a graph of external dependencies in the given file (report RP0402 must
-# not be disabled)
-ext-import-graph=
-
-# Create a graph of internal dependencies in the given file (report RP0402 must
-# not be disabled)
-int-import-graph=
-
-
-[DESIGN]
-
-# Maximum number of arguments for function / method
-max-args=5
-
-# Argument names that match this expression will be ignored. Default to name
-# with leading underscore
-ignored-argument-names=_.*
-
-# Maximum number of locals for function / method body
-max-locals=15
-
-# Maximum number of return / yield for function / method body
-max-returns=6
-
-# Maximum number of branch for function / method body
-max-branchs=12
-
-# Maximum number of statements in function / method body
-max-statements=50
-
-# Maximum number of parents for a class (see R0901).
-max-parents=7
-
-# Maximum number of attributes for a class (see R0902).
-max-attributes=7
+[MISCELLANEOUS]
+notes = FIXME,XXX,TODO
-# Minimum number of public methods for a class (see R0903).
-min-public-methods=2
+[SIMILARITIES]
+min-similarity-lines = 4
+ignore-comments = yes
+ignore-docstrings = yes
+ignore-imports = no
-# Maximum number of public methods for a class (see R0904).
-max-public-methods=20
+[TYPECHECK]
+ignore-mixin-members = yes
+ignored-classes = SQLObject
+unsafe-load-any-extension = yes
+generated-members =
+ REQUEST,
+ acl_users,
+ aq_parent,
+ objects,
+ DoesNotExist,
+ can_read,
+ can_write,
+ get_url,
+ size,
+ content,
+ status_code,
+ create,
+ build,
+ fields,
+ tag,
+ org,
+ course,
+ category,
+ name,
+ revision,
+ _meta,
+[VARIABLES]
+init-import = no
+dummy-variables-rgx = _|dummy|unused|.*_unused
+additional-builtins =
[CLASSES]
+defining-attr-methods = __init__,__new__,setUp
+valid-classmethod-first-arg = cls
+valid-metaclass-classmethod-first-arg = mcs
-# List of interface methods to ignore, separated by a comma. This is used for
-# instance to not check methods defines in Zope's Interface base class.
-ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
-
-# List of method names used to declare (i.e. assign) instance attributes.
-defining-attr-methods=__init__,__new__,setUp
-
-# List of valid names for the first argument in a class method.
-valid-classmethod-first-arg=cls
-
-# List of valid names for the first argument in a metaclass class method.
-valid-metaclass-classmethod-first-arg=mcs
+[DESIGN]
+max-args = 5
+ignored-argument-names = _.*
+max-locals = 15
+max-returns = 6
+max-branches = 12
+max-statements = 50
+max-parents = 7
+max-attributes = 7
+min-public-methods = 2
+max-public-methods = 20
+[IMPORTS]
+deprecated-modules = regsub,TERMIOS,Bastion,rexec
+import-graph =
+ext-import-graph =
+int-import-graph =
[EXCEPTIONS]
+overgeneral-exceptions = Exception
-# Exceptions that will emit a warning when being caught. Defaults to
-# "Exception"
-overgeneral-exceptions=Exception
+# 941d509de1467c9055cf96d19d63c87893403eda
diff --git a/pylintrc_tweaks b/pylintrc_tweaks
new file mode 100644
index 00000000000..4764e22f7f7
--- /dev/null
+++ b/pylintrc_tweaks
@@ -0,0 +1,4 @@
+# pylintrc tweaks for use with edx_lint.
+[MASTER]
+ignore = migrations
+load-plugins = edx_lint.pylint,pylint_django,pylint_celery
diff --git a/requirements.txt b/requirements.txt
index db6dfc678af..868011f9f10 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1 @@
# Empty so that we will use the versions of dependencies installed in edx-platform.
-# See local_requirements.txt for the requirements needed for local development.
diff --git a/requirements/base.in b/requirements/base.in
new file mode 100644
index 00000000000..d3cbe6c2a9f
--- /dev/null
+++ b/requirements/base.in
@@ -0,0 +1,14 @@
+# Core requirements for using this application
+
+# Django/Framework Packages
+django>=1.8,<1.9a
+django-model-utils>=2.3.1
+djangorestframework>=3.1
+django-ipware>=1.1.0
+pytz>=2012h
+pycrypto>=2.6
+python-dateutil>=2.1
+
+# edX packages
+-e git+https://github.com/edx/event-tracking.git@0.2.2#egg=event-tracking==0.2.2
+edx-opaque-keys>=0.4
diff --git a/requirements/dev.in b/requirements/dev.in
new file mode 100644
index 00000000000..30e56bacc9d
--- /dev/null
+++ b/requirements/dev.in
@@ -0,0 +1,12 @@
+# Additional requirements for development of this application
+
+diff-cover # Changeset diff test coverage
+edx-lint # For updating pylintrc
+edx-i18n-tools # For i18n_tool dummy
+pip-tools # Requirements file management
+tox # virtualenv management for tests
+tox-battery # Makes tox aware of requirements file changes
+twine # Utility for PyPI package uploads
+wheel # For generation of wheels for PyPI
+
+django>=1.8,<1.9a
diff --git a/requirements/doc.in b/requirements/doc.in
new file mode 100644
index 00000000000..309998f4e34
--- /dev/null
+++ b/requirements/doc.in
@@ -0,0 +1,7 @@
+# Requirements for documentation validation
+
+doc8 # reStructuredText style checker
+edx_sphinx_theme # edX theme for Sphinx output
+readme_renderer # Validates README.rst for usage on PyPI
+Sphinx # Documentation builder
+sphinxcontrib-napoleon # Google Style docstring support for sphinx
diff --git a/requirements/private.readme b/requirements/private.readme
new file mode 100644
index 00000000000..5600a1075bc
--- /dev/null
+++ b/requirements/private.readme
@@ -0,0 +1,15 @@
+# If there are any Python packages you want to keep in your virtualenv beyond
+# those listed in the official requirements files, create a "private.in" file
+# and list them there. Generate the corresponding "private.txt" file pinning
+# all of their indirect dependencies to specific versions as follows:
+
+# pip-compile private.in
+
+# This allows you to use "pip-sync" without removing these packages:
+
+# pip-sync requirements/*.txt
+
+# "private.in" and "private.txt" aren't checked into git to avoid merge
+# conflicts, and the presence of this file allows "private.*" to be
+# included in scripted pip-sync usage without requiring that those files be
+# created first.
diff --git a/requirements/quality.in b/requirements/quality.in
new file mode 100644
index 00000000000..b19fff084fe
--- /dev/null
+++ b/requirements/quality.in
@@ -0,0 +1,7 @@
+# Requirements for code quality checks
+
+caniusepython3 # Additional Python 3 compatibility pylint checks
+edx-lint # edX pylint rules and plugins
+isort # to standardize order of imports
+pycodestyle # PEP 8 compliance validation
+pydocstyle # PEP 257 compliance validation
diff --git a/requirements/test.in b/requirements/test.in
new file mode 100644
index 00000000000..63e3e825c23
--- /dev/null
+++ b/requirements/test.in
@@ -0,0 +1,18 @@
+# Requirements for test runs.
+
+pytest-catchlog # Show log output for test failures
+pytest-cov # pytest extension for code coverage statistics
+pytest-django # pytest extension for better Django support
+
+bok-choy>=0.3.1
+ddt>=0.8.0
+django_nose>=1.4.1
+freezegun>=0.3.1
+httmock>=1.2.3
+httpretty>=0.8.0
+logilab-common>=0.63.2
+mock==1.0.1
+nose>=1.3.3
+selenium>=2.45.0
+sure==1.2.7
+testfixtures>=4.0.0
diff --git a/run_tests b/run_tests
deleted file mode 100755
index 2715b0115f3..00000000000
--- a/run_tests
+++ /dev/null
@@ -1,15 +0,0 @@
-ECHO 'Beginning Test Run...'
-ECHO ''
-ECHO 'Removing *.pyc files'
-find . -name "*.pyc" -exec rm -rf {} \;
-
-ECHO 'Running test suite'
-coverage run manage.py test edx_proctoring --verbosity=3
-coverage report -m
-coverage html
-pep8 edx_proctoring
-pylint edx_proctoring --report=no
-ECHO ''
-ECHO 'View the full coverage report at {CODE_PATH}/edx-proctoring/htmlcov/index.html'
-ECHO ''
-ECHO 'Testing Complete!'
diff --git a/setup.cfg b/setup.cfg
index 4ca4f955b8d..2830de93501 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,4 +1,10 @@
-[pep8]
-ignore=E501
-max_line_length=119
-exclude=settings,*/migrations/*
+[isort]
+line_length = 120
+known_edx =
+known_django = django
+known_djangoapp = model_utils
+known_first_party = edx_proctoring
+sections = FUTURE,STDLIB,THIRDPARTY,DJANGO,DJANGOAPP,EDX,FIRSTPARTY,LOCALFOLDER
+
+[wheel]
+universal = 1
diff --git a/setup.py b/setup.py
index d81db1c371c..2cd10ca33c4 100755
--- a/setup.py
+++ b/setup.py
@@ -1,60 +1,65 @@
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# pylint: disable=C0111,W6005,W6100
+from __future__ import absolute_import, print_function
-from setuptools import setup, find_packages
+import os
+import re
+import sys
-def is_requirement(line):
- """
- Return True if the requirement line is a package requirement;
- that is, it is not blank, a comment, or editable.
- """
- # Remove whitespace at the start/end of the line
- line = line.strip()
-
- # Skip blank lines, comments, and editable installs
- return not (
- line == '' or
- line.startswith('-r') or
- line.startswith('#') or
- line.startswith('-e') or
- line.startswith('git+')
- )
-
-def load_requirements(*requirements_paths):
+from setuptools import setup
+
+
+def get_version(*file_paths):
"""
- Load all requirements from the specified requirements files.
- Returns a list of requirement strings.
+ Extract the version string from the file at the given relative path fragments.
"""
- requirements = set()
- for path in requirements_paths:
- requirements.update(
- line.strip() for line in open(path).readlines()
- if is_requirement(line)
- )
- return list(requirements)
+ filename = os.path.join(os.path.dirname(__file__), *file_paths)
+ version_file = open(filename).read()
+ version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
+ version_file, re.M)
+ if version_match:
+ return version_match.group(1)
+ raise RuntimeError('Unable to find version string.')
+
+
+VERSION = get_version('edx_proctoring', '__init__.py')
+
+if sys.argv[-1] == 'tag':
+ print("Tagging the version on github:")
+ os.system("git tag -a %s -m 'version %s'" % (VERSION, VERSION))
+ os.system("git push --tags")
+ sys.exit()
+
+README = open(os.path.join(os.path.dirname(__file__), 'README.rst')).read()
+CHANGELOG = open(os.path.join(os.path.dirname(__file__), 'CHANGELOG.rst')).read()
setup(
name='edx-proctoring',
- version='0.16.2',
+ version=VERSION,
description='Proctoring subsystem for Open edX',
- long_description=open('README.md').read(),
+ long_description=README + '\n\n' + CHANGELOG,
author='edX',
+ author_email='oscm@edx.org',
url='https://github.com/edx/edx-proctoring',
- license='AGPL',
+ license="AGPL 3.0",
+ zip_safe=False,
+ keywords='Django edx',
classifiers=[
- 'Development Status :: 4 - Beta',
- 'Environment :: Web Environment',
- 'Intended Audience :: Developers',
- 'License :: OSI Approved :: GNU Affero General Public License v3',
- 'Operating System :: OS Independent',
- 'Programming Language :: Python',
+ 'Development Status :: 3 - Alpha',
'Framework :: Django',
+ 'Framework :: Django :: 1.8',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)',
+ 'Natural Language :: English',
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 2.7',
+ ],
+ packages=[
+ 'edx_proctoring',
],
- packages=find_packages(exclude=["tests"]),
- package_data={
- '': ['*.html', '*.underscore', '*.png', '*.js', '*swf']
- },
- dependency_links=[
+ include_package_data=True,
+ install_requires=[
+ "Django>=1.8,<1.11"
],
- install_requires=load_requirements('requirements.txt'),
- tests_require=load_requirements('test_requirements.txt')
)
diff --git a/test_requirements.txt b/test_requirements.txt
deleted file mode 100644
index 5460f17b7b3..00000000000
--- a/test_requirements.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-# Testing Packages
-logilab-common==0.63.2
-coverage==3.7.1
-astroid==1.3.4
-django_nose==1.4.1
-nose==1.3.3
-httpretty==0.8.0
-pep8==1.6.2
-pylint==1.4.2
-pep257==0.3.2
-mock==1.0.1
-testfixtures==4.0.0
-bok-choy>=0.3.1
-sure==1.2.7
-ddt==0.8.0
-selenium>=2.45.0
-freezegun==0.3.1
-httmock==1.2.3
diff --git a/settings.py b/test_settings.py
similarity index 89%
rename from settings.py
rename to test_settings.py
index 343d02ef787..ddc2eaf37f1 100644
--- a/settings.py
+++ b/test_settings.py
@@ -1,6 +1,12 @@
"""
-Django settings file for local development purposes
+These settings are here to use during tests, because Django requires them.
+
+In a real-world use case, apps in this project are installed into other
+Django applications, so these settings will not be used.
"""
+
+from __future__ import absolute_import, unicode_literals
+
import sys
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 00000000000..e7830469895
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,58 @@
+[tox]
+envlist = {py27}-django{18}
+
+[doc8]
+max-line-length = 120
+
+[pycodestyle]
+exclude = .git,.tox,migrations
+max-line-length = 120
+
+[pydocstyle]
+; D101 = Missing docstring in public class
+; D200 = One-line docstring should fit on one line with quotes
+; D203 = 1 blank line required before class docstring
+; D212 = Multi-line docstring summary should start at the first line
+ignore = D101,D200,D203,D212
+match-dir = (?!migrations)
+
+[testenv]
+setenv =
+ DJANGO_SETTINGS_MODULE = test_settings
+deps =
+ django18: Django>=1.8,<1.9
+ django19: Django>=1.9,<1.10
+ django110: Django>=1.10,<1.11
+ -rrequirements/test.txt
+commands =
+ coverage run ./manage.py test {posargs}
+
+[testenv:docs]
+setenv =
+ DJANGO_SETTINGS_MODULE = test_settings
+ PYTHONPATH = {toxinidir}
+whitelist_externals =
+ make
+ rm
+deps =
+ -r{toxinidir}/requirements/doc.txt
+commands =
+ doc8 --ignore-path docs/_build README.rst docs
+ rm -f docs/edx_proctoring.rst
+ rm -f docs/modules.rst
+ make -C docs clean
+ make -C docs html
+ python setup.py check --restructuredtext --strict
+
+[testenv:quality]
+whitelist_externals =
+ make
+ rm
+ touch
+deps =
+ -r{toxinidir}/requirements/doc.txt
+ -r{toxinidir}/requirements/quality.txt
+ -r{toxinidir}/requirements/test.txt
+commands =
+ pylint edx_proctoring
+ pycodestyle edx_proctoring