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"