From 75acab6d86aa6eb98463350323b5d0568defc16a Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 8 Jul 2019 14:45:09 -0500 Subject: [PATCH 01/10] Upgrade to Django 2.2, switch to a compatible bootstrap datepicker --- course/auth.py | 6 ++-- .../templates/course/generic-course-form.html | 11 +++++++ package.json | 1 + relate/settings.py | 1 - relate/templates/base.html | 1 - relate/templates/generic-form.html | 11 +++++++ requirements.txt | 5 +-- yarn.lock | 32 +++++++++++++++++++ 8 files changed, 62 insertions(+), 6 deletions(-) diff --git a/course/auth.py b/course/auth.py index 1ab1ae74..deeb2504 100644 --- a/course/auth.py +++ b/course/auth.py @@ -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() diff --git a/course/templates/course/generic-course-form.html b/course/templates/course/generic-course-form.html index 4ebf2909..89726805 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/package.json b/package.json index 7f3613cc..468a1d5f 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 fc3603a2..64ee658e 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 9354cc0e..f58bb5b3 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 41a92225..ca56a166 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 1b855b20..c879d86f 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 diff --git a/yarn.lock b/yarn.lock index 25aeb4fd..f7b542cf 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" -- GitLab From e0b805131b4606b92d53d4ed59224d5ddb22cdf4 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 8 Jul 2019 15:14:07 -0500 Subject: [PATCH 02/10] Update CommandParser initialization in manage.py to respect kwarg-only arguments --- manage.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/manage.py b/manage.py index a47f2ea6..9d45a90b 100755 --- a/manage.py +++ b/manage.py @@ -11,8 +11,10 @@ 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( + missing_args_message=None, + usage="%(prog)s subcommand [options] [args]", + add_help=False) parser.add_argument('--local_test_settings', dest="local_test_settings") -- GitLab From fb50173836191208c98ff38800b0932a0dd6bf97 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 8 Jul 2019 15:14:23 -0500 Subject: [PATCH 03/10] Fix, update to compatible CodeMirror widget --- requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c879d86f..0dd6b33d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -45,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 -- GitLab From 967419234f91f0bc0ed0b7a8ff9e6b48871378f8 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 8 Jul 2019 15:29:58 -0500 Subject: [PATCH 04/10] Retain Django 1.11 compatibility for Py2 --- manage.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/manage.py b/manage.py index 9d45a90b..a900a29c 100755 --- a/manage.py +++ b/manage.py @@ -11,10 +11,17 @@ 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 + import django + + if django.VERSION < (2, 1): + cparser_args = (None,) + else: + cparser_args = () parser = CommandParser( - missing_args_message=None, + *cparser_args, usage="%(prog)s subcommand [options] [args]", add_help=False) + parser.add_argument('--local_test_settings', dest="local_test_settings") -- GitLab From 7f01e157687123d64ea8f4029db263ed3806693c Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 8 Jul 2019 15:52:46 -0500 Subject: [PATCH 05/10] Fix setup.py to match requirements.txt for Django requirement --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7ea6c203..8ccf234c 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", -- GitLab From b8309c3116f08ae17c695a685a7060956a784e87 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 8 Jul 2019 15:54:30 -0500 Subject: [PATCH 06/10] Drop support for Python 2 --- .gitlab-ci.yml | 12 ------------ .travis.yml | 2 -- doc/misc.rst | 3 +-- manage.py | 6 ------ run-tests-for-ci.sh | 14 ++------------ 5 files changed, 3 insertions(+), 34 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f2cb682d..41b235c0 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 112f62f0..d97eea7c 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/doc/misc.rst b/doc/misc.rst index 49b7c5ef..44569039 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 a900a29c..b7d4dc98 100755 --- a/manage.py +++ b/manage.py @@ -11,14 +11,8 @@ 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 - import django - if django.VERSION < (2, 1): - cparser_args = (None,) - else: - cparser_args = () parser = CommandParser( - *cparser_args, usage="%(prog)s subcommand [options] [args]", add_help=False) diff --git a/run-tests-for-ci.sh b/run-tests-for-ci.sh index 5d57804e..7d8aff57 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 -- GitLab From 355f2fa04337f0ed07572965d13b926f125d716c Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 8 Jul 2019 15:55:06 -0500 Subject: [PATCH 07/10] Use InMemoryUploadedFile.size, not _size --- course/page/upload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/course/page/upload.py b/course/page/upload.py index ee6c6948..47fa879d 100644 --- a/course/page/upload.py +++ b/course/page/upload.py @@ -69,7 +69,7 @@ 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.") -- GitLab From ec767a085b68755fc2519e0b0e2838d730f67a1a Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 8 Jul 2019 16:19:14 -0500 Subject: [PATCH 08/10] Fix: Use InMemoryUploadedFile.size, not _size --- course/page/upload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/course/page/upload.py b/course/page/upload.py index 47fa879d..cb2618de 100644 --- a/course/page/upload.py +++ b/course/page/upload.py @@ -74,7 +74,7 @@ class FileUploadForm(StyledForm): _("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": -- GitLab From f810421801975ba9dd79bdacb4d46e7830060420 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 8 Jul 2019 18:22:28 -0500 Subject: [PATCH 09/10] Fix AuthenticationBackend.authenticate implementations to take request arg --- course/auth.py | 4 ++-- course/exam.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/course/auth.py b/course/auth.py index deeb2504..dd264f00 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) @@ -1130,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 b2321bd0..ea8dbdf2 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) -- GitLab From 12c7b140c0913a647dc90aa9392f4138dcaa3a33 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 8 Jul 2019 18:32:15 -0500 Subject: [PATCH 10/10] Fix EmailedTokenBackendTest to pass request parameter to authenticate --- tests/test_auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_auth.py b/tests/test_auth.py index 3705a84b..88fbad52 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() -- GitLab