diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f2cb682d54b107a844e2bac2d0f2e7c74aac701b..41b235c07c0abfd9fe79bc5804b1e872e70b882f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,15 +1,3 @@
-"Python 2.7":
- script:
- - "PY_EXE=python2.7 bash ./run-tests-for-ci.sh"
- tags:
- - python2.7
- - linux
- except:
- - tags
- coverage: '/TOTAL.+ ([0-9]{1,3}%)/'
- variables:
- CODECOV_TOKEN: "895e3bf2-cfd0-45f8-9a14-4b7bd148f76d"
-
Python 3:
script:
- "PY_EXE=python3 bash ./run-tests-for-ci.sh"
diff --git a/.travis.yml b/.travis.yml
index 112f62f047663d7dabf1795fa85d5b769adde0b8..d97eea7cebc328494d282183b385ca30f99ebcba 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,8 +3,6 @@ cache: pip
install: true
matrix:
include:
- - python: "2.7"
- env: RL_TRAVIS_TEST=test PY_EXE=python2.7
- python: "3.5"
env: RL_TRAVIS_TEST=test PY_EXE=python3.5
- python: "3.6"
diff --git a/course/auth.py b/course/auth.py
index 1ab1ae741bad889e8d7bf34e2dfbcf1eb710af47..dd264f00c95fc3310b685167389ae0c5fc8f270a 100644
--- a/course/auth.py
+++ b/course/auth.py
@@ -332,7 +332,7 @@ def logout_confirmation_required(
class EmailedTokenBackend(object):
- def authenticate(self, user_id=None, token=None):
+ def authenticate(self, request, user_id=None, token=None):
users = get_user_model().objects.filter(
id=user_id, sign_in_key=token)
@@ -405,8 +405,10 @@ def sign_in_by_user_pw(request, redirect_field_name=REDIRECT_FIELD_NAME):
if form.is_valid():
# Ensure the user-originating redirection url is safe.
- if not is_safe_url(url=redirect_to, host=request.get_host(),
- require_https=request.is_secure()):
+ if not is_safe_url(
+ url=redirect_to,
+ allowed_hosts=set([request.get_host()]),
+ require_https=request.is_secure()):
redirect_to = resolve_url("relate-home")
user = form.get_user()
@@ -1128,7 +1130,7 @@ def find_matching_token(
class APIBearerTokenBackend(object):
- def authenticate(self, course_identifier=None, token_id=None,
+ def authenticate(self, request, course_identifier=None, token_id=None,
token_hash_str=None, now_datetime=None):
token = find_matching_token(course_identifier, token_id, token_hash_str,
now_datetime)
diff --git a/course/exam.py b/course/exam.py
index b2321bd01e0e08848621147fbb33e7c13af2d525..ea8dbdf2a018bf2ebea09b52090f559be5083731 100644
--- a/course/exam.py
+++ b/course/exam.py
@@ -498,7 +498,7 @@ def check_exam_ticket(
class ExamTicketBackend(object):
- def authenticate(self, username=None, code=None, now_datetime=None,
+ def authenticate(self, request, username=None, code=None, now_datetime=None,
facilities=None):
is_valid, msg = check_exam_ticket(username, code, now_datetime, facilities)
diff --git a/course/page/upload.py b/course/page/upload.py
index ee6c6948f22c1da96c09d901ffddb4170586c21c..cb2618de84c6fb9c7b3afc0254179ff4550c8524 100644
--- a/course/page/upload.py
+++ b/course/page/upload.py
@@ -69,12 +69,12 @@ class FileUploadForm(StyledForm):
uploaded_file = self.cleaned_data['uploaded_file']
from django.template.defaultfilters import filesizeformat
- if uploaded_file._size > self.max_file_size:
+ if uploaded_file.size > self.max_file_size:
raise forms.ValidationError(
_("Please keep file size under %(allowedsize)s. "
"Current filesize is %(uploadedsize)s.")
% {'allowedsize': filesizeformat(self.max_file_size),
- 'uploadedsize': filesizeformat(uploaded_file._size)})
+ 'uploadedsize': filesizeformat(uploaded_file.size)})
if self.mime_types is not None and self.mime_types == ["application/pdf"]:
if uploaded_file.read()[:4] != b"%PDF":
diff --git a/course/templates/course/generic-course-form.html b/course/templates/course/generic-course-form.html
index 4ebf2909d8570a05b3f70abafa7f229553321f99..8972680578b29b5b761be503e47bc52a9beaea5f 100644
--- a/course/templates/course/generic-course-form.html
+++ b/course/templates/course/generic-course-form.html
@@ -1,5 +1,6 @@
{% extends "course/course-base.html" %}
{% load i18n %}
+{% load static %}
{% load crispy_forms_tags %}
@@ -7,6 +8,16 @@
{{ form_description }} - {{ relate_site_name }}
{% endblock %}
+{# keep this in sync with relate/templates/generic-form.html #}
+{% block head_assets_form_media %}
+
+
+
+
+
+ {{ form.media }}
+{% endblock %}
+
{% block content %}
{% if form_description %}
{{ form_description }}
diff --git a/doc/misc.rst b/doc/misc.rst
index 49b7c5ef36fbf04572e1636d9a53938915848b29..4456903913c003c79d1e437fbf365c9c3aa0355b 100644
--- a/doc/misc.rst
+++ b/doc/misc.rst
@@ -1,8 +1,7 @@
Installation
============
-RELATE currently works with Python 2.7 and Python 3. (By default, :file:`requirements.txt`
-is set up for Python 3. See below for edit instructions if you are using Python 2.)
+RELATE requires Python 3.
Install [Node.js](https://nodejs.org) and NPM, or [Yarn](https://yarnpkg.com)
(alternative package manager) at your option.
diff --git a/manage.py b/manage.py
index a47f2ea67e45b69585b30d8c663f9464ca2dc3b0..b7d4dc98e215b2a32e7a907e9c9ffe5fcbbbf100 100755
--- a/manage.py
+++ b/manage.py
@@ -11,8 +11,11 @@ def get_local_test_settings_file(argv):
assert os.path.isfile(os.path.join(local_settings_dir, "manage.py"))
from django.core.management import CommandParser, CommandError
- parser = CommandParser(None, usage="%(prog)s subcommand [options] [args]",
- add_help=False)
+
+ parser = CommandParser(
+ usage="%(prog)s subcommand [options] [args]",
+ add_help=False)
+
parser.add_argument('--local_test_settings',
dest="local_test_settings")
diff --git a/package.json b/package.json
index 7f3613cc7ce768a055eb971518288eb76db55305..468a1d5f3841f4b205e061c158e4637b032bb603 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"datatables.net-fixedcolumns": "^3.2.4",
"datatables.net-fixedcolumns-bs": "^3.2.4",
"datatables-i18n": "git+https://github.com/dzhuang/datatables-i18n.git",
+ "eonasdan-bootstrap-datetimepicker": "^4.17.47",
"font-awesome": "^4.7.0",
"fullcalendar": "^2.9.1",
"jquery": "^3.3.1",
diff --git a/relate/settings.py b/relate/settings.py
index fc3603a2d579687bdbd5d421926503e1c0a930f7..64ee658e6d24a336f91a60332887d7b29893b250 100644
--- a/relate/settings.py
+++ b/relate/settings.py
@@ -53,7 +53,6 @@ INSTALLED_APPS = (
"django.contrib.staticfiles",
"crispy_forms",
"jsonfield",
- "bootstrap3_datetime",
"django_select2",
# message queue
diff --git a/relate/templates/base.html b/relate/templates/base.html
index 9354cc0e2bb73a2a25d3b3f47b8c77b2b281c300..f58bb5b361df1e1721db6150ee338c0deac0f53d 100644
--- a/relate/templates/base.html
+++ b/relate/templates/base.html
@@ -20,7 +20,6 @@
{# Don't be tempted to move all this JS stuff down to the end. #}
- {# The datepicker generates inline JS that relies on this being loaded. #}
diff --git a/relate/templates/generic-form.html b/relate/templates/generic-form.html
index 41a92225a3a45ce666b6921da15b7b0a342cf3d8..ca56a1667166392c71062e76e0e408a94de82d13 100644
--- a/relate/templates/generic-form.html
+++ b/relate/templates/generic-form.html
@@ -1,5 +1,6 @@
{% extends "base.html" %}
{% load i18n %}
+{% load static %}
{% load crispy_forms_tags %}
@@ -7,6 +8,16 @@
{{ form_description }} - {{ relate_site_name }}
{% endblock %}
+{# keep this in sync with course/templates/course/generic-course-form.html #}
+{% block head_assets_form_media %}
+
+
+
+
+
+ {{ form.media }}
+{% endblock %}
+
{% block content %}
{% block form_description %}
{% if form_description %}
diff --git a/requirements.txt b/requirements.txt
index 1b855b200bdb664aa5b0c39abc6c37b6de09c8ac..0dd6b33dce502657b4e2779893969a01401c4438 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-django>=1.10,<2.1
+django>=2.1.10
# Automatically renders Django forms in a pretty, Bootstrap-compatible way.
django-crispy-forms>=1.5.1
@@ -33,7 +33,8 @@ ecdsa
paramiko
# A date picker widget
-git+https://github.com/inducer/django-bootstrap3-datetimepicker.git#egg=django-bootstrap3-datetimepicker
+# https://github.com/tutorcruncher/django-bootstrap3-datetimepicker
+django-bootstrap3-datetimepicker-2
# For in-class instant messaging
dnspython
@@ -44,7 +45,9 @@ django-npm
# For comfortable code entry
-django-codemirror-widget
+# django-codemirror-widget
+# https://github.com/lambdalisue/django-codemirror-widget/pull/29
+git+https://github.com/inducer/django-codemirror-widget.git#egg=django-codemirror-widget
# Optional, used for caching, requires 'libmemcached-dev' (Debian package name)
# pylibmc
diff --git a/run-tests-for-ci.sh b/run-tests-for-ci.sh
index 5d57804e4b9187571401014b90d2c7062ebe44bc..7d8aff57e29bf80b87fb79f94e475b2110b991e1 100644
--- a/run-tests-for-ci.sh
+++ b/run-tests-for-ci.sh
@@ -52,15 +52,7 @@ export PATH=`pwd`/.env/local/bin:$PATH
PIP="${PY_EXE} $(which pip)"
-if [[ "$PY_EXE" = python2* ]]; then
- grep -Ev "django>|django=" requirements.txt > req.txt
- $PIP install "django<2"
- $PIP install mock
-else
- cp requirements.txt req.txt
-fi
-
-$PIP install -r req.txt
+$PIP install -r requirements.txt
cp local_settings_example.py local_settings.py
@@ -90,9 +82,7 @@ if [[ -n $(grep "msgid" output.txt) ]]; then
exit 1;
fi
-if [[ "$PY_EXE" = python3* ]]; then
- ${PY_EXE} manage.py compilemessages
-fi
+${PY_EXE} manage.py compilemessages
$PIP install codecov factory_boy
diff --git a/setup.py b/setup.py
index 7ea6c203111d69bc5e3c4df4da6e8b563bf626b7..8ccf234c3012b35723df38c00c83c9f6997f7989 100644
--- a/setup.py
+++ b/setup.py
@@ -22,7 +22,7 @@ setup(name="relate-courseware",
license="MIT",
packages=find_packages(exclude=['tests']),
install_requires=[
- "django>=1.10,<2.1",
+ "django>=2.1.10",
"django-crispy-forms>=1.5.1",
"colorama",
"markdown<3.0",
diff --git a/tests/test_auth.py b/tests/test_auth.py
index 3705a84b88634e3b4b04329441492c496fc8da3f..88fbad522639d6d82d18358513897852a7c65e2a 100644
--- a/tests/test_auth.py
+++ b/tests/test_auth.py
@@ -1904,10 +1904,10 @@ class EmailedTokenBackendTest(CoursesTestMixinBase, TestCase):
backend = EmailedTokenBackend()
self.assertEqual(
- backend.authenticate(user.pk, token=user.sign_in_key), user)
+ backend.authenticate(None, user.pk, token=user.sign_in_key), user)
self.assertIsNone(
- backend.authenticate(user.pk, token="non_exist_sign_in_key"))
+ backend.authenticate(None, user.pk, token="non_exist_sign_in_key"))
def test_get_user(self):
user = factories.UserFactory()
diff --git a/yarn.lock b/yarn.lock
index 25aeb4fd0da0265ca9969e6963e36c7cc0c24ed8..f7b542cfd93b47720b5e7f45ef0bc25bd9c64fad 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -17,6 +17,11 @@ blueimp-tmpl@^3.11.0:
version "3.11.0"
resolved "https://registry.yarnpkg.com/blueimp-tmpl/-/blueimp-tmpl-3.11.0.tgz#47e78cbab16770e3922b019a250b4ad9c7b20f8f"
+bootstrap@^3.3:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.4.1.tgz#c3a347d419e289ad11f4033e3c4132b87c081d72"
+ integrity sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==
+
bootstrap@^3.3.7:
version "3.3.7"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.3.7.tgz#5a389394549f23330875a3b150656574f8a9eb71"
@@ -65,6 +70,16 @@ dom-walk@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018"
+eonasdan-bootstrap-datetimepicker@^4.17.47:
+ version "4.17.47"
+ resolved "https://registry.yarnpkg.com/eonasdan-bootstrap-datetimepicker/-/eonasdan-bootstrap-datetimepicker-4.17.47.tgz#7a49970044065276e7965efd16f822735219e735"
+ integrity sha1-ekmXAEQGUnbnll79Fvgic1IZ5zU=
+ dependencies:
+ bootstrap "^3.3"
+ jquery "^1.8.3 || ^2.0 || ^3.0"
+ moment "^2.10"
+ moment-timezone "^0.4.0"
+
es5-shim@^4.5.1:
version "4.5.10"
resolved "https://registry.yarnpkg.com/es5-shim/-/es5-shim-4.5.10.tgz#b7e17ef4df2a145b821f1497b50c25cf94026205"
@@ -120,6 +135,11 @@ jquery@>=1.7, jquery@>=1.7.1, jquery@>=1.9.1, jquery@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca"
+"jquery@^1.8.3 || ^2.0 || ^3.0":
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2"
+ integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==
+
jstree@^3.3.5:
version "3.3.5"
resolved "https://registry.yarnpkg.com/jstree/-/jstree-3.3.5.tgz#9c578db32d0a643775cddd8020ad5992f4119c13"
@@ -136,6 +156,18 @@ min-document@^2.19.0, min-document@^2.6.1:
dependencies:
dom-walk "^0.1.0"
+moment-timezone@^0.4.0:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.4.1.tgz#81f598c3ad5e22cdad796b67ecd8d88d0f5baa06"
+ integrity sha1-gfWYw61eIs2teWtn7NjYjQ9bqgY=
+ dependencies:
+ moment ">= 2.6.0"
+
+"moment@>= 2.6.0", moment@^2.10:
+ version "2.24.0"
+ resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
+ integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
+
moment@>=2.5.0:
version "2.21.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a"