From d4f5b1a2be99f41d93776622e8969980e079f3a8 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Mon, 11 Sep 2023 16:54:02 +0100 Subject: [PATCH 01/58] initial --- .vscode/extensions.json | 13 ----------- .vscode/launch.json | 22 +++++++++++++++---- .vscode/settings.json | 13 ++++++++--- backend/manage.py | 4 ++-- backend/pyproject.toml | 5 +++++ .../{example_project => service}/__init__.py | 0 backend/{example_project => service}/asgi.py | 0 .../{example_project => service}/settings.py | 0 backend/{example_project => service}/urls.py | 0 backend/{example_project => service}/wsgi.py | 0 backend/{myapp => sso}/__init__.py | 0 backend/{myapp => sso}/admin.py | 0 backend/{myapp => sso}/apps.py | 0 backend/{myapp => sso}/migrations/__init__.py | 0 backend/{myapp => sso}/models.py | 0 backend/{myapp => sso}/templates/.gitkeep | 0 backend/{myapp => sso}/tests.py | 0 backend/{myapp => sso}/urls.py | 0 backend/{myapp => sso}/views.py | 0 19 files changed, 35 insertions(+), 22 deletions(-) delete mode 100644 .vscode/extensions.json create mode 100644 backend/pyproject.toml rename backend/{example_project => service}/__init__.py (100%) rename backend/{example_project => service}/asgi.py (100%) rename backend/{example_project => service}/settings.py (100%) rename backend/{example_project => service}/urls.py (100%) rename backend/{example_project => service}/wsgi.py (100%) rename backend/{myapp => sso}/__init__.py (100%) rename backend/{myapp => sso}/admin.py (100%) rename backend/{myapp => sso}/apps.py (100%) rename backend/{myapp => sso}/migrations/__init__.py (100%) rename backend/{myapp => sso}/models.py (100%) rename backend/{myapp => sso}/templates/.gitkeep (100%) rename backend/{myapp => sso}/tests.py (100%) rename backend/{myapp => sso}/urls.py (100%) rename backend/{myapp => sso}/views.py (100%) diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 7ef36c1..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. - // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp - - // List of extensions which should be recommended for users of this workspace. - "recommendations": [ - "dbaeumer.vscode-eslint" - ], - // List of extensions recommended by VS Code that should not be recommended for users of this workspace. - "unwantedRecommendations": [ - - ] -} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index dbd1590..013a98c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,11 +5,25 @@ "version": "0.2.0", "configurations": [ { - "type": "chrome", + "name": "Django Server", + "type": "python", "request": "launch", - "name": "Launch Chrome", - "url": "http://localhost:3000", - "webRoot": "${workspaceFolder}/src" + "django": true, + "justMyCode": false, + "program": "${workspaceFolder}/backend/manage.py", + "args": [ + "runserver", + "localhost:8001" + ] + }, + { + "name": "Pytest", + "type": "python", + "request": "test", + "justMyCode": false, + "presentation": { + "hidden": true + } } ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 1e2ec29..3f253e9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,12 @@ { - "editor.tabSize": 2, - "javascript.preferences.quoteStyle": "single", - "typescript.preferences.quoteStyle": "single" + "black-formatter.args": [ + "--config", + "backend/pyproject.toml" + ], + "python.testing.pytestArgs": [ + "-c=backend/pyproject.toml", + "backend" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true } \ No newline at end of file diff --git a/backend/manage.py b/backend/manage.py index 55f7cc3..c134dd8 100755 --- a/backend/manage.py +++ b/backend/manage.py @@ -6,7 +6,7 @@ def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example_project.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "service.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -18,5 +18,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/backend/pyproject.toml b/backend/pyproject.toml new file mode 100644 index 0000000..e843634 --- /dev/null +++ b/backend/pyproject.toml @@ -0,0 +1,5 @@ +[tool.black] +line-length = 80 + +[tool.pytest.ini_options] +env = ["DJANGO_SETTINGS_MODULE=service.settings"] diff --git a/backend/example_project/__init__.py b/backend/service/__init__.py similarity index 100% rename from backend/example_project/__init__.py rename to backend/service/__init__.py diff --git a/backend/example_project/asgi.py b/backend/service/asgi.py similarity index 100% rename from backend/example_project/asgi.py rename to backend/service/asgi.py diff --git a/backend/example_project/settings.py b/backend/service/settings.py similarity index 100% rename from backend/example_project/settings.py rename to backend/service/settings.py diff --git a/backend/example_project/urls.py b/backend/service/urls.py similarity index 100% rename from backend/example_project/urls.py rename to backend/service/urls.py diff --git a/backend/example_project/wsgi.py b/backend/service/wsgi.py similarity index 100% rename from backend/example_project/wsgi.py rename to backend/service/wsgi.py diff --git a/backend/myapp/__init__.py b/backend/sso/__init__.py similarity index 100% rename from backend/myapp/__init__.py rename to backend/sso/__init__.py diff --git a/backend/myapp/admin.py b/backend/sso/admin.py similarity index 100% rename from backend/myapp/admin.py rename to backend/sso/admin.py diff --git a/backend/myapp/apps.py b/backend/sso/apps.py similarity index 100% rename from backend/myapp/apps.py rename to backend/sso/apps.py diff --git a/backend/myapp/migrations/__init__.py b/backend/sso/migrations/__init__.py similarity index 100% rename from backend/myapp/migrations/__init__.py rename to backend/sso/migrations/__init__.py diff --git a/backend/myapp/models.py b/backend/sso/models.py similarity index 100% rename from backend/myapp/models.py rename to backend/sso/models.py diff --git a/backend/myapp/templates/.gitkeep b/backend/sso/templates/.gitkeep similarity index 100% rename from backend/myapp/templates/.gitkeep rename to backend/sso/templates/.gitkeep diff --git a/backend/myapp/tests.py b/backend/sso/tests.py similarity index 100% rename from backend/myapp/tests.py rename to backend/sso/tests.py diff --git a/backend/myapp/urls.py b/backend/sso/urls.py similarity index 100% rename from backend/myapp/urls.py rename to backend/sso/urls.py diff --git a/backend/myapp/views.py b/backend/sso/views.py similarity index 100% rename from backend/myapp/views.py rename to backend/sso/views.py From 1ba8153f7128463802eb953297ad9f66fac14ea3 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Tue, 12 Sep 2023 15:22:26 +0100 Subject: [PATCH 02/58] initial code --- .gitignore | 190 ++++++++++++++++- .vscode/launch.json | 3 +- .vscode/tasks.json | 24 +++ backend/Pipfile | 11 +- backend/Pipfile.lock | 199 ++++++++++++------ backend/{sso => api}/__init__.py | 0 backend/api/apps.py | 6 + .../{sso/migrations => api/tests}/__init__.py | 0 .../.gitkeep => api/tests/views/__init__.py} | 0 backend/api/tests/views/cron/__init__.py | 0 backend/api/tests/views/cron/session.py | 0 backend/api/tests/views/session.py | 0 backend/api/urls/__init__.py | 11 + backend/api/urls/cron/__init__.py | 7 + backend/api/urls/cron/session.py | 5 + backend/api/urls/csrf.py | 8 + backend/api/urls/session.py | 8 + backend/api/views/__init__.py | 0 backend/api/views/cron/__init__.py | 0 backend/api/views/cron/session.py | 0 backend/api/views/csrf.py | 14 ++ backend/api/views/session.py | 12 ++ backend/service/asgi.py | 4 +- backend/service/settings.py | 76 +++---- backend/service/urls.py | 9 +- backend/service/wsgi.py | 4 +- backend/sso/admin.py | 3 - backend/sso/apps.py | 6 - backend/sso/models.py | 3 - backend/sso/tests.py | 3 - backend/sso/urls.py | 7 - backend/sso/views.py | 5 - 32 files changed, 473 insertions(+), 145 deletions(-) create mode 100644 .vscode/tasks.json rename backend/{sso => api}/__init__.py (100%) create mode 100644 backend/api/apps.py rename backend/{sso/migrations => api/tests}/__init__.py (100%) rename backend/{sso/templates/.gitkeep => api/tests/views/__init__.py} (100%) create mode 100644 backend/api/tests/views/cron/__init__.py create mode 100644 backend/api/tests/views/cron/session.py create mode 100644 backend/api/tests/views/session.py create mode 100644 backend/api/urls/__init__.py create mode 100644 backend/api/urls/cron/__init__.py create mode 100644 backend/api/urls/cron/session.py create mode 100644 backend/api/urls/csrf.py create mode 100644 backend/api/urls/session.py create mode 100644 backend/api/views/__init__.py create mode 100644 backend/api/views/cron/__init__.py create mode 100644 backend/api/views/cron/session.py create mode 100644 backend/api/views/csrf.py create mode 100644 backend/api/views/session.py delete mode 100644 backend/sso/admin.py delete mode 100644 backend/sso/apps.py delete mode 100644 backend/sso/models.py delete mode 100644 backend/sso/tests.py delete mode 100644 backend/sso/urls.py delete mode 100644 backend/sso/views.py diff --git a/.gitignore b/.gitignore index 069ed83..9a3bf80 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,173 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. -#IDE files -/.idea +# ------------------------------------------------------------------------------ +# Backend +# ------------------------------------------------------------------------------ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ + +# ------------------------------------------------------------------------------ +# Frontend +# ------------------------------------------------------------------------------ # dependencies */node_modules @@ -9,7 +175,10 @@ .pnp.js # testing -/coverage +coverage + +# production +/build # misc .DS_Store @@ -22,11 +191,18 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -# custom - -# files generated by the Django bundler and settings +# Custom +.nyc_output +cypress/videos +cypress/screenshots +/backend/.python-version /backend/*/static/react -/backend/*/templates/my_app_react.html +/backend/*/templates/portal.html /backend/static *.sqlite3 /frontend/build +node_modules +.cache +package-lock.json +/package.json +/yarn.lock \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 013a98c..ab4afc1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,8 @@ "args": [ "runserver", "localhost:8001" - ] + ], + "preLaunchTask": "migrate-db" }, { "name": "Pytest", diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..8cdd097 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,24 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "install-dev-deps", + "type": "shell", + "options": { + "cwd": "${workspaceFolder}/backend" + }, + "command": "pipenv install --dev" + }, + { + "label": "migrate-db", + "type": "shell", + "options": { + "cwd": "${workspaceFolder}/backend" + }, + "dependsOn": [ + "install-dev-deps" + ], + "command": "pipenv run manage.py migrate" + } + ] +} \ No newline at end of file diff --git a/backend/Pipfile b/backend/Pipfile index ff95d35..28487c3 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -4,9 +4,16 @@ verify_ssl = true name = "pypi" [packages] -codeforlife = {ref = "v0.1.12", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} +# codeforlife = {ref = "v0.1.12", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} +# cfl-common = "==6.33.1" +django = "==3.2.19" +djangorestframework = "==3.13.1" [dev-packages] +black = "*" +pytest-django = "==4.5.2" +pytest = "==7.*" +pytest-env = "==0.8.2" [requires] -python_version = "3.11" +python_version = "3.8" diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 581b4ee..f3ef34e 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "52d5cc6b86027774311be6cafb6ca2ea46a998d5312f5452bb62b9b23d773418" + "sha256": "e42cce937a9b849a6a2c94a0edbff81fa3f320bdee5c6df3632c649d18fe2f99" }, "pipfile-spec": 6, "requires": { - "python_version": "3.11" + "python_version": "3.8" }, "sources": [ { @@ -18,107 +18,184 @@ "default": { "asgiref": { "hashes": [ - "sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac", - "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506" + "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e", + "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed" ], "markers": "python_version >= '3.7'", - "version": "==3.6.0" - }, - "codeforlife": { - "git": "https://github.com/ocadotechnology/codeforlife-package-python.git", - "ref": "5889196548dea315f279d0fb3b9bacce23fe8d95" + "version": "==3.7.2" }, "django": { "hashes": [ - "sha256:08208dfe892eb64fff073ca743b3b952311104f939e7f6dae954fe72dcc533ba", - "sha256:4d492d9024c7b3dfababf49f94511ab6a58e2c9c3c7207786f1ba4eb77750706" + "sha256:031365bae96814da19c10706218c44dff3b654cc4de20a98bd2d29b9bde469f0", + "sha256:21cc991466245d659ab79cb01204f9515690f8dae00e5eabde307f14d24d4d7d" ], - "markers": "python_version >= '3.6'", - "version": "==3.2.18" + "index": "pypi", + "version": "==3.2.19" }, - "django-countries": { + "djangorestframework": { "hashes": [ - "sha256:5a4ee958f77810bcc38ae96605e47d76a707e81f53cf2938743ef45faafd2fce", - "sha256:b6b439cc5c7e766ec2335615160fdcebb9f2774ccc17aaa5e173306832d77594" + "sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee", + "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa" ], - "version": "==7.3.1" + "index": "pypi", + "version": "==3.13.1" }, - "django-formtools": { + "pytz": { "hashes": [ - "sha256:deb932be55b1d9419e37dc4d65dfbfeb8d307b71c8c11fd52f159aba5fc0deed", - "sha256:f5f32f62ec8192cd1bc55bd929ca7dff5a5f2addf9027db95a5906ecfaa64836" + "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b", + "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7" ], - "markers": "python_version >= '3.6'", - "version": "==2.4" + "version": "==2023.3.post1" + }, + "sqlparse": { + "hashes": [ + "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3", + "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c" + ], + "markers": "python_version >= '3.5'", + "version": "==0.4.4" }, - "django-otp": { + "typing-extensions": { + "hashes": [ + "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", + "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2" + ], + "markers": "python_version < '3.11'", + "version": "==4.7.1" + } + }, + "develop": { + "black": { "hashes": [ - "sha256:8a431a934afdd4359ab838551d02d68d3cd90c974aa9e7337cbcbb6d45db621b", - "sha256:eac8e06041efe90e0687faea997fc0b3340f5bc74476f20aca0f6a96978f4253" + "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f", + "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7", + "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100", + "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573", + "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d", + "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f", + "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9", + "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300", + "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948", + "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325", + "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9", + "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71", + "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186", + "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f", + "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe", + "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855", + "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80", + "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393", + "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c", + "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204", + "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377", + "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301" ], - "version": "==1.1.6" + "index": "pypi", + "version": "==23.9.1" }, - "django-phonenumber-field": { + "click": { "hashes": [ - "sha256:72a3e7a3e7493bf2a12c07a3bc77ce89813acc16592bf04d0eee3b5a452097ed", - "sha256:a31b4f05ac0ff898661516c84940f83adb5cdcf0ae4b9b1d31bb8ad3ff345b58" + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" ], "markers": "python_version >= '3.7'", - "version": "==6.4.0" + "version": "==8.1.7" }, - "django-two-factor-auth": { + "exceptiongroup": { "hashes": [ - "sha256:3fac266d12472ac66475dd737bb18f2992484313bf56acf5a2eea5e824291ee6", - "sha256:44fb9f6a52dbf83229de52c6f642eb2e0feafdb919854f3dc0e7716877c340ba" + "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9", + "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3" ], - "version": "==1.13.2" + "markers": "python_version < '3.11'", + "version": "==1.1.3" }, - "djangorestframework": { + "iniconfig": { "hashes": [ - "sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee", - "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa" + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" ], - "markers": "python_version >= '3.6'", - "version": "==3.13.1" + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "mypy-extensions": { + "hashes": [ + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.0" }, - "pypng": { + "packaging": { "hashes": [ - "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c", - "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1" + "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", + "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" ], - "version": "==0.20220715.0" + "markers": "python_version >= '3.7'", + "version": "==23.1" }, - "pytz": { + "pathspec": { "hashes": [ - "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0", - "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a" + "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20", + "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3" ], - "version": "==2022.7.1" + "markers": "python_version >= '3.7'", + "version": "==0.11.2" }, - "qrcode": { + "platformdirs": { "hashes": [ - "sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a", - "sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845" + "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d", + "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d" ], "markers": "python_version >= '3.7'", + "version": "==3.10.0" + }, + "pluggy": { + "hashes": [ + "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12", + "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7" + ], + "markers": "python_version >= '3.8'", + "version": "==1.3.0" + }, + "pytest": { + "hashes": [ + "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002", + "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069" + ], + "index": "pypi", "version": "==7.4.2" }, - "sqlparse": { + "pytest-django": { "hashes": [ - "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34", - "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268" + "sha256:c60834861933773109334fe5a53e83d1ef4828f2203a1d6a0fa9972f4f75ab3e", + "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2" ], - "markers": "python_version >= '3.5'", - "version": "==0.4.3" + "index": "pypi", + "version": "==4.5.2" + }, + "pytest-env": { + "hashes": [ + "sha256:5e533273f4d9e6a41c3a3120e0c7944aae5674fa773b329f00a5eb1f23c53a38", + "sha256:baed9b3b6bae77bd75b9238e0ed1ee6903a42806ae9d6aeffb8754cd5584d4ff" + ], + "index": "pypi", + "version": "==0.8.2" + }, + "tomli": { + "hashes": [ + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + ], + "markers": "python_version < '3.11'", + "version": "==2.0.1" }, "typing-extensions": { "hashes": [ - "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb", - "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4" + "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", + "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2" ], - "markers": "python_version >= '3.7'", - "version": "==4.5.0" + "markers": "python_version < '3.11'", + "version": "==4.7.1" } - }, - "develop": {} + } } diff --git a/backend/sso/__init__.py b/backend/api/__init__.py similarity index 100% rename from backend/sso/__init__.py rename to backend/api/__init__.py diff --git a/backend/api/apps.py b/backend/api/apps.py new file mode 100644 index 0000000..878e7d5 --- /dev/null +++ b/backend/api/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ApiConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "api" diff --git a/backend/sso/migrations/__init__.py b/backend/api/tests/__init__.py similarity index 100% rename from backend/sso/migrations/__init__.py rename to backend/api/tests/__init__.py diff --git a/backend/sso/templates/.gitkeep b/backend/api/tests/views/__init__.py similarity index 100% rename from backend/sso/templates/.gitkeep rename to backend/api/tests/views/__init__.py diff --git a/backend/api/tests/views/cron/__init__.py b/backend/api/tests/views/cron/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/api/tests/views/cron/session.py b/backend/api/tests/views/cron/session.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/api/tests/views/session.py b/backend/api/tests/views/session.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/api/urls/__init__.py b/backend/api/urls/__init__.py new file mode 100644 index 0000000..f7b9a83 --- /dev/null +++ b/backend/api/urls/__init__.py @@ -0,0 +1,11 @@ +from django.urls import include, path + +from .cron import urlpatterns as cron_urlpatterns +from .csrf import urlpatterns as csrf_urlpatterns +from .session import urlpatterns as session_urlpatterns + +urlpatterns = [ + path("cron/", include(cron_urlpatterns)), + path("csrf/", include(csrf_urlpatterns)), + path("session/", include(session_urlpatterns)), +] diff --git a/backend/api/urls/cron/__init__.py b/backend/api/urls/cron/__init__.py new file mode 100644 index 0000000..d73459e --- /dev/null +++ b/backend/api/urls/cron/__init__.py @@ -0,0 +1,7 @@ +from django.urls import path, include + +from .session import urlpatterns as session_urlpatterns + +urlpatterns = [ + path("session/", include(session_urlpatterns)), +] diff --git a/backend/api/urls/cron/session.py b/backend/api/urls/cron/session.py new file mode 100644 index 0000000..365d176 --- /dev/null +++ b/backend/api/urls/cron/session.py @@ -0,0 +1,5 @@ +from django.urls import path + +urlpatterns = [ + # path(""), +] diff --git a/backend/api/urls/csrf.py b/backend/api/urls/csrf.py new file mode 100644 index 0000000..bd25177 --- /dev/null +++ b/backend/api/urls/csrf.py @@ -0,0 +1,8 @@ +from django.urls import path + +from ..views.csrf import CookieView + +# TODO: import these url patterns form codeforlife package. +urlpatterns = [ + path("cookie/", CookieView.as_view(), name="get-csrf-cookie"), +] diff --git a/backend/api/urls/session.py b/backend/api/urls/session.py new file mode 100644 index 0000000..88f9585 --- /dev/null +++ b/backend/api/urls/session.py @@ -0,0 +1,8 @@ +from django.urls import path + +from ..views.session import LoginView, LogoutView + +urlpatterns = [ + path("login/", LoginView.as_view(), name="login"), + path("logout/", LogoutView.as_view(), name="logout"), +] diff --git a/backend/api/views/__init__.py b/backend/api/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/api/views/cron/__init__.py b/backend/api/views/cron/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/api/views/cron/session.py b/backend/api/views/cron/session.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/api/views/csrf.py b/backend/api/views/csrf.py new file mode 100644 index 0000000..355aea1 --- /dev/null +++ b/backend/api/views/csrf.py @@ -0,0 +1,14 @@ +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import ensure_csrf_cookie +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.views import APIView + + +# TODO: import this view form codeforlife package. +class CookieView(APIView): + http_method_names = ["get"] + + @method_decorator(ensure_csrf_cookie) + def get(self, request: Request): + return Response() diff --git a/backend/api/views/session.py b/backend/api/views/session.py new file mode 100644 index 0000000..6cc6d95 --- /dev/null +++ b/backend/api/views/session.py @@ -0,0 +1,12 @@ +from django.contrib.auth.views import ( + LoginView as _LoginView, + LogoutView as _LogoutView, +) + + +class LoginView(_LoginView): + pass + + +class LogoutView(_LogoutView): + pass diff --git a/backend/service/asgi.py b/backend/service/asgi.py index 962e088..fcb88c5 100644 --- a/backend/service/asgi.py +++ b/backend/service/asgi.py @@ -1,5 +1,5 @@ """ -ASGI config for example_project project. +ASGI config for service project. It exposes the ASGI callable as a module-level variable named ``application``. @@ -11,6 +11,6 @@ from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example_project.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "service.settings") application = get_asgi_application() diff --git a/backend/service/settings.py b/backend/service/settings.py index 3b6bd9d..a2faf66 100644 --- a/backend/service/settings.py +++ b/backend/service/settings.py @@ -1,5 +1,5 @@ """ -Django settings for example_project project. +Django settings for service project. Generated by 'django-admin startproject' using Django 3.2.18. @@ -20,7 +20,7 @@ # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'replace-me-and-put-me-in-a-github-secret' +SECRET_KEY = "replace-me-and-put-me-in-a-github-secret" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -30,54 +30,54 @@ # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'codeforlife', - 'myapp', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + # 'codeforlife', + "api", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'example_project.urls' +ROOT_URLCONF = "service.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'example_project.wsgi.application' +WSGI_APPLICATION = "service.wsgi.application" # Database # https://docs.djangoproject.com/en/3.2/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", } } @@ -87,16 +87,16 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -104,9 +104,9 @@ # Internationalization # https://docs.djangoproject.com/en/3.2/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -125,4 +125,4 @@ # Default primary key field type # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/backend/service/urls.py b/backend/service/urls.py index 5263794..84352c8 100644 --- a/backend/service/urls.py +++ b/backend/service/urls.py @@ -1,4 +1,4 @@ -"""example_project URL Configuration +"""service URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/3.2/topics/http/urls/ @@ -13,11 +13,10 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ -from django.conf.urls import include, url from django.contrib import admin -from django.urls import path +from django.urls import include, path urlpatterns = [ - path('admin/', admin.site.urls), - path('', include("myapp.urls")), + path("admin/", admin.site.urls), + path("api/", include("api.urls")), ] diff --git a/backend/service/wsgi.py b/backend/service/wsgi.py index 1dd0839..8736081 100644 --- a/backend/service/wsgi.py +++ b/backend/service/wsgi.py @@ -1,5 +1,5 @@ """ -WSGI config for example_project project. +WSGI config for service project. It exposes the WSGI callable as a module-level variable named ``application``. @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example_project.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "service.settings") application = get_wsgi_application() diff --git a/backend/sso/admin.py b/backend/sso/admin.py deleted file mode 100644 index 8c38f3f..0000000 --- a/backend/sso/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/backend/sso/apps.py b/backend/sso/apps.py deleted file mode 100644 index c34fb20..0000000 --- a/backend/sso/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class MyappConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'myapp' diff --git a/backend/sso/models.py b/backend/sso/models.py deleted file mode 100644 index 71a8362..0000000 --- a/backend/sso/models.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. diff --git a/backend/sso/tests.py b/backend/sso/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/backend/sso/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/backend/sso/urls.py b/backend/sso/urls.py deleted file mode 100644 index 02ac898..0000000 --- a/backend/sso/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.conf.urls import url - -from . import views - -urlpatterns = [ - url(r".*", views.render_react, name="react_app"), -] diff --git a/backend/sso/views.py b/backend/sso/views.py deleted file mode 100644 index 9a941f5..0000000 --- a/backend/sso/views.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.shortcuts import render - - -def render_react(request): - return render(request, "my_app_react.html") From 194d50dd348b7c106a9855701c5c6408bbbdb822 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Tue, 12 Sep 2023 16:16:28 +0100 Subject: [PATCH 03/58] quick save --- .vscode/tasks.json | 2 +- backend/Pipfile | 5 +- backend/Pipfile.lock | 526 ++++++++++++- backend/api/tests/views/session.py | 9 + backend/api/urls/session.py | 3 +- backend/api/views/session.py | 10 +- backend/portal/__init__.py | 0 backend/portal/apps.py | 6 + .../migrations/0001_squashed_0041_new_news.py | 736 ++++++++++++++++++ .../portal/migrations/0042_school_country.py | 17 + .../migrations/0043_auto_20150430_0952.py | 19 + .../migrations/0044_auto_20150430_0959.py | 37 + .../migrations/0045_auto_20150430_1446.py | 17 + .../migrations/0046_auto_20150723_1101.py | 20 + .../0047_remove_userprofile_avatar.py | 10 + .../0048_plural_management_frontnews.py | 14 + .../0049_refactor_emailverifications.py | 32 + .../0050_refactor_emailverifications_2.py | 27 + .../migrations/0051_add_missing_ev_records.py | 38 + .../0052_refactor_emailverifications_3.py | 11 + .../0053_refactor_teacher_student_1.py | 65 ++ .../0054_pending_join_request_can_be_blank.py | 22 + .../migrations/0055_add_preview_user.py | 21 + .../migrations/0056_remove_preview_user.py | 14 + .../migrations/0057_delete_frontpagenews.py | 11 + .../migrations/0058_move_to_common_models.py | 217 ++++++ ...0059_move_email_verifications_to_common.py | 34 + .../portal/migrations/0060_delete_guardian.py | 28 + .../0061_make_portaladmin_teacher.py | 114 +++ .../migrations/0062_verify_portaladmin.py | 44 ++ backend/portal/migrations/__init__.py | 1 + backend/service/settings.py | 9 +- 32 files changed, 2103 insertions(+), 16 deletions(-) create mode 100644 backend/portal/__init__.py create mode 100644 backend/portal/apps.py create mode 100644 backend/portal/migrations/0001_squashed_0041_new_news.py create mode 100644 backend/portal/migrations/0042_school_country.py create mode 100644 backend/portal/migrations/0043_auto_20150430_0952.py create mode 100644 backend/portal/migrations/0044_auto_20150430_0959.py create mode 100644 backend/portal/migrations/0045_auto_20150430_1446.py create mode 100644 backend/portal/migrations/0046_auto_20150723_1101.py create mode 100644 backend/portal/migrations/0047_remove_userprofile_avatar.py create mode 100644 backend/portal/migrations/0048_plural_management_frontnews.py create mode 100644 backend/portal/migrations/0049_refactor_emailverifications.py create mode 100644 backend/portal/migrations/0050_refactor_emailverifications_2.py create mode 100644 backend/portal/migrations/0051_add_missing_ev_records.py create mode 100644 backend/portal/migrations/0052_refactor_emailverifications_3.py create mode 100644 backend/portal/migrations/0053_refactor_teacher_student_1.py create mode 100644 backend/portal/migrations/0054_pending_join_request_can_be_blank.py create mode 100644 backend/portal/migrations/0055_add_preview_user.py create mode 100644 backend/portal/migrations/0056_remove_preview_user.py create mode 100644 backend/portal/migrations/0057_delete_frontpagenews.py create mode 100644 backend/portal/migrations/0058_move_to_common_models.py create mode 100644 backend/portal/migrations/0059_move_email_verifications_to_common.py create mode 100644 backend/portal/migrations/0060_delete_guardian.py create mode 100644 backend/portal/migrations/0061_make_portaladmin_teacher.py create mode 100644 backend/portal/migrations/0062_verify_portaladmin.py create mode 100644 backend/portal/migrations/__init__.py diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8cdd097..e2eb22b 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -18,7 +18,7 @@ "dependsOn": [ "install-dev-deps" ], - "command": "pipenv run manage.py migrate" + "command": "pipenv run ./manage.py migrate" } ] } \ No newline at end of file diff --git a/backend/Pipfile b/backend/Pipfile index 28487c3..1d1946c 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -5,7 +5,10 @@ name = "pypi" [packages] # codeforlife = {ref = "v0.1.12", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} -# cfl-common = "==6.33.1" +cfl-common = "==6.33.1" # TODO: remove +aimmo = ">=2" # TODO: remove +rapid-router = ">=4" # TODO: remove +phonenumbers = "==8.12.12" # TODO: remove django = "==3.2.19" djangorestframework = "==3.13.1" diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index f3ef34e..50f4961 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e42cce937a9b849a6a2c94a0edbff81fa3f320bdee5c6df3632c649d18fe2f99" + "sha256": "7ae6fedf5043a359b3ca03aa973280becc2ec820d0167be131ba42cc6428f611" }, "pipfile-spec": 6, "requires": { @@ -16,6 +16,14 @@ ] }, "default": { + "aimmo": { + "hashes": [ + "sha256:42d01d0f5a0bfd9acf79242726e3e86d5643d6309188dd5951eabd79d5722401", + "sha256:c15924ca5719ae61f11ee8240eb40a63a1a18167f29bab45646f20f241fe7155" + ], + "index": "pypi", + "version": "==2.9.0" + }, "asgiref": { "hashes": [ "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e", @@ -24,22 +32,395 @@ "markers": "python_version >= '3.7'", "version": "==3.7.2" }, + "attrs": { + "hashes": [ + "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", + "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015" + ], + "markers": "python_version >= '3.7'", + "version": "==23.1.0" + }, + "cachetools": { + "hashes": [ + "sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590", + "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b" + ], + "markers": "python_version >= '3.7'", + "version": "==5.3.1" + }, + "certifi": { + "hashes": [ + "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", + "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" + ], + "markers": "python_version >= '3.6'", + "version": "==2023.7.22" + }, + "cfl-common": { + "hashes": [ + "sha256:58a1c9b4a2938eb49e4c9c831cd3a220bc91ba2f1cc2504fd8de4cdb88eb500d", + "sha256:b19e630e1575f0ebb9c56fd4053555e0842b39a84debba6de4640c8d06f3804c" + ], + "index": "pypi", + "version": "==6.33.1" + }, + "charset-normalizer": { + "hashes": [ + "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96", + "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c", + "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710", + "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706", + "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020", + "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252", + "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad", + "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329", + "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a", + "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f", + "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6", + "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4", + "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a", + "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46", + "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2", + "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23", + "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace", + "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd", + "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982", + "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10", + "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2", + "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea", + "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09", + "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5", + "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149", + "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489", + "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9", + "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80", + "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592", + "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3", + "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6", + "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed", + "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c", + "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200", + "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a", + "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e", + "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d", + "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6", + "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623", + "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669", + "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3", + "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa", + "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9", + "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2", + "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f", + "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1", + "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4", + "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a", + "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8", + "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3", + "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029", + "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f", + "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959", + "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22", + "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7", + "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952", + "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346", + "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e", + "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d", + "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299", + "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd", + "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a", + "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3", + "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037", + "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94", + "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c", + "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858", + "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a", + "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449", + "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c", + "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918", + "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1", + "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c", + "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac", + "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.2.0" + }, "django": { "hashes": [ "sha256:031365bae96814da19c10706218c44dff3b654cc4de20a98bd2d29b9bde469f0", "sha256:21cc991466245d659ab79cb01204f9515690f8dae00e5eabde307f14d24d4d7d" ], "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==3.2.19" }, + "django-countries": { + "hashes": [ + "sha256:5a4ee958f77810bcc38ae96605e47d76a707e81f53cf2938743ef45faafd2fce", + "sha256:b6b439cc5c7e766ec2335615160fdcebb9f2774ccc17aaa5e173306832d77594" + ], + "version": "==7.3.1" + }, + "django-csp": { + "hashes": [ + "sha256:01443a07723f9a479d498bd7bb63571aaa771e690f64bde515db6cdb76e8041a", + "sha256:01eda02ad3f10261c74131cdc0b5a6a62b7c7ad4fd017fbefb7a14776e0a9727" + ], + "version": "==3.7" + }, + "django-formtools": { + "hashes": [ + "sha256:21f8d5dac737f1e636fa8a0a10969c1c32f525a6dfa27c29592827ba70d9643a", + "sha256:49ea8a64ddef4728a558bf5f8f622c0f4053b979edcf193bf00dd80432ab2f12" + ], + "markers": "python_version >= '3.7'", + "version": "==2.4.1" + }, + "django-js-reverse": { + "hashes": [ + "sha256:2a392d169f44e30b883c30dfcfd917a14167ce8fe196c99d2385b31c90d77aa0", + "sha256:8134c2ab6307c945edfa90671ca65e85d6c1754d48566bdd6464be259cc80c30" + ], + "version": "==0.9.1" + }, + "django-otp": { + "hashes": [ + "sha256:007a6354dabb3a1a54574bf73abf045ebbde0bb8734a38e2ed7845ba450f345e", + "sha256:90765d5dac238a719f9550ac05681dd6307f513a81a10b6adb879b4abc6bc1a3" + ], + "markers": "python_version >= '3.7'", + "version": "==1.2.2" + }, + "django-phonenumber-field": { + "hashes": [ + "sha256:72a3e7a3e7493bf2a12c07a3bc77ce89813acc16592bf04d0eee3b5a452097ed", + "sha256:a31b4f05ac0ff898661516c84940f83adb5cdcf0ae4b9b1d31bb8ad3ff345b58" + ], + "markers": "python_version >= '3.7'", + "version": "==6.4.0" + }, + "django-pipeline": { + "hashes": [ + "sha256:26f1d344a7bf39bc92c9dc520093471d912de53abd7d22ac715e77d779a831c8", + "sha256:56c299cec0e644e77d5f928f4cebfff804b919cc10ff5c0bfaa070ff57e8da44" + ], + "version": "==2.0.8" + }, + "django-two-factor-auth": { + "hashes": [ + "sha256:3fac266d12472ac66475dd737bb18f2992484313bf56acf5a2eea5e824291ee6", + "sha256:44fb9f6a52dbf83229de52c6f642eb2e0feafdb919854f3dc0e7716877c340ba" + ], + "version": "==1.13.2" + }, "djangorestframework": { "hashes": [ "sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee", "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa" ], "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==3.13.1" }, + "dnspython": { + "hashes": [ + "sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01", + "sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "eventlet": { + "hashes": [ + "sha256:27ae41fad9deed9bbf4166f3e3b65acc15d524d42210a518e5877da85a6b8c5d", + "sha256:b36ec2ecc003de87fc87b93197d77fea528aa0f9204a34fdf3b2f8d0f01e017b" + ], + "version": "==0.31.0" + }, + "google-auth": { + "hashes": [ + "sha256:2cec41407bd1e207f5b802638e32bb837df968bb5c05f413d0fa526fac4cf7a7", + "sha256:753a26312e6f1eaeec20bc6f2644a10926697da93446e1f8e24d6d32d45a922a" + ], + "markers": "python_version >= '3.7'", + "version": "==2.23.0" + }, + "greenlet": { + "hashes": [ + "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a", + "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a", + "sha256:1087300cf9700bbf455b1b97e24db18f2f77b55302a68272c56209d5587c12d1", + "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43", + "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33", + "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8", + "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088", + "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca", + "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343", + "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645", + "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db", + "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df", + "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3", + "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86", + "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2", + "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a", + "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf", + "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7", + "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394", + "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40", + "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3", + "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6", + "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74", + "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0", + "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3", + "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91", + "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5", + "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9", + "sha256:8512a0c38cfd4e66a858ddd1b17705587900dd760c6003998e9472b77b56d417", + "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8", + "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b", + "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6", + "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb", + "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73", + "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b", + "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df", + "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9", + "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f", + "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0", + "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857", + "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a", + "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249", + "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30", + "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292", + "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b", + "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d", + "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b", + "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c", + "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca", + "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7", + "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75", + "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae", + "sha256:d4606a527e30548153be1a9f155f4e283d109ffba663a15856089fb55f933e47", + "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b", + "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470", + "sha256:d967650d3f56af314b72df7089d96cda1083a7fc2da05b375d2bc48c82ab3f3c", + "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564", + "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9", + "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099", + "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0", + "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5", + "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19", + "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1", + "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==2.0.2" + }, + "hypothesis": { + "hashes": [ + "sha256:6a3471ff74864ab04a0650c75500ef15f2f4a901d49ccbb7cbec668365736688", + "sha256:989162a9e0715c624b99ad9b2b4206765879b40eb51eef17b1e37de3e898370a" + ], + "markers": "python_version >= '3.6'", + "version": "==5.41.3" + }, + "idna": { + "hashes": [ + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + ], + "markers": "python_version >= '3.5'", + "version": "==3.4" + }, + "kubernetes": { + "hashes": [ + "sha256:5854b0c508e8d217ca205591384ab58389abdae608576f9c9afc35a3c76a366c", + "sha256:e3db6800abf7e36c38d2629b5cb6b74d10988ee0cba6fba45595a7cbe60c0042" + ], + "markers": "python_version >= '3.6'", + "version": "==26.1.0" + }, + "libsass": { + "hashes": [ + "sha256:081e256ab3c5f3f09c7b8dea3bf3bf5e64a97c6995fd9eea880639b3f93a9f9a", + "sha256:3ab5ad18e47db560f4f0c09e3d28cf3bb1a44711257488ac2adad69f4f7f8425", + "sha256:65455a2728b696b62100eb5932604aa13a29f4ac9a305d95773c14aaa7200aaf", + "sha256:89c5ce497fcf3aba1dd1b19aae93b99f68257e5f2026b731b00a872f13324c7f", + "sha256:f1efc1b612299c88aec9e39d6ca0c266d360daa5b19d9430bdeaffffa86993f9" + ], + "markers": "python_version >= '3.6'", + "version": "==0.22.0" + }, + "more-itertools": { + "hashes": [ + "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced", + "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713" + ], + "markers": "python_version >= '3.5'", + "version": "==8.7.0" + }, + "oauthlib": { + "hashes": [ + "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", + "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918" + ], + "markers": "python_version >= '3.6'", + "version": "==3.2.2" + }, + "phonenumbers": { + "hashes": [ + "sha256:23944f9e628f32a975d3b221b6d76e6ba8ae618d53cb3d82fc23d9e100a59b29", + "sha256:70aa98a50ba7bc7f6bf17851f806c927107e7c44e7d21eb46bdbec07b99d23ae" + ], + "index": "pypi", + "version": "==8.12.12" + }, + "pyasn1": { + "hashes": [ + "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57", + "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==0.5.0" + }, + "pyasn1-modules": { + "hashes": [ + "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c", + "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==0.3.0" + }, + "pyhamcrest": { + "hashes": [ + "sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316", + "sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29" + ], + "markers": "python_version >= '3.5'", + "version": "==2.0.2" + }, + "pyjwt": { + "hashes": [ + "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd", + "sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14" + ], + "markers": "python_version >= '3.7'", + "version": "==2.6.0" + }, + "pypng": { + "hashes": [ + "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c", + "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1" + ], + "version": "==0.20220715.0" + }, + "python-dateutil": { + "hashes": [ + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.8.2" + }, "pytz": { "hashes": [ "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b", @@ -47,6 +428,125 @@ ], "version": "==2023.3.post1" }, + "pyyaml": { + "hashes": [ + "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", + "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", + "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", + "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", + "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", + "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", + "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", + "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", + "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", + "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", + "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", + "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", + "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", + "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", + "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", + "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", + "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", + "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", + "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", + "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", + "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", + "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", + "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", + "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", + "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", + "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", + "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", + "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", + "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", + "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", + "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", + "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", + "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", + "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", + "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", + "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", + "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", + "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", + "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", + "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", + "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", + "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", + "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", + "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", + "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", + "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", + "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", + "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", + "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", + "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" + ], + "markers": "python_version >= '3.6'", + "version": "==6.0.1" + }, + "qrcode": { + "hashes": [ + "sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a", + "sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845" + ], + "markers": "python_version >= '3.7'", + "version": "==7.4.2" + }, + "rapid-router": { + "hashes": [ + "sha256:2d4e1069755f2a96cedf670c3e544508a0764f47fcf9963f75247f99b070d894", + "sha256:7f617e37600f3a83be6dc6669786aa2582b9703f94a81cf316dfa44003a04f3e" + ], + "index": "pypi", + "version": "==5.11.0" + }, + "requests": { + "hashes": [ + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + ], + "markers": "python_version >= '3.7'", + "version": "==2.31.0" + }, + "requests-oauthlib": { + "hashes": [ + "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5", + "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.3.1" + }, + "rsa": { + "hashes": [ + "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", + "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21" + ], + "markers": "python_version >= '3.6' and python_version < '4'", + "version": "==4.9" + }, + "setuptools": { + "hashes": [ + "sha256:56ee14884fd8d0cd015411f4a13f40b4356775a0aefd9ebc1d3bfb9a1acb32f1", + "sha256:eff96148eb336377ab11beee0c73ed84f1709a40c0b870298b0d058828761bae" + ], + "markers": "python_version >= '3.8'", + "version": "==68.2.1" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "sortedcontainers": { + "hashes": [ + "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", + "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" + ], + "version": "==2.4.0" + }, "sqlparse": { "hashes": [ "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3", @@ -60,8 +560,24 @@ "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2" ], - "markers": "python_version < '3.11'", + "markers": "python_version >= '3.7'", "version": "==4.7.1" + }, + "urllib3": { + "hashes": [ + "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f", + "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.16" + }, + "websocket-client": { + "hashes": [ + "sha256:3aad25d31284266bcfcfd1fd8a743f63282305a364b8d0948a43bd606acc652f", + "sha256:6cfc30d051ebabb73a5fa246efdcc14c8fbebbd0330f8984ac3bb6d9edd2ad03" + ], + "markers": "python_version >= '3.8'", + "version": "==1.6.3" } }, "develop": { @@ -91,6 +607,7 @@ "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301" ], "index": "pypi", + "markers": "python_version >= '3.8'", "version": "==23.9.1" }, "click": { @@ -163,6 +680,7 @@ "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069" ], "index": "pypi", + "markers": "python_version >= '3.7'", "version": "==7.4.2" }, "pytest-django": { @@ -171,6 +689,7 @@ "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2" ], "index": "pypi", + "markers": "python_version >= '3.5'", "version": "==4.5.2" }, "pytest-env": { @@ -179,6 +698,7 @@ "sha256:baed9b3b6bae77bd75b9238e0ed1ee6903a42806ae9d6aeffb8754cd5584d4ff" ], "index": "pypi", + "markers": "python_version >= '3.7'", "version": "==0.8.2" }, "tomli": { @@ -194,7 +714,7 @@ "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2" ], - "markers": "python_version < '3.11'", + "markers": "python_version >= '3.7'", "version": "==4.7.1" } } diff --git a/backend/api/tests/views/session.py b/backend/api/tests/views/session.py index e69de29..8c7e4c5 100644 --- a/backend/api/tests/views/session.py +++ b/backend/api/tests/views/session.py @@ -0,0 +1,9 @@ +from django.test import TestCase + + +class LoginViewTests(TestCase): + fixtures = [] + + def test_otp(self): + # TODO: implement and test otp login + pass diff --git a/backend/api/urls/session.py b/backend/api/urls/session.py index 88f9585..3309039 100644 --- a/backend/api/urls/session.py +++ b/backend/api/urls/session.py @@ -1,6 +1,7 @@ +from django.contrib.auth.views import LogoutView from django.urls import path -from ..views.session import LoginView, LogoutView +from ..views.session import LoginView urlpatterns = [ path("login/", LoginView.as_view(), name="login"), diff --git a/backend/api/views/session.py b/backend/api/views/session.py index 6cc6d95..af18802 100644 --- a/backend/api/views/session.py +++ b/backend/api/views/session.py @@ -1,12 +1,6 @@ -from django.contrib.auth.views import ( - LoginView as _LoginView, - LogoutView as _LogoutView, -) +from django.contrib.auth.views import LoginView as _LoginView +# TODO: add 2FA logic class LoginView(_LoginView): pass - - -class LogoutView(_LogoutView): - pass diff --git a/backend/portal/__init__.py b/backend/portal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/portal/apps.py b/backend/portal/apps.py new file mode 100644 index 0000000..d912bf9 --- /dev/null +++ b/backend/portal/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PortalConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "portal" diff --git a/backend/portal/migrations/0001_squashed_0041_new_news.py b/backend/portal/migrations/0001_squashed_0041_new_news.py new file mode 100644 index 0000000..a6fa448 --- /dev/null +++ b/backend/portal/migrations/0001_squashed_0041_new_news.py @@ -0,0 +1,736 @@ +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + +from django.contrib.auth.hashers import make_password +from django.utils import timezone +import os + +# Functions from the following migrations need manual copying. +# Move them and any dependencies into this file, then update the +# RunPython operations to refer to the local versions: +# portal.migrations.0001_squashed_0036_data_viewing_user_fix +# portal.migrations.0037_userprofile_developer +# portal.migrations.0041_new_news +# portal.migrations.0040_initial_news + + +def insert_admin_user(apps, schema_editor): + User = apps.get_model("auth", "User") + admin = User.objects.create() + admin.username = "portaladmin" + admin.email = ("codeforlife-portal@ocado.com",) + admin.is_superuser = True + admin.is_staff = True + admin.password = make_password(os.getenv("ADMIN_PASSWORD", "abc123")) + admin.save() + + +def insert_users(apps, schema_editor): + User = apps.get_model("auth", "User") + UserProfile = apps.get_model("portal", "UserProfile") + School = apps.get_model("portal", "School") + Teacher = apps.get_model("portal", "Teacher") + Class = apps.get_model("portal", "Class") + Student = apps.get_model("portal", "Student") + + # Create school + school = School.objects.create( + name="Swiss Federal Polytechnic", + postcode="AL10 9NE", + town="Welwyn Hatfield", + latitude="51.76183", + longitude="-0.244361", + ) + + # Create teachers' users + teacher_user = User.objects.create( + username="test teacher", + first_name="Albert", + last_name="Einstein", + email="alberteinstein@codeforlife.com", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + teacher2_user = User.objects.create( + username="test teacher2", + first_name="Max", + last_name="Planck", + email="maxplanck@codeforlife.com", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + teacher3_user = User.objects.create( + username="media ram", + first_name="Ram", + last_name="Leith", + email="ramleith@codeforlife.com", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + # Create students' users + student1_user = User.objects.create( + username="test student1", + first_name="Leonardo", + last_name="DaVinci", + email="leonardodavinci@codeforlife.com", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + student2_user = User.objects.create( + username="test student2", + first_name="Galileo", + last_name="Galilei", + email="galileogalilei@codeforlife.com", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + student3_user = User.objects.create( + username="Issac", + first_name="Isaac", + last_name="Newton", + email="isaacnewton@codeforlife.com", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + student4_user = User.objects.create( + username="test student4", + first_name="Richard", + last_name="Feynman", + email="richardfeynman@codeforlife.com", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + student5_user = User.objects.create( + username="test student5", + first_name="Alexander", + last_name="Flemming", + email="alexanderflemming@codeforlife.com", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + student6_user = User.objects.create( + username="test student6", + first_name="Daniel", + last_name="Bernoulli", + email="danielbernoulli@codeforlife.com", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + indy_user = User.objects.create( + username="indy", + first_name="Indiana", + last_name="Jones", + email="indianajones@codeforlife.com", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + student7_user = User.objects.create( + username="media noah", + first_name="Noah", + last_name="Monaghan", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + student8_user = User.objects.create( + username="media elliot", + first_name="Elliot", + last_name="Sharp", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + student9_user = User.objects.create( + username="media tajmae", + first_name="Tajmae", + last_name="Joseph", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + student10_user = User.objects.create( + username="media carlton", + first_name="Carlton", + last_name="Joseph", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + student11_user = User.objects.create( + username="media nadal", + first_name="Nadal", + last_name="Spencer-Jennings", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + student12_user = User.objects.create( + username="media freddie", + first_name="Freddie", + last_name="Goff", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + student13_user = User.objects.create( + username="media leon", + first_name="Leon", + last_name="Scott", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + student14_user = User.objects.create( + username="media betty", + first_name="Betty", + last_name="Kessell", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + # Create UserProfiles + teacher_userprofile = UserProfile.objects.create(user=teacher_user, developer=True) + teacher2_userprofile = UserProfile.objects.create(user=teacher2_user) + teacher3_userprofile = UserProfile.objects.create(user=teacher3_user) + student1_userprofile = UserProfile.objects.create( + user=student1_user, developer=True + ) + student2_userprofile = UserProfile.objects.create(user=student2_user) + student3_userprofile = UserProfile.objects.create(user=student3_user) + student4_userprofile = UserProfile.objects.create(user=student4_user) + student5_userprofile = UserProfile.objects.create(user=student5_user) + student6_userprofile = UserProfile.objects.create(user=student6_user) + indy_userprofile = UserProfile.objects.create(user=indy_user) + student7_userprofile = UserProfile.objects.create(user=student7_user) + student8_userprofile = UserProfile.objects.create(user=student8_user) + student9_userprofile = UserProfile.objects.create(user=student9_user) + student10_userprofile = UserProfile.objects.create(user=student10_user) + student11_userprofile = UserProfile.objects.create(user=student11_user) + student12_userprofile = UserProfile.objects.create(user=student12_user) + student13_userprofile = UserProfile.objects.create(user=student13_user) + student14_userprofile = UserProfile.objects.create(user=student14_user) + + # Create teachers + teacher = Teacher.objects.create( + title="Mr", + user=teacher_userprofile, + school=school, + is_admin=True, + pending_join_request=None, + ) + teacher2 = Teacher.objects.create( + title="Mr", + user=teacher2_userprofile, + school=school, + is_admin=False, + pending_join_request=None, + ) + teacher3 = Teacher.objects.create( + title="Mrs", + user=teacher3_userprofile, + school=school, + is_admin=True, + pending_join_request=None, + ) + + # Create classes + klass = Class.objects.create( + name="Class 101", + teacher=teacher, + access_code="AB123", + classmates_data_viewable=True, + always_accept_requests=True, + ) + class2 = Class.objects.create( + name="Class 102", + teacher=teacher2, + access_code="AB124", + classmates_data_viewable=True, + always_accept_requests=True, + ) + + class3 = Class.objects.create( + name="Class 103", + teacher=teacher2, + access_code="AB125", + classmates_data_viewable=True, + always_accept_requests=True, + ) + + class4 = Class.objects.create( + name="Young Coders 101", + teacher=teacher3, + access_code="RL123", + classmates_data_viewable=True, + always_accept_requests=True, + ) + + # Create students + student1 = Student.objects.create( + class_field=klass, user=student1_userprofile, pending_class_request=None + ) + + student2 = Student.objects.create( + class_field=klass, user=student2_userprofile, pending_class_request=None + ) + + student3 = Student.objects.create( + class_field=None, user=student3_userprofile, pending_class_request=None + ) + + student4 = Student.objects.create( + class_field=class2, user=student4_userprofile, pending_class_request=None + ) + + student5 = Student.objects.create( + class_field=class2, user=student5_userprofile, pending_class_request=None + ) + + student6 = Student.objects.create( + class_field=class3, user=student6_userprofile, pending_class_request=None + ) + + indy_student = Student.objects.create( + user=indy_userprofile, pending_class_request=None + ) + + student7 = Student.objects.create( + class_field=class4, user=student7_userprofile, pending_class_request=None + ) + + student8 = Student.objects.create( + class_field=class4, user=student8_userprofile, pending_class_request=None + ) + + student9 = Student.objects.create( + class_field=class4, user=student9_userprofile, pending_class_request=None + ) + + student10 = Student.objects.create( + class_field=class4, user=student10_userprofile, pending_class_request=None + ) + + student11 = Student.objects.create( + class_field=class4, user=student11_userprofile, pending_class_request=None + ) + + student12 = Student.objects.create( + class_field=class4, user=student12_userprofile, pending_class_request=None + ) + + student13 = Student.objects.create( + class_field=class4, user=student13_userprofile, pending_class_request=None + ) + + student14 = Student.objects.create( + class_field=class4, user=student14_userprofile, pending_class_request=None + ) + + user = User.objects.create( + username="DATA_AGGREGATE", + first_name="DATA", + last_name="AGGREGATE", + email="aggregator@codeforlife.com", + password=make_password(os.getenv("DATA_AGGREGATE_PASSWORD", "Password1")), + ) + + user_profile = UserProfile.objects.create(user=user, can_view_aggregated_data=True) + + +def insert_news(apps, schema_editor): + FrontPageNews = apps.get_model("portal", "FrontPageNews") + + news1 = FrontPageNews.objects.create( + title="Teachers 'not confident on coding'", + text="Computer coding is being introduced to the school curriculum in less than six weeks' time, but many primary school teachers will be under-prepared to teach the new subject, according to a new poll.", + link="http://www.dailymail.co.uk/wires/pa/article-2700124/TEACHERS-NOT-CONFIDENT-ON-CODING.html?ITO=1490&ns_mchannel=rss&ns_campaign=1490", + link_text="Read more on www.dailymail.co.uk", + added_dstamp=timezone.now(), + ) + news2 = FrontPageNews.objects.create( + title="British schools are not prepared to teach coding", + text="With just six weeks until the new computing curriculum is introduced in UK schools, research has revealed that British primary school teachers are not fully prepared to teach their pupils how to code.", + link="http://www.cbronline.com/news/social/british-teachers-are-not-prepared-to-teach-coding-4322259", + link_text="Read more on www.cbronline.co.uk", + added_dstamp=timezone.now(), + ) + news3 = FrontPageNews.objects.create( + title="Ocado launches free Code For Life tool to help 130,000 apprehensive primary school teachers", + text="Thousands of primary school teachers aren’t ready to teach computer coding lessons that become part of the country’s national curriculum for computing before the start of the academic year in September.", + link="http://www.itproportal.com/2014/07/21/ocado-launches-free-code-life-tool-help-130000-apprehensive-primary-school-teachers/?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+itproportal%2Frss+(Latest+ITProPortal+News)", + link_text="Read more on itproportal.com", + added_dstamp=timezone.now(), + ) + news4 = FrontPageNews.objects.create( + title="Ocado Technology launches coding resources for teachers", + text="Ocado Technology is the latest business to get involved with preparing teachers for the new computing curriculum that comes into effect in September", + link="http://www.computerworlduk.com/news/careers/3531070/ocado-technology-launches-coding-resources-for-teachers/", + link_text="Read more on www.computerworlduk.com", + added_dstamp=timezone.now(), + ) + news5 = FrontPageNews.objects.create( + title="Not Enough Teachers Will Know Code By September", + text="A new curriculum brings coding into primary schools in September – and it looks as if teachers won’t be able to cope. ", + link="http://www.techweekeurope.co.uk/news/not-enough-teachers-know-code-149462", + link_text="Read more on www.techweekeurope.co.uk", + added_dstamp=timezone.now(), + ) + news6 = FrontPageNews.objects.create( + title="Ocado Technology readies primary school teachers with code initiative", + text="Ocado Technology has launched a coding initiative after finding that 73% of primary school teachers feel they have not been given the necessary resources to teach children to code.", + link="http://www.computerweekly.com/news/2240225117/Ocado-Technology-readies-primary-school-teachers-with-code-initiative", + link_text="Read more on www.computerweekly.com", + added_dstamp=timezone.now(), + ) + news7 = FrontPageNews.objects.create( + title="Curriculum countdown", + text="According to new research more than 130,000 primary school teachers don’t feel confident enough to teach computer coding", + link="http://edtechnology.co.uk/Featured-Content/curriculum_countdown", + link_text="Read more on www.edtechnology.co.uk", + added_dstamp=timezone.now(), + ) + news8 = FrontPageNews.objects.create( + title="Computing Curriculum Countdown: Over 130,000 Primary School Teachers Don’t Feel Confident Enough to Teach Computer Coding", + text="Many primary school teachers feel they haven’t been given the necessary resources to teach the new Computing curriculum from September. ", + link="http://www.primarytimes.net/news/2014/07/computing-curriculum-countdown-over-130-000-primary-school-teachers-don-t-feel-confident-enough-to-teach-computer-coding-", + link_text="Read more on www.primarytimes.net", + added_dstamp=timezone.now(), + ) + news9 = FrontPageNews.objects.create( + title="Ocado’s technology team to release primary school coding tool", + text="Ocado’s technology team is launching Code for Life, the online grocer’s initiative to help children learn to code.", + link="http://internetretailing.net/2014/07/ocados-technology-team-launch-primary-school-coding-tool/", + link_text="Read more on www.internetretailing.net", + added_dstamp=timezone.now(), + ) + news10 = FrontPageNews.objects.create( + title="Teachers 'not confident' over coding", + text="Computer coding is being introduced to the school curriculum in less than six weeks' time, but many primary school teachers will be under-prepared to teach the new subject, according to a new poll.", + link="http://www.dailyecho.co.uk/leisure/technology/11358091.Teachers__not_confident__over_coding/", + link_text="Read more on www.dailyecho.co.uk", + added_dstamp=timezone.now(), + ) + news11 = FrontPageNews.objects.create( + title="Ocado Technology launches coding resources for teachers", + text="Ocado Technology is a latest business to get concerned with scheming teachers for a new computing curriculum that comes into outcome in September.", + link="http://www.datacentremanagement.org/2014/07/ocado-technology-launches-coding-resources-for-teachers/", + link_text="Read more on www.datacentremanagement.org", + added_dstamp=timezone.now(), + ) + news12 = FrontPageNews.objects.create( + title="Teachers 'not confident enough' to teach code", + text="More than 70% of primary school teachers say they don't feel confident enough to teach the new coding syllabus being introduced this September.", + link="http://www.newelectronics.co.uk/electronics-news/teachers-not-confident-enough-to-teach-code/62697/", + link_text="Read more on www.newelectronics.co.uk", + added_dstamp=timezone.now(), + ) + + +def insert_news2(apps, schema_editor): + FrontPageNews = apps.get_model("portal", "FrontPageNews") + + news1 = FrontPageNews.objects.create( + title="Bletchley Park: From Code-Breaking to Kids Coding", + text="The National Museum of Computer has opened its free Weekend Codability Project. This is part of the Code for Life initiative which aims to inspire the next generation of computer scientists.", + link="http://www.idgconnect.com/blog-abstract/9056/bletchley-park-from-code-breaking-kids-coding", + link_text="Read more on idgconnect.com", + added_dstamp=timezone.now(), + ) + news2 = FrontPageNews.objects.create( + title="Giving kids Codability", + text="Weekend Codability aims to empower young people by introducing them to programming computers. Children will be taught how to give instructions to computers, change existing instructions in programs and create their own programs.", + link="http://edtechnology.co.uk/News/giving_kids_codability", + link_text="Read more on edtechnology.co.uk", + added_dstamp=timezone.now(), + ) + news3 = FrontPageNews.objects.create( + title="Try your hand at Bletchley Park codability project", + text="Following the introduction of computing to England’s school curriculum last month, young people across the country are being invited to try their hand at programming computers in Block H, the world's first purpose-built computer centre, on Bletchley Park.", + link="http://www.mkweb.co.uk/COMPUTERS-Try-hand-Bletchley-Park-codability/story-23860128-detail/story.html", + link_text="Read more on mkweb.co.uk", + added_dstamp=timezone.now(), + ) + + +class Migration(migrations.Migration): + + replaces = [ + ("portal", "0001_initial"), + ("portal", "0002_admin_user"), + ("portal", "0003_school_teacher_relationship"), + ("portal", "0004_class_access_code"), + ("portal", "0005_student_pin"), + ("portal", "0006_teacher_email_verification"), + ("portal", "0007_school_admin"), + ("portal", "0008_auto_20140729_0938"), + ("portal", "0009_remove_student_pin"), + ("portal", "0010_auto_20140729_1030"), + ("portal", "0009_teacher_pending_join_request"), + ("portal", "0011_merge"), + ("portal", "0012_auto_20140729_1359"), + ("portal", "0013_auto_20140730_1100"), + ("portal", "0014_auto_20140730_1459"), + ("portal", "0015_student_pending_class_request"), + ("portal", "0016_emailverification_email"), + ("portal", "0017_school_postcode"), + ("portal", "0018_auto_20140804_1246"), + ("portal", "0019_auto_20140804_1713"), + ("portal", "0020_auto_20140806_1632"), + ("portal", "0020_auto_20140806_1205"), + ("portal", "0021_merge"), + ("portal", "0022_remove_student_name"), + ("portal", "0023_class_classmates_data_viewable"), + ("portal", "0024_auto_20140813_1536"), + ("portal", "0025_trial_users"), + ("portal", "0026_fix_trial_users"), + ("portal", "0027_more_trial_users"), + ("portal", "0028_demoting_max_planck"), + ("portal", "0029_trial_independent_student"), + ("portal", "0030_media_users"), + ("portal", "0031_auto_20140903_1450"), + ("portal", "0032_userprofile_can_view_aggregated_data"), + ("portal", "0033_data_viewing_user"), + ("portal", "0034_data_viewing_user_fix"), + ("portal", "0035_data_viewing_user_hack"), + ("portal", "0036_data_viewing_user_fix"), + ("portal", "0037_userprofile_developer"), + ("portal", "0038_frontpagenews"), + ("portal", "0039_auto_20141109_1827"), + ("portal", "0040_initial_news"), + ("portal", "0041_new_news"), + ] + + dependencies = [ + ("auth", "0001_initial"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="UserProfile", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "avatar", + models.ImageField( + default="static/portal/img/avatars/default-avatar.jpeg", + null=True, + upload_to="static/portal/img/avatars/", + blank=True, + ), + ), + ( + "user", + models.OneToOneField( + to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE + ), + ), + ("awaiting_email_verification", models.BooleanField(default=False)), + ("can_view_aggregated_data", models.BooleanField(default=False)), + ("developer", models.BooleanField(default=False)), + ], + ), + migrations.CreateModel( + name="School", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ("name", models.CharField(max_length=200)), + ("postcode", models.CharField(max_length=10)), + ("latitude", models.CharField(max_length=20)), + ("longitude", models.CharField(max_length=20)), + ("town", models.CharField(max_length=200)), + ], + ), + migrations.CreateModel( + name="Teacher", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "user", + models.OneToOneField( + to="portal.UserProfile", on_delete=models.CASCADE + ), + ), + ( + "pending_join_request", + models.ForeignKey( + related_name="join_request", + to="portal.School", + null=True, + on_delete=models.SET_NULL, + ), + ), + ("is_admin", models.BooleanField(default=False)), + ("title", models.CharField(max_length=35)), + ( + "school", + models.ForeignKey( + related_name="teacher_school", + to="portal.School", + null=True, + on_delete=models.SET_NULL, + ), + ), + ], + ), + migrations.CreateModel( + name="Class", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ("name", models.CharField(max_length=200)), + ("access_code", models.CharField(max_length=5)), + ("classmates_data_viewable", models.BooleanField(default=False)), + ("accept_requests_until", models.DateTimeField(null=True)), + ("always_accept_requests", models.BooleanField(default=False)), + ( + "teacher", + models.ForeignKey( + related_name="class_teacher", + to="portal.Teacher", + on_delete=models.CASCADE, + ), + ), + ], + options={"verbose_name_plural": "classes"}, + ), + migrations.CreateModel( + name="Student", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "class_field", + models.ForeignKey( + related_name="students", + to="portal.Class", + null=True, + on_delete=models.CASCADE, + ), + ), + ( + "user", + models.OneToOneField( + to="portal.UserProfile", on_delete=models.CASCADE + ), + ), + ( + "pending_class_request", + models.ForeignKey( + related_name="class_request", + to="portal.Class", + null=True, + on_delete=models.CASCADE, + ), + ), + ], + ), + migrations.CreateModel( + name="Guardian", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ("name", models.CharField(max_length=200)), + ("children", models.ManyToManyField(to="portal.Student")), + ( + "user", + models.OneToOneField( + to="portal.UserProfile", on_delete=models.CASCADE + ), + ), + ], + ), + migrations.CreateModel( + name="EmailVerification", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ("token", models.CharField(max_length=30)), + ("expiry", models.DateTimeField()), + ("used", models.BooleanField(default=False)), + ( + "user", + models.ForeignKey( + related_name="email_verifications", + to="portal.UserProfile", + on_delete=models.CASCADE, + ), + ), + ( + "email", + models.CharField( + default=None, max_length=200, null=True, blank=True + ), + ), + ], + ), + migrations.CreateModel( + name="FrontPageNews", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ("title", models.CharField(max_length=200)), + ("text", models.CharField(max_length=1000)), + ("link", models.CharField(max_length=500)), + ("link_text", models.CharField(max_length=200)), + ("added_dstamp", models.DateTimeField()), + ], + ), + migrations.RunPython(code=insert_admin_user), + migrations.RunPython(code=insert_users), + migrations.RunPython(code=insert_news), + migrations.RunPython(code=insert_news2), + ] diff --git a/backend/portal/migrations/0042_school_country.py b/backend/portal/migrations/0042_school_country.py new file mode 100644 index 0000000..34d11d4 --- /dev/null +++ b/backend/portal/migrations/0042_school_country.py @@ -0,0 +1,17 @@ +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [("portal", "0001_squashed_0041_new_news")] + + operations = [ + migrations.AddField( + model_name="school", + name="country", + field=models.CharField(max_length=200, null=True, blank=True), + preserve_default=True, + ) + ] diff --git a/backend/portal/migrations/0043_auto_20150430_0952.py b/backend/portal/migrations/0043_auto_20150430_0952.py new file mode 100644 index 0000000..d22dc3c --- /dev/null +++ b/backend/portal/migrations/0043_auto_20150430_0952.py @@ -0,0 +1,19 @@ +from __future__ import unicode_literals + +from django.db import models, migrations +import django_countries.fields + + +class Migration(migrations.Migration): + + dependencies = [("portal", "0042_school_country")] + + operations = [ + migrations.AlterField( + model_name="school", + name="country", + field=django_countries.fields.CountryField( + max_length=2, null=True, blank=True + ), + ) + ] diff --git a/backend/portal/migrations/0044_auto_20150430_0959.py b/backend/portal/migrations/0044_auto_20150430_0959.py new file mode 100644 index 0000000..39a26aa --- /dev/null +++ b/backend/portal/migrations/0044_auto_20150430_0959.py @@ -0,0 +1,37 @@ +from __future__ import unicode_literals + +from builtins import str +from django.db import models, migrations + + +class Migration(migrations.Migration): + + # It will guess the country of the school using postcode, UK will be set if no result is returned by the API + def populate_country(apps, schema_editor): + + School = apps.get_model("portal", "School") + for school in School.objects.all(): + country, town, lat, lng = ( + "GB", + "0", + "0", + "0", + ) + school.country = str(country) + school.town = town + school.lat = lat + school.lng = lng + school.save() + + def reset_country(apps, schema_editor): + School = apps.get_model("portal", "School") + for school in School.objects.all(): + school.country = "" + school.town = "0" + school.lat = "0" + school.lng = "0" + school.save() + + dependencies = [("portal", "0043_auto_20150430_0952")] + + operations = [migrations.RunPython(populate_country, reset_country)] diff --git a/backend/portal/migrations/0045_auto_20150430_1446.py b/backend/portal/migrations/0045_auto_20150430_1446.py new file mode 100644 index 0000000..9787d5d --- /dev/null +++ b/backend/portal/migrations/0045_auto_20150430_1446.py @@ -0,0 +1,17 @@ +from __future__ import unicode_literals + +from django.db import models, migrations +import django_countries.fields + + +class Migration(migrations.Migration): + + dependencies = [("portal", "0044_auto_20150430_0959")] + + operations = [ + migrations.AlterField( + model_name="school", + name="country", + field=django_countries.fields.CountryField(max_length=2), + ) + ] diff --git a/backend/portal/migrations/0046_auto_20150723_1101.py b/backend/portal/migrations/0046_auto_20150723_1101.py new file mode 100644 index 0000000..2fee5b9 --- /dev/null +++ b/backend/portal/migrations/0046_auto_20150723_1101.py @@ -0,0 +1,20 @@ +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [("portal", "0045_auto_20150430_1446")] + + operations = [ + migrations.AlterModelOptions( + name="school", + options={ + "permissions": ( + ("view_aggregated_data", "Can see available aggregated data"), + ("view_map_data", "Can see schools' location displayed on map"), + ) + }, + ) + ] diff --git a/backend/portal/migrations/0047_remove_userprofile_avatar.py b/backend/portal/migrations/0047_remove_userprofile_avatar.py new file mode 100644 index 0000000..f150268 --- /dev/null +++ b/backend/portal/migrations/0047_remove_userprofile_avatar.py @@ -0,0 +1,10 @@ +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [("portal", "0046_auto_20150723_1101")] + + operations = [migrations.RemoveField(model_name="userprofile", name="avatar")] diff --git a/backend/portal/migrations/0048_plural_management_frontnews.py b/backend/portal/migrations/0048_plural_management_frontnews.py new file mode 100644 index 0000000..23e3e7b --- /dev/null +++ b/backend/portal/migrations/0048_plural_management_frontnews.py @@ -0,0 +1,14 @@ +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [("portal", "0047_remove_userprofile_avatar")] + + operations = [ + migrations.AlterModelOptions( + name="frontpagenews", options={"verbose_name_plural": "front page news"} + ) + ] diff --git a/backend/portal/migrations/0049_refactor_emailverifications.py b/backend/portal/migrations/0049_refactor_emailverifications.py new file mode 100644 index 0000000..7ce004a --- /dev/null +++ b/backend/portal/migrations/0049_refactor_emailverifications.py @@ -0,0 +1,32 @@ +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [("portal", "0048_plural_management_frontnews")] + + operations = [ + migrations.AddField( + model_name="emailverification", + name="new_user", + field=models.ForeignKey( + to=settings.AUTH_USER_MODEL, + related_name="email_verifications_old", + null=True, + blank=True, + on_delete=models.CASCADE, + ), + ), + migrations.AddField( + model_name="emailverification", + name="verified", + field=models.BooleanField(default=False), + ), + migrations.RunSQL( + "UPDATE portal_emailverification SET new_user_id = ( SELECT portal_userprofile.user_id FROM portal_userprofile WHERE portal_userprofile.id = portal_emailverification.user_id);" + ), + migrations.RunSQL("UPDATE portal_emailverification SET verified = used;"), + ] diff --git a/backend/portal/migrations/0050_refactor_emailverifications_2.py b/backend/portal/migrations/0050_refactor_emailverifications_2.py new file mode 100644 index 0000000..7dfa68d --- /dev/null +++ b/backend/portal/migrations/0050_refactor_emailverifications_2.py @@ -0,0 +1,27 @@ +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + dependencies = [("portal", "0049_refactor_emailverifications")] + + operations = [ + migrations.RemoveField(model_name="emailverification", name="user"), + migrations.RemoveField(model_name="emailverification", name="used"), + migrations.AddField( + model_name="emailverification", + name="user", + field=models.ForeignKey( + to=settings.AUTH_USER_MODEL, + related_name="email_verifications", + null=True, + blank=True, + on_delete=models.CASCADE, + ), + ), + migrations.RunSQL( + ("UPDATE portal_emailverification" " SET user_id = new_user_id;") + ), + ] diff --git a/backend/portal/migrations/0051_add_missing_ev_records.py b/backend/portal/migrations/0051_add_missing_ev_records.py new file mode 100644 index 0000000..ebeb8c1 --- /dev/null +++ b/backend/portal/migrations/0051_add_missing_ev_records.py @@ -0,0 +1,38 @@ +from __future__ import unicode_literals + +from uuid import uuid4 +from datetime import timedelta + +from django.db import migrations +from django.utils import timezone + + +def add_missing_ev_records(apps, schema_editor): + User = apps.get_model("auth", "User") + EmailVerification = apps.get_model("portal", "EmailVerification") + verified_users_without_ev = User.objects.filter( + userprofile__awaiting_email_verification=False + ).exclude(email_verifications__verified=True) + + records = [ + EmailVerification( + user=user, + email=user.email, + token=uuid4().hex[:30], + expiry=timezone.now() + timedelta(hours=1), + verified=True, + ) + for user in verified_users_without_ev + ] + + EmailVerification.objects.bulk_create(records) + + +def reverse(apps, schema_editor): + pass + + +class Migration(migrations.Migration): + dependencies = [("portal", "0050_refactor_emailverifications_2")] + + operations = [migrations.RunPython(add_missing_ev_records, reverse)] diff --git a/backend/portal/migrations/0052_refactor_emailverifications_3.py b/backend/portal/migrations/0052_refactor_emailverifications_3.py new file mode 100644 index 0000000..3c02bd3 --- /dev/null +++ b/backend/portal/migrations/0052_refactor_emailverifications_3.py @@ -0,0 +1,11 @@ +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [("portal", "0051_add_missing_ev_records")] + + operations = [ + migrations.RemoveField(model_name="emailverification", name="new_user") + ] diff --git a/backend/portal/migrations/0053_refactor_teacher_student_1.py b/backend/portal/migrations/0053_refactor_teacher_student_1.py new file mode 100644 index 0000000..e3e409b --- /dev/null +++ b/backend/portal/migrations/0053_refactor_teacher_student_1.py @@ -0,0 +1,65 @@ +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + dependencies = [("portal", "0052_refactor_emailverifications_3")] + + operations = [ + migrations.AddField( + model_name="teacher", + name="new_user", + field=models.OneToOneField( + to=settings.AUTH_USER_MODEL, + related_name="new_teacher", + null=True, + blank=True, + on_delete=models.CASCADE, + ), + ), + migrations.AddField( + model_name="student", + name="new_user", + field=models.OneToOneField( + to=settings.AUTH_USER_MODEL, + related_name="new_student", + null=True, + blank=True, + on_delete=models.CASCADE, + ), + ), + migrations.AddField( + model_name="guardian", + name="new_user", + field=models.OneToOneField( + to=settings.AUTH_USER_MODEL, + related_name="new_guardian", + null=True, + blank=True, + on_delete=models.CASCADE, + ), + ), + migrations.RunSQL( + ( + "UPDATE portal_teacher SET new_user_id = (" + " SELECT portal_userprofile.user_id FROM portal_userprofile " + " WHERE portal_userprofile.id = portal_teacher.user_id);" + ) + ), + migrations.RunSQL( + ( + "UPDATE portal_student SET new_user_id = (" + " SELECT portal_userprofile.user_id FROM portal_userprofile " + " WHERE portal_userprofile.id = portal_student.user_id);" + ) + ), + migrations.RunSQL( + ( + "UPDATE portal_guardian SET new_user_id = (" + " SELECT portal_userprofile.user_id FROM portal_userprofile " + " WHERE portal_userprofile.id = portal_guardian.user_id);" + ) + ), + ] diff --git a/backend/portal/migrations/0054_pending_join_request_can_be_blank.py b/backend/portal/migrations/0054_pending_join_request_can_be_blank.py new file mode 100644 index 0000000..4ef7717 --- /dev/null +++ b/backend/portal/migrations/0054_pending_join_request_can_be_blank.py @@ -0,0 +1,22 @@ +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [("portal", "0053_refactor_teacher_student_1")] + + operations = [ + migrations.AlterField( + model_name="teacher", + name="pending_join_request", + field=models.ForeignKey( + related_name="join_request", + blank=True, + to="portal.School", + null=True, + on_delete=models.SET_NULL, + ), + ) + ] diff --git a/backend/portal/migrations/0055_add_preview_user.py b/backend/portal/migrations/0055_add_preview_user.py new file mode 100644 index 0000000..1fb634c --- /dev/null +++ b/backend/portal/migrations/0055_add_preview_user.py @@ -0,0 +1,21 @@ +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [("portal", "0054_pending_join_request_can_be_blank")] + + operations = [ + migrations.AddField( + model_name="school", + name="eligible_for_testing", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="userprofile", + name="preview_user", + field=models.BooleanField(default=False), + ), + ] diff --git a/backend/portal/migrations/0056_remove_preview_user.py b/backend/portal/migrations/0056_remove_preview_user.py new file mode 100644 index 0000000..1cb4b2d --- /dev/null +++ b/backend/portal/migrations/0056_remove_preview_user.py @@ -0,0 +1,14 @@ +# Generated by Django 1.10.8 on 2019-08-27 08:37 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [("portal", "0055_add_preview_user")] + + operations = [ + migrations.RemoveField(model_name="userprofile", name="preview_user"), + migrations.RemoveField(model_name="school", name="eligible_for_testing"), + ] diff --git a/backend/portal/migrations/0057_delete_frontpagenews.py b/backend/portal/migrations/0057_delete_frontpagenews.py new file mode 100644 index 0000000..76ed4a9 --- /dev/null +++ b/backend/portal/migrations/0057_delete_frontpagenews.py @@ -0,0 +1,11 @@ +# Generated by Django 1.11.24 on 2020-03-02 13:10 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [("portal", "0056_remove_preview_user")] + + operations = [migrations.DeleteModel(name="FrontPageNews")] diff --git a/backend/portal/migrations/0058_move_to_common_models.py b/backend/portal/migrations/0058_move_to_common_models.py new file mode 100644 index 0000000..ff9c1dd --- /dev/null +++ b/backend/portal/migrations/0058_move_to_common_models.py @@ -0,0 +1,217 @@ +""" +This migration essentially "replaces" the majority of the models in models.py with +the models in common/models.py. +The models affected here are UserProfile, School, Teacher, Class and Student. +For each of these models, this migration: +- removes each of the model fields, +- updates Guardian's foreign keys to use the models in common/models.py, +- deletes the model, and updates the DB's references of these models to the new ones +created in common/models.py. + +The important thing to note here is the use of migrations.SeparateDatabaseAndState. +This operation makes it possible to makes different changes to the state and to the +database. Essentially this is used here to replace Portal's models with Common's +models, without moving or deleting anything from the database. +This migration has been made following the tutorial on how to move Django models: +https://realpython.com/move-django-model/ (following the Django way example). + +Since Rapid Router's models used to reference Portal's models, Rapid Router's migration +0071 needs to happen first, to avoid causing an error when migrating. +""" +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + atomic = False + + dependencies = [ + ("common", "0001_initial"), + ("game", "0071_use_common_models"), + ("portal", "0057_delete_frontpagenews"), + ] + + operations = [ + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.RemoveField( + model_name="class", + name="teacher", + ), + ], + database_operations=[], + ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.RemoveField( + model_name="student", + name="class_field", + ), + ], + database_operations=[], + ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.RemoveField( + model_name="student", + name="new_user", + ), + ], + database_operations=[], + ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.RemoveField( + model_name="student", + name="pending_class_request", + ), + ], + database_operations=[], + ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.RemoveField( + model_name="student", + name="user", + ), + ], + database_operations=[], + ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.RemoveField( + model_name="teacher", + name="new_user", + ), + ], + database_operations=[], + ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.RemoveField( + model_name="teacher", + name="pending_join_request", + ), + ], + database_operations=[], + ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.RemoveField( + model_name="teacher", + name="school", + ), + ], + database_operations=[], + ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.RemoveField( + model_name="teacher", + name="user", + ), + ], + database_operations=[], + ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.RemoveField( + model_name="userprofile", + name="user", + ), + ], + database_operations=[], + ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.AlterField( + model_name="guardian", + name="children", + field=models.ManyToManyField(to="common.Student"), + ), + ], + # You're reusing an existing table, so do nothing + database_operations=[], + ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.AlterField( + model_name="guardian", + name="user", + field=models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to="common.UserProfile", + ), + ), + ], + # You're reusing an existing table, so do nothing + database_operations=[], + ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.DeleteModel( + name="Class", + ), + ], + database_operations=[ + migrations.AlterModelTable( + name="Class", + table="common_class", + ), + ], + ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.DeleteModel( + name="School", + ), + ], + database_operations=[ + migrations.AlterModelTable( + name="School", + table="common_school", + ), + ], + ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.DeleteModel( + name="Student", + ), + ], + database_operations=[ + migrations.AlterModelTable( + name="Student", + table="common_student", + ), + ], + ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.DeleteModel( + name="Teacher", + ), + ], + database_operations=[ + migrations.AlterModelTable( + name="Teacher", + table="common_teacher", + ), + ], + ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.DeleteModel( + name="UserProfile", + ), + ], + database_operations=[ + migrations.AlterModelTable( + name="UserProfile", + table="common_userprofile", + ), + ], + ), + ] diff --git a/backend/portal/migrations/0059_move_email_verifications_to_common.py b/backend/portal/migrations/0059_move_email_verifications_to_common.py new file mode 100644 index 0000000..bca5f00 --- /dev/null +++ b/backend/portal/migrations/0059_move_email_verifications_to_common.py @@ -0,0 +1,34 @@ +# Generated by Django 1.11.24 on 2020-09-10 08:46 +from __future__ import unicode_literals + +from django.db import migrations +from django.db.migrations.operations.special import SeparateDatabaseAndState + + +class Migration(migrations.Migration): + atomic = False + + dependencies = [ + ("portal", "0058_move_to_common_models"), + ("common", "0002_emailverification"), + ] + + operations = [ + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.RemoveField( + model_name="emailverification", + name="user", + ), + ], + database_operations=[], + ), + migrations.SeparateDatabaseAndState( + state_operations=[migrations.DeleteModel(name="EmailVerification")], + database_operations=[ + migrations.AlterModelTable( + name="EmailVerification", table="common_emailverification" + ) + ], + ), + ] diff --git a/backend/portal/migrations/0060_delete_guardian.py b/backend/portal/migrations/0060_delete_guardian.py new file mode 100644 index 0000000..f6501c0 --- /dev/null +++ b/backend/portal/migrations/0060_delete_guardian.py @@ -0,0 +1,28 @@ +# Generated by Django 2.0 on 2020-10-28 17:36 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("portal", "0059_move_email_verifications_to_common"), + ] + + operations = [ + migrations.RemoveField( + model_name="guardian", + name="children", + ), + migrations.RemoveField( + model_name="guardian", + name="new_user", + ), + migrations.RemoveField( + model_name="guardian", + name="user", + ), + migrations.DeleteModel( + name="Guardian", + ), + ] diff --git a/backend/portal/migrations/0061_make_portaladmin_teacher.py b/backend/portal/migrations/0061_make_portaladmin_teacher.py new file mode 100644 index 0000000..c239c14 --- /dev/null +++ b/backend/portal/migrations/0061_make_portaladmin_teacher.py @@ -0,0 +1,114 @@ +# Generated by Django 2.0 on 2020-10-28 17:36 + +import os + +from django.contrib.auth.hashers import make_password +from django.db import migrations + + +def give_portaladmin_teacher_profile(apps, schema_editor): + """ + This migration is so that we can still log in using the portaladmin User, but from + the teacher login form, which requires school data to be linked to the User. + """ + User = apps.get_model("auth", "User") + UserProfile = apps.get_model("common", "UserProfile") + School = apps.get_model("common", "School") + Teacher = apps.get_model("common", "Teacher") + Class = apps.get_model("common", "Class") + Student = apps.get_model("common", "Student") + + # Amend portaladmin details + portaladmin = User.objects.get(username="portaladmin") + portaladmin.first_name = "Portal" + portaladmin.last_name = "Admin" + portaladmin.email = "codeforlife-portal@ocado.com" + portaladmin.save() + + # Create portaladmin UserProfile + portaladmin_userprofile = UserProfile.objects.create(user=portaladmin) + + # Find test school + portaladmin_school, _ = School.objects.get_or_create( + name="Swiss Federal Polytechnic" + ) + + # Create Teacher object and link it to School + portaladmin_teacher = Teacher.objects.create( + title="Mr", + user=portaladmin_userprofile, + new_user=portaladmin, + school=portaladmin_school, + is_admin=True, + pending_join_request=None, + ) + + # Create a Class + portaladmin_class = Class.objects.create( + name="Portaladmin's class", + teacher=portaladmin_teacher, + access_code="PO123", + classmates_data_viewable=True, + always_accept_requests=True, + ) + + # Create the student User + portaladmin_student_user = User.objects.create( + username="portaladmin student", + first_name="Portaladmin", + last_name="Student", + email="adminstudent@codeforlife.com", + password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), + ) + + # Create the student UserProfile + portaladmin_student_userprofile = UserProfile.objects.create( + user=portaladmin_student_user + ) + + # Create the Student + Student.objects.create( + class_field=portaladmin_class, + user=portaladmin_student_userprofile, + new_user=portaladmin_student_user, + pending_class_request=None, + ) + + +def revert_portaladmin_data(apps, schema_editor): + User = apps.get_model("auth", "User") + UserProfile = apps.get_model("common", "UserProfile") + Teacher = apps.get_model("common", "Teacher") + Class = apps.get_model("common", "Class") + Student = apps.get_model("common", "Student") + + portaladmin = User.objects.get(username="portaladmin") + portaladmin_userprofile = UserProfile.objects.get(user=portaladmin) + portaladmin_teacher = Teacher.objects.get(new_user=portaladmin) + portaladmin_class = Class.objects.get(teacher=portaladmin_teacher) + portaladmin_student_user = User.objects.get(username="portaladmin student") + portaladmin_student_userprofile = UserProfile.objects.get( + user=portaladmin_student_user + ) + portaladmin_student = Student.objects.get(user=portaladmin_student_userprofile) + + portaladmin_student.delete() + portaladmin_student_userprofile.delete() + portaladmin_student_user.delete() + portaladmin_class.delete() + portaladmin_teacher.delete() + portaladmin_userprofile.delete() + + portaladmin.first_name = "" + portaladmin.last_name = "" + portaladmin.email = "('codeforlife-portal@ocado.com',)" + portaladmin.save() + + +class Migration(migrations.Migration): + + dependencies = [("portal", "0060_delete_guardian")] + + operations = [ + migrations.RunPython(give_portaladmin_teacher_profile, revert_portaladmin_data) + ] diff --git a/backend/portal/migrations/0062_verify_portaladmin.py b/backend/portal/migrations/0062_verify_portaladmin.py new file mode 100644 index 0000000..6b7997d --- /dev/null +++ b/backend/portal/migrations/0062_verify_portaladmin.py @@ -0,0 +1,44 @@ +# Generated by Django 2.0 on 2020-10-28 17:36 + +import datetime +from uuid import uuid4 + +from django.db import migrations +from django.utils import timezone + + +def verify_portaladmin(apps, schema_editor): + """ + This migration is so that we can still log in using the portaladmin User, as the + login form now requires email verification. + """ + User = apps.get_model("auth", "User") + EmailVerification = apps.get_model("common", "EmailVerification") + + portaladmin = User.objects.get(username="portaladmin") + + EmailVerification.objects.create( + user=portaladmin, + email=portaladmin.email, + token=uuid4().hex[:30], + expiry=timezone.now() + datetime.timedelta(hours=1), + verified=True, + ) + + +def revert_portaladmin_verification(apps, schema_editor): + User = apps.get_model("auth", "User") + EmailVerification = apps.get_model("common", "EmailVerification") + + portaladmin = User.objects.get(username="portaladmin") + portaladmin_verification = EmailVerification.objects.get(user=portaladmin) + portaladmin_verification.delete() + + +class Migration(migrations.Migration): + + dependencies = [("portal", "0061_make_portaladmin_teacher")] + + operations = [ + migrations.RunPython(verify_portaladmin, revert_portaladmin_verification) + ] diff --git a/backend/portal/migrations/__init__.py b/backend/portal/migrations/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/backend/portal/migrations/__init__.py @@ -0,0 +1 @@ + diff --git a/backend/service/settings.py b/backend/service/settings.py index a2faf66..4d917ca 100644 --- a/backend/service/settings.py +++ b/backend/service/settings.py @@ -10,6 +10,7 @@ https://docs.djangoproject.com/en/3.2/ref/settings/ """ +import os from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. @@ -36,8 +37,12 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", - # 'codeforlife', + # "codeforlife", "api", + "aimmo", # TODO: remove this + "game", # TODO: remove this + "common", # TODO: remove this + "portal", # TODO: remove this ] MIDDLEWARE = [ @@ -77,7 +82,7 @@ DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", - "NAME": BASE_DIR / "db.sqlite3", + "NAME": Path(os.getenv("DB_NAME", BASE_DIR / "db.sqlite3")).resolve(), } } From cf5526dfb66358621a91ac0ff50565f83c054be7 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Tue, 12 Sep 2023 16:20:45 +0100 Subject: [PATCH 04/58] install portal instead --- backend/Pipfile | 1 + backend/Pipfile.lock | 369 +++++++-- backend/portal/__init__.py | 0 backend/portal/apps.py | 6 - .../migrations/0001_squashed_0041_new_news.py | 736 ------------------ .../portal/migrations/0042_school_country.py | 17 - .../migrations/0043_auto_20150430_0952.py | 19 - .../migrations/0044_auto_20150430_0959.py | 37 - .../migrations/0045_auto_20150430_1446.py | 17 - .../migrations/0046_auto_20150723_1101.py | 20 - .../0047_remove_userprofile_avatar.py | 10 - .../0048_plural_management_frontnews.py | 14 - .../0049_refactor_emailverifications.py | 32 - .../0050_refactor_emailverifications_2.py | 27 - .../migrations/0051_add_missing_ev_records.py | 38 - .../0052_refactor_emailverifications_3.py | 11 - .../0053_refactor_teacher_student_1.py | 65 -- .../0054_pending_join_request_can_be_blank.py | 22 - .../migrations/0055_add_preview_user.py | 21 - .../migrations/0056_remove_preview_user.py | 14 - .../migrations/0057_delete_frontpagenews.py | 11 - .../migrations/0058_move_to_common_models.py | 217 ------ ...0059_move_email_verifications_to_common.py | 34 - .../portal/migrations/0060_delete_guardian.py | 28 - .../0061_make_portaladmin_teacher.py | 114 --- .../migrations/0062_verify_portaladmin.py | 44 -- backend/portal/migrations/__init__.py | 1 - 27 files changed, 301 insertions(+), 1624 deletions(-) delete mode 100644 backend/portal/__init__.py delete mode 100644 backend/portal/apps.py delete mode 100644 backend/portal/migrations/0001_squashed_0041_new_news.py delete mode 100644 backend/portal/migrations/0042_school_country.py delete mode 100644 backend/portal/migrations/0043_auto_20150430_0952.py delete mode 100644 backend/portal/migrations/0044_auto_20150430_0959.py delete mode 100644 backend/portal/migrations/0045_auto_20150430_1446.py delete mode 100644 backend/portal/migrations/0046_auto_20150723_1101.py delete mode 100644 backend/portal/migrations/0047_remove_userprofile_avatar.py delete mode 100644 backend/portal/migrations/0048_plural_management_frontnews.py delete mode 100644 backend/portal/migrations/0049_refactor_emailverifications.py delete mode 100644 backend/portal/migrations/0050_refactor_emailverifications_2.py delete mode 100644 backend/portal/migrations/0051_add_missing_ev_records.py delete mode 100644 backend/portal/migrations/0052_refactor_emailverifications_3.py delete mode 100644 backend/portal/migrations/0053_refactor_teacher_student_1.py delete mode 100644 backend/portal/migrations/0054_pending_join_request_can_be_blank.py delete mode 100644 backend/portal/migrations/0055_add_preview_user.py delete mode 100644 backend/portal/migrations/0056_remove_preview_user.py delete mode 100644 backend/portal/migrations/0057_delete_frontpagenews.py delete mode 100644 backend/portal/migrations/0058_move_to_common_models.py delete mode 100644 backend/portal/migrations/0059_move_email_verifications_to_common.py delete mode 100644 backend/portal/migrations/0060_delete_guardian.py delete mode 100644 backend/portal/migrations/0061_make_portaladmin_teacher.py delete mode 100644 backend/portal/migrations/0062_verify_portaladmin.py delete mode 100644 backend/portal/migrations/__init__.py diff --git a/backend/Pipfile b/backend/Pipfile index 1d1946c..4711fe7 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -6,6 +6,7 @@ name = "pypi" [packages] # codeforlife = {ref = "v0.1.12", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} cfl-common = "==6.33.1" # TODO: remove +codeforlife-portal = "*" # TODO: remove aimmo = ">=2" # TODO: remove rapid-router = ">=4" # TODO: remove phonenumbers = "==8.12.12" # TODO: remove diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 50f4961..5a82cf2 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "7ae6fedf5043a359b3ca03aa973280becc2ec820d0167be131ba42cc6428f611" + "sha256": "a91cf7cb4e37bda071cd9aff029fef9ce7e3949b5d477e8cba3c753b8dc76d77" }, "pipfile-spec": 6, "requires": { @@ -145,15 +145,45 @@ "markers": "python_full_version >= '3.7.0'", "version": "==3.2.0" }, + "codeforlife-portal": { + "hashes": [ + "sha256:358ded433a127d8a067e0b6517bcd084e3af794b78e8b73c28c10fe195d09155", + "sha256:a0f210e13990bd1182d6960135022c5f0f85ee79ee410e3b98700f3f83ac8b20" + ], + "index": "pypi", + "version": "==6.33.1" + }, + "defusedxml": { + "hashes": [ + "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", + "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==0.7.1" + }, + "diff-match-patch": { + "hashes": [ + "sha256:953019cdb9c9d2c9e47b5b12bcff3cf4746fc4598eb406076fa1fc27e6a1f15c", + "sha256:dce43505fb7b1b317de7195579388df0746d90db07015ed47a85e5e44930ef93" + ], + "markers": "python_version >= '3.7'", + "version": "==20230430" + }, "django": { "hashes": [ "sha256:031365bae96814da19c10706218c44dff3b654cc4de20a98bd2d29b9bde469f0", "sha256:21cc991466245d659ab79cb01204f9515690f8dae00e5eabde307f14d24d4d7d" ], "index": "pypi", - "markers": "python_version >= '3.6'", "version": "==3.2.19" }, + "django-classy-tags": { + "hashes": [ + "sha256:25eb4f95afee396148683bfb4811b83b3f5729218d73ad0a3399271a6f9fcc49", + "sha256:d59d98bdf96a764dcf7a2929a86439d023b283a9152492811c7e44fc47555bc9" + ], + "version": "==2.0.0" + }, "django-countries": { "hashes": [ "sha256:5a4ee958f77810bcc38ae96605e47d76a707e81f53cf2938743ef45faafd2fce", @@ -170,11 +200,19 @@ }, "django-formtools": { "hashes": [ - "sha256:21f8d5dac737f1e636fa8a0a10969c1c32f525a6dfa27c29592827ba70d9643a", - "sha256:49ea8a64ddef4728a558bf5f8f622c0f4053b979edcf193bf00dd80432ab2f12" + "sha256:304fa777b8ef9e0693ce7833f885cb89ba46b0e46fc23b01176900a93f46742f", + "sha256:c5272c03c1cd51b2375abf7397a199a3148a9fbbf2f100e186467a84025d13b2" + ], + "markers": "python_version >= '3.7'", + "version": "==2.2" + }, + "django-import-export": { + "hashes": [ + "sha256:1d3f2cb2ee3cca0386ed60651fa1623be989f130d9fbdf98a67f7dc3a94b8a37", + "sha256:38fd7b9439b9e3aa1a4747421c1087a5bc194e915a28d795fb8429a5f8028f2d" ], "markers": "python_version >= '3.7'", - "version": "==2.4.1" + "version": "==3.2.0" }, "django-js-reverse": { "hashes": [ @@ -185,11 +223,11 @@ }, "django-otp": { "hashes": [ - "sha256:007a6354dabb3a1a54574bf73abf045ebbde0bb8734a38e2ed7845ba450f345e", - "sha256:90765d5dac238a719f9550ac05681dd6307f513a81a10b6adb879b4abc6bc1a3" + "sha256:8ba5ab9bd2738c7321376c349d7cce49cf4404e79f6804e0a3cc462a91728e18", + "sha256:f523fb9dec420f28a29d3e2ad72ac06f64588956ed4f2b5b430d8e957ebb8287" ], "markers": "python_version >= '3.7'", - "version": "==1.2.2" + "version": "==1.0.2" }, "django-phonenumber-field": { "hashes": [ @@ -206,6 +244,39 @@ ], "version": "==2.0.8" }, + "django-preventconcurrentlogins": { + "hashes": [ + "sha256:9cb45fcd63edeec55e5ac29bbd2ee96974dc2a72d74ab88088dbf6a1f52978e9" + ], + "version": "==0.8.2" + }, + "django-ratelimit": { + "hashes": [ + "sha256:73223d860abd5c5d7b9a807fabb39a6220068129b514be8d78044b52607ab154", + "sha256:857e797f23de948b204a31dba9d88aea3ce731b7a5d926d0240c772e19b5486f" + ], + "markers": "python_version >= '3.4'", + "version": "==3.0.1" + }, + "django-recaptcha": { + "hashes": [ + "sha256:567784963fd5400feaf92e8951d8dbbbdb4b4c48a76e225d4baa63a2c9d2cd8c" + ], + "version": "==2.0.6" + }, + "django-sekizai": { + "hashes": [ + "sha256:5c5e16845d37ce822fc655ce79ec02715191b3d03330b550997bcb842cf24fdf", + "sha256:e829f09b0d6bf01ee5cde05de1fb3faf2fbc5df66dc4dc280fbaac224ca4336f" + ], + "version": "==2.0.0" + }, + "django-treebeard": { + "hashes": [ + "sha256:83aebc34a9f06de7daaec330d858d1c47887e81be3da77e3541fe7368196dd8a" + ], + "version": "==4.3.1" + }, "django-two-factor-auth": { "hashes": [ "sha256:3fac266d12472ac66475dd737bb18f2992484313bf56acf5a2eea5e824291ee6", @@ -219,7 +290,6 @@ "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa" ], "index": "pypi", - "markers": "python_version >= '3.6'", "version": "==3.13.1" }, "dnspython": { @@ -230,6 +300,14 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, + "et-xmlfile": { + "hashes": [ + "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c", + "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada" + ], + "markers": "python_version >= '3.6'", + "version": "==1.1.0" + }, "eventlet": { "hashes": [ "sha256:27ae41fad9deed9bbf4166f3e3b65acc15d524d42210a518e5877da85a6b8c5d", @@ -331,6 +409,14 @@ "markers": "python_version >= '3.5'", "version": "==3.4" }, + "importlib-metadata": { + "hashes": [ + "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116", + "sha256:dd0173e8f150d6815e098fd354f6414b0f079af4644ddfe90c71e2fc6174346d" + ], + "markers": "python_version >= '3.7'", + "version": "==4.13.0" + }, "kubernetes": { "hashes": [ "sha256:5854b0c508e8d217ca205591384ab58389abdae608576f9c9afc35a3c76a366c", @@ -350,6 +436,12 @@ "markers": "python_version >= '3.6'", "version": "==0.22.0" }, + "markuppy": { + "hashes": [ + "sha256:1adee2c0a542af378fe84548ff6f6b0168f3cb7f426b46961038a2bcfaad0d5f" + ], + "version": "==1.14" + }, "more-itertools": { "hashes": [ "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced", @@ -366,6 +458,20 @@ "markers": "python_version >= '3.6'", "version": "==3.2.2" }, + "odfpy": { + "hashes": [ + "sha256:db766a6e59c5103212f3cc92ec8dd50a0f3a02790233ed0b52148b70d3c438ec", + "sha256:fc3b8d1bc098eba4a0fda865a76d9d1e577c4ceec771426bcb169a82c5e9dfe0" + ], + "version": "==1.4.1" + }, + "openpyxl": { + "hashes": [ + "sha256:a6f5977418eff3b2d5500d54d9db50c8277a368436f4e4f8ddb1be3422870184", + "sha256:f91456ead12ab3c6c2e9491cf33ba6d08357d802192379bb482f1033ade496f5" + ], + "version": "==3.1.2" + }, "phonenumbers": { "hashes": [ "sha256:23944f9e628f32a975d3b221b6d76e6ba8ae618d53cb3d82fc23d9e100a59b29", @@ -374,6 +480,68 @@ "index": "pypi", "version": "==8.12.12" }, + "pillow": { + "hashes": [ + "sha256:00e65f5e822decd501e374b0650146063fbb30a7264b4d2744bdd7b913e0cab5", + "sha256:040586f7d37b34547153fa383f7f9aed68b738992380ac911447bb78f2abe530", + "sha256:0b6eb5502f45a60a3f411c63187db83a3d3107887ad0d036c13ce836f8a36f1d", + "sha256:1ce91b6ec08d866b14413d3f0bbdea7e24dfdc8e59f562bb77bc3fe60b6144ca", + "sha256:1f62406a884ae75fb2f818694469519fb685cc7eaff05d3451a9ebe55c646891", + "sha256:22c10cc517668d44b211717fd9775799ccec4124b9a7f7b3635fc5386e584992", + "sha256:3400aae60685b06bb96f99a21e1ada7bc7a413d5f49bce739828ecd9391bb8f7", + "sha256:349930d6e9c685c089284b013478d6f76e3a534e36ddfa912cde493f235372f3", + "sha256:368ab3dfb5f49e312231b6f27b8820c823652b7cd29cfbd34090565a015e99ba", + "sha256:38250a349b6b390ee6047a62c086d3817ac69022c127f8a5dc058c31ccef17f3", + "sha256:3a684105f7c32488f7153905a4e3015a3b6c7182e106fe3c37fbb5ef3e6994c3", + "sha256:3a82c40d706d9aa9734289740ce26460a11aeec2d9c79b7af87bb35f0073c12f", + "sha256:3b08d4cc24f471b2c8ca24ec060abf4bebc6b144cb89cba638c720546b1cf538", + "sha256:3ed64f9ca2f0a95411e88a4efbd7a29e5ce2cea36072c53dd9d26d9c76f753b3", + "sha256:3f07ea8d2f827d7d2a49ecf1639ec02d75ffd1b88dcc5b3a61bbb37a8759ad8d", + "sha256:520f2a520dc040512699f20fa1c363eed506e94248d71f85412b625026f6142c", + "sha256:5c6e3df6bdd396749bafd45314871b3d0af81ff935b2d188385e970052091017", + "sha256:608bfdee0d57cf297d32bcbb3c728dc1da0907519d1784962c5f0c68bb93e5a3", + "sha256:685ac03cc4ed5ebc15ad5c23bc555d68a87777586d970c2c3e216619a5476223", + "sha256:76de421f9c326da8f43d690110f0e79fe3ad1e54be811545d7d91898b4c8493e", + "sha256:76edb0a1fa2b4745fb0c99fb9fb98f8b180a1bbceb8be49b087e0b21867e77d3", + "sha256:7be600823e4c8631b74e4a0d38384c73f680e6105a7d3c6824fcf226c178c7e6", + "sha256:81ff539a12457809666fef6624684c008e00ff6bf455b4b89fd00a140eecd640", + "sha256:88af2003543cc40c80f6fca01411892ec52b11021b3dc22ec3bc9d5afd1c5334", + "sha256:8c11160913e3dd06c8ffdb5f233a4f254cb449f4dfc0f8f4549eda9e542c93d1", + "sha256:8f8182b523b2289f7c415f589118228d30ac8c355baa2f3194ced084dac2dbba", + "sha256:9211e7ad69d7c9401cfc0e23d49b69ca65ddd898976d660a2fa5904e3d7a9baa", + "sha256:92be919bbc9f7d09f7ae343c38f5bb21c973d2576c1d45600fce4b74bafa7ac0", + "sha256:9c82b5b3e043c7af0d95792d0d20ccf68f61a1fec6b3530e718b688422727396", + "sha256:9f7c16705f44e0504a3a2a14197c1f0b32a95731d251777dcb060aa83022cb2d", + "sha256:9fb218c8a12e51d7ead2a7c9e101a04982237d4855716af2e9499306728fb485", + "sha256:a74ba0c356aaa3bb8e3eb79606a87669e7ec6444be352870623025d75a14a2bf", + "sha256:b4f69b3700201b80bb82c3a97d5e9254084f6dd5fb5b16fc1a7b974260f89f43", + "sha256:bc2ec7c7b5d66b8ec9ce9f720dbb5fa4bace0f545acd34870eff4a369b44bf37", + "sha256:c189af0545965fa8d3b9613cfdb0cd37f9d71349e0f7750e1fd704648d475ed2", + "sha256:c1fbe7621c167ecaa38ad29643d77a9ce7311583761abf7836e1510c580bf3dd", + "sha256:c7cf14a27b0d6adfaebb3ae4153f1e516df54e47e42dcc073d7b3d76111a8d86", + "sha256:c9f72a021fbb792ce98306ffb0c348b3c9cb967dce0f12a49aa4c3d3fdefa967", + "sha256:cd25d2a9d2b36fcb318882481367956d2cf91329f6892fe5d385c346c0649629", + "sha256:ce543ed15570eedbb85df19b0a1a7314a9c8141a36ce089c0a894adbfccb4568", + "sha256:ce7b031a6fc11365970e6a5686d7ba8c63e4c1cf1ea143811acbb524295eabed", + "sha256:d35e3c8d9b1268cbf5d3670285feb3528f6680420eafe35cccc686b73c1e330f", + "sha256:d50b6aec14bc737742ca96e85d6d0a5f9bfbded018264b3b70ff9d8c33485551", + "sha256:d5d0dae4cfd56969d23d94dc8e89fb6a217be461c69090768227beb8ed28c0a3", + "sha256:d5db32e2a6ccbb3d34d87c87b432959e0db29755727afb37290e10f6e8e62614", + "sha256:d72e2ecc68a942e8cf9739619b7f408cc7b272b279b56b2c83c6123fcfa5cdff", + "sha256:d737a602fbd82afd892ca746392401b634e278cb65d55c4b7a8f48e9ef8d008d", + "sha256:d80cf684b541685fccdd84c485b31ce73fc5c9b5d7523bf1394ce134a60c6883", + "sha256:db24668940f82321e746773a4bc617bfac06ec831e5c88b643f91f122a785684", + "sha256:dbc02381779d412145331789b40cc7b11fdf449e5d94f6bc0b080db0a56ea3f0", + "sha256:dffe31a7f47b603318c609f378ebcd57f1554a3a6a8effbc59c3c69f804296de", + "sha256:edf4392b77bdc81f36e92d3a07a5cd072f90253197f4a52a55a8cec48a12483b", + "sha256:efe8c0681042536e0d06c11f48cebe759707c9e9abf880ee213541c5b46c5bf3", + "sha256:f31f9fdbfecb042d046f9d91270a0ba28368a723302786c0009ee9b9f1f60199", + "sha256:f88a0b92277de8e3ca715a0d79d68dc82807457dae3ab8699c758f07c20b3c51", + "sha256:faaf07ea35355b01a35cb442dd950d8f1bb5b040a7787791a535de13db15ed90" + ], + "markers": "python_version >= '3.8'", + "version": "==10.0.0" + }, "pyasn1": { "hashes": [ "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57", @@ -430,59 +598,38 @@ }, "pyyaml": { "hashes": [ - "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", - "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", - "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", - "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", - "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", - "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", - "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", - "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", - "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", - "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", - "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", - "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", - "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", - "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", - "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", - "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", - "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", - "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", - "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", - "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", - "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", - "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", - "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", - "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", - "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", - "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", - "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", - "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", - "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", - "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", - "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", - "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", - "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", - "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", - "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", - "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", - "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", - "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", - "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", - "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", - "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", - "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", - "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", - "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", - "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", - "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", - "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", - "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", - "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", - "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" + "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", + "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696", + "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393", + "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77", + "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922", + "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5", + "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8", + "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10", + "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc", + "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", + "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", + "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", + "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347", + "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", + "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541", + "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", + "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", + "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc", + "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", + "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa", + "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", + "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122", + "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", + "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", + "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", + "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc", + "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247", + "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6", + "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" ], - "markers": "python_version >= '3.6'", - "version": "==6.0.1" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==5.4.1" }, "qrcode": { "hashes": [ @@ -500,6 +647,57 @@ "index": "pypi", "version": "==5.11.0" }, + "reportlab": { + "hashes": [ + "sha256:0b94e4f65a5f77a631cc010c9a7892d69e33f3251b760639dcc76420e138ce95", + "sha256:11a71c314183532d889ad4b3941f61c3fe4bfdda769c768a7f02d93cb69dd1bb", + "sha256:149718c3eaee937f28094325f0dd9ae1add3172c2dacbb93ff5403f37c9d3c57", + "sha256:21d6b6bcdecee9c7ce047156d0553a30d736b8172629e4c0fcacab35ba261f3b", + "sha256:269c59e508df08be498ab9e5278addb2cc16989677a03f800b17f8a31f8c5cc7", + "sha256:36568d3cb4101a210c4d821d9101635c2ef6e06bd649335938c01eb197f50c5d", + "sha256:3cb0da4975dbade6cc2ea6b0b0b17578af266dc3f669e959648f3306af993369", + "sha256:48eadd93237c7e2739525c74cf6615dd6c1a767c839f4b0d7c12167dc0b09911", + "sha256:57add04824bca89a130f9d428ace1b003cce4061386e0ec2a1b45b554ffe7aa3", + "sha256:58ea3471b9b4b8e7952bd357e8487789da11213470be328ffb3e5b7d7690c2c7", + "sha256:5a460f4c0c30bdf9d7bef46a816671a4386a9253670a53d35c694c666544261f", + "sha256:6172481e8acffcf72042653e977281fbd807a41705a39456d92d2606d8b8c5e2", + "sha256:65b441e22d8fe93154567a30662d8539e639b78142815afcaf92b388846eb3c1", + "sha256:6ea46fef07c588fef84d1164d4788fef322b39feb2bfb2df0a0706181dff79b8", + "sha256:6f75d33f7a3720cf47371ab63ced0f0ebd1aeb6db19386ae92f8977a09be9611", + "sha256:6fdac930dfdc6227720545ec44fdb396e92d53ec227a6f5ae58cc8cb9a6cbe89", + "sha256:701290747662d2b3be49fc0de33898ecc9ce3fafe0e2887d406e24693465e5ae", + "sha256:753485bb2b18cbd11340e227e4aaf9bde3bb64f83406dfa011e92ad0231a42c9", + "sha256:7b690bc30f58931b0abd47635d93a43a82d67972e83a6511cc8adbcd7da25310", + "sha256:7efdf68e97e8fea8683bfc17f25747fefbda729b9018bc2e3221658ac41ee0bd", + "sha256:7ff89011b5ee30209b3106641e3b7b4959f10aa6e9d6f3030205123c178f605d", + "sha256:8260c002e4845a5af65908d5ee2099bcc25a16c7646c5c417fa27f1e4b844bc1", + "sha256:8e4983486d419daa45cade40874bb869976e27ba11f77fb4b9ae32417284ade7", + "sha256:8f00175f8e12e6f7d3a01309de6d7008fac94a2cdce6837ad066f0961472c9e5", + "sha256:9f869286fcefa7f8e89e38448309891ff110ad74f58a7317ec204f3d4b8ad5f5", + "sha256:a0330322c6c8123745ac7667fcc6ae3e0de3b73c15bdfaa28c788a9eaa0f50da", + "sha256:a043cff1781ddb2a0ba0e8e760a79fc5be2430957c4f2a1f51bd4528cc53178f", + "sha256:a477f652e6c417ad40387a8498d9ad827421006f156aab16f67adc9b81699a72", + "sha256:a4dbc28fede7f504b9ac65ce9cbea35585e999d63f9fa68bc73f5a75b4929302", + "sha256:afb418409e0d323c6cb5e3be7ea4d14dfbf8a07eb03ab0b0062904cacf819878", + "sha256:b0d91663d450c11404ec189ebc5a4abdf20f7c4eca5954a920427cdbf5601525", + "sha256:ba6f533b262f4ee1636b754992bb2fb349df0500d765ac9be014a375c047f4db", + "sha256:bbdbba1ec3498b17eefca14d424ee90bb95b53e1423ecb22f1c17733c3406559", + "sha256:ca8eb7a6607f8a664187a330bab9f8d11c9f81ed885e063dfbb29a130944a72a", + "sha256:cca2d4c783f985b91b98e80d09ac79b6ed3f317a729cba5ba86edfe5eb9a2d9c", + "sha256:d59e62faa03003be81aa14d37ac34ea110e5ac59c8678fd4c0daa7d8b8f42096", + "sha256:d95fc8bc177a009053548c6d851a513b2147c465a5e8fea82287ea22d6825c4e", + "sha256:dbddadca6f08212732e83a60e30a42cfc7d2695892cedea208b3c3e7131c9993", + "sha256:e13a4e81761636591f5b60104f6e1eec70832ffd9aa781db68d7ebb576970d4b", + "sha256:e28a8d9cf462e2b4c9e71abd0630f9ec245d88b976b283b0dbb4602c9ddb3938", + "sha256:e5949f3b4e207fa7901c0cc3b49470b2a3372617a47dfbc892db31c2b56af296", + "sha256:e98965c6e60d76ff63989d9400ae8e65efd67c665d785b377f438f166a57c053", + "sha256:f1993a68c0edc45895d3df350d01b0456efe79aaf309cef777762742be501f2a", + "sha256:faeebde62f0f6ad86985bec5685411260393d2eb7ba907972da56af586b644e8", + "sha256:ff09a0a1e5cef05309ac09dfc5185e8151d927bcf45470d2f540c96260f8a355" + ], + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==3.6.13" + }, "requests": { "hashes": [ "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", @@ -526,11 +724,11 @@ }, "setuptools": { "hashes": [ - "sha256:56ee14884fd8d0cd015411f4a13f40b4356775a0aefd9ebc1d3bfb9a1acb32f1", - "sha256:eff96148eb336377ab11beee0c73ed84f1709a40c0b870298b0d058828761bae" + "sha256:26ead7d1f93efc0f8c804d9fafafbe4a44b179580a7105754b245155f9af05a8", + "sha256:47c7b0c0f8fc10eec4cf1e71c6fdadf8decaa74ffa087e68cd1c20db7ad6a592" ], - "markers": "python_version >= '3.8'", - "version": "==68.2.1" + "markers": "python_version >= '3.7'", + "version": "==62.1.0" }, "six": { "hashes": [ @@ -555,6 +753,21 @@ "markers": "python_version >= '3.5'", "version": "==0.4.4" }, + "tablib": { + "extras": [ + "html", + "ods", + "xls", + "xlsx", + "yaml" + ], + "hashes": [ + "sha256:9821caa9eca6062ff7299fa645e737aecff982e6b2b42046928a6413c8dabfd9", + "sha256:f6661dfc45e1d4f51fa8a6239f9c8349380859a5bfaa73280645f046d6c96e33" + ], + "markers": "python_version >= '3.8'", + "version": "==3.5.0" + }, "typing-extensions": { "hashes": [ "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", @@ -578,6 +791,28 @@ ], "markers": "python_version >= '3.8'", "version": "==1.6.3" + }, + "xlrd": { + "hashes": [ + "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd", + "sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88" + ], + "version": "==2.0.1" + }, + "xlwt": { + "hashes": [ + "sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e", + "sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88" + ], + "version": "==1.3.0" + }, + "zipp": { + "hashes": [ + "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0", + "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147" + ], + "markers": "python_version >= '3.8'", + "version": "==3.16.2" } }, "develop": { @@ -607,7 +842,6 @@ "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301" ], "index": "pypi", - "markers": "python_version >= '3.8'", "version": "==23.9.1" }, "click": { @@ -680,7 +914,6 @@ "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069" ], "index": "pypi", - "markers": "python_version >= '3.7'", "version": "==7.4.2" }, "pytest-django": { @@ -689,7 +922,6 @@ "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2" ], "index": "pypi", - "markers": "python_version >= '3.5'", "version": "==4.5.2" }, "pytest-env": { @@ -698,7 +930,6 @@ "sha256:baed9b3b6bae77bd75b9238e0ed1ee6903a42806ae9d6aeffb8754cd5584d4ff" ], "index": "pypi", - "markers": "python_version >= '3.7'", "version": "==0.8.2" }, "tomli": { diff --git a/backend/portal/__init__.py b/backend/portal/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/portal/apps.py b/backend/portal/apps.py deleted file mode 100644 index d912bf9..0000000 --- a/backend/portal/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class PortalConfig(AppConfig): - default_auto_field = "django.db.models.BigAutoField" - name = "portal" diff --git a/backend/portal/migrations/0001_squashed_0041_new_news.py b/backend/portal/migrations/0001_squashed_0041_new_news.py deleted file mode 100644 index a6fa448..0000000 --- a/backend/portal/migrations/0001_squashed_0041_new_news.py +++ /dev/null @@ -1,736 +0,0 @@ -from __future__ import unicode_literals - -from django.db import models, migrations -from django.conf import settings - -from django.contrib.auth.hashers import make_password -from django.utils import timezone -import os - -# Functions from the following migrations need manual copying. -# Move them and any dependencies into this file, then update the -# RunPython operations to refer to the local versions: -# portal.migrations.0001_squashed_0036_data_viewing_user_fix -# portal.migrations.0037_userprofile_developer -# portal.migrations.0041_new_news -# portal.migrations.0040_initial_news - - -def insert_admin_user(apps, schema_editor): - User = apps.get_model("auth", "User") - admin = User.objects.create() - admin.username = "portaladmin" - admin.email = ("codeforlife-portal@ocado.com",) - admin.is_superuser = True - admin.is_staff = True - admin.password = make_password(os.getenv("ADMIN_PASSWORD", "abc123")) - admin.save() - - -def insert_users(apps, schema_editor): - User = apps.get_model("auth", "User") - UserProfile = apps.get_model("portal", "UserProfile") - School = apps.get_model("portal", "School") - Teacher = apps.get_model("portal", "Teacher") - Class = apps.get_model("portal", "Class") - Student = apps.get_model("portal", "Student") - - # Create school - school = School.objects.create( - name="Swiss Federal Polytechnic", - postcode="AL10 9NE", - town="Welwyn Hatfield", - latitude="51.76183", - longitude="-0.244361", - ) - - # Create teachers' users - teacher_user = User.objects.create( - username="test teacher", - first_name="Albert", - last_name="Einstein", - email="alberteinstein@codeforlife.com", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - teacher2_user = User.objects.create( - username="test teacher2", - first_name="Max", - last_name="Planck", - email="maxplanck@codeforlife.com", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - teacher3_user = User.objects.create( - username="media ram", - first_name="Ram", - last_name="Leith", - email="ramleith@codeforlife.com", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - # Create students' users - student1_user = User.objects.create( - username="test student1", - first_name="Leonardo", - last_name="DaVinci", - email="leonardodavinci@codeforlife.com", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - student2_user = User.objects.create( - username="test student2", - first_name="Galileo", - last_name="Galilei", - email="galileogalilei@codeforlife.com", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - student3_user = User.objects.create( - username="Issac", - first_name="Isaac", - last_name="Newton", - email="isaacnewton@codeforlife.com", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - student4_user = User.objects.create( - username="test student4", - first_name="Richard", - last_name="Feynman", - email="richardfeynman@codeforlife.com", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - student5_user = User.objects.create( - username="test student5", - first_name="Alexander", - last_name="Flemming", - email="alexanderflemming@codeforlife.com", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - student6_user = User.objects.create( - username="test student6", - first_name="Daniel", - last_name="Bernoulli", - email="danielbernoulli@codeforlife.com", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - indy_user = User.objects.create( - username="indy", - first_name="Indiana", - last_name="Jones", - email="indianajones@codeforlife.com", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - student7_user = User.objects.create( - username="media noah", - first_name="Noah", - last_name="Monaghan", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - student8_user = User.objects.create( - username="media elliot", - first_name="Elliot", - last_name="Sharp", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - student9_user = User.objects.create( - username="media tajmae", - first_name="Tajmae", - last_name="Joseph", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - student10_user = User.objects.create( - username="media carlton", - first_name="Carlton", - last_name="Joseph", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - student11_user = User.objects.create( - username="media nadal", - first_name="Nadal", - last_name="Spencer-Jennings", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - student12_user = User.objects.create( - username="media freddie", - first_name="Freddie", - last_name="Goff", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - student13_user = User.objects.create( - username="media leon", - first_name="Leon", - last_name="Scott", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - student14_user = User.objects.create( - username="media betty", - first_name="Betty", - last_name="Kessell", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - # Create UserProfiles - teacher_userprofile = UserProfile.objects.create(user=teacher_user, developer=True) - teacher2_userprofile = UserProfile.objects.create(user=teacher2_user) - teacher3_userprofile = UserProfile.objects.create(user=teacher3_user) - student1_userprofile = UserProfile.objects.create( - user=student1_user, developer=True - ) - student2_userprofile = UserProfile.objects.create(user=student2_user) - student3_userprofile = UserProfile.objects.create(user=student3_user) - student4_userprofile = UserProfile.objects.create(user=student4_user) - student5_userprofile = UserProfile.objects.create(user=student5_user) - student6_userprofile = UserProfile.objects.create(user=student6_user) - indy_userprofile = UserProfile.objects.create(user=indy_user) - student7_userprofile = UserProfile.objects.create(user=student7_user) - student8_userprofile = UserProfile.objects.create(user=student8_user) - student9_userprofile = UserProfile.objects.create(user=student9_user) - student10_userprofile = UserProfile.objects.create(user=student10_user) - student11_userprofile = UserProfile.objects.create(user=student11_user) - student12_userprofile = UserProfile.objects.create(user=student12_user) - student13_userprofile = UserProfile.objects.create(user=student13_user) - student14_userprofile = UserProfile.objects.create(user=student14_user) - - # Create teachers - teacher = Teacher.objects.create( - title="Mr", - user=teacher_userprofile, - school=school, - is_admin=True, - pending_join_request=None, - ) - teacher2 = Teacher.objects.create( - title="Mr", - user=teacher2_userprofile, - school=school, - is_admin=False, - pending_join_request=None, - ) - teacher3 = Teacher.objects.create( - title="Mrs", - user=teacher3_userprofile, - school=school, - is_admin=True, - pending_join_request=None, - ) - - # Create classes - klass = Class.objects.create( - name="Class 101", - teacher=teacher, - access_code="AB123", - classmates_data_viewable=True, - always_accept_requests=True, - ) - class2 = Class.objects.create( - name="Class 102", - teacher=teacher2, - access_code="AB124", - classmates_data_viewable=True, - always_accept_requests=True, - ) - - class3 = Class.objects.create( - name="Class 103", - teacher=teacher2, - access_code="AB125", - classmates_data_viewable=True, - always_accept_requests=True, - ) - - class4 = Class.objects.create( - name="Young Coders 101", - teacher=teacher3, - access_code="RL123", - classmates_data_viewable=True, - always_accept_requests=True, - ) - - # Create students - student1 = Student.objects.create( - class_field=klass, user=student1_userprofile, pending_class_request=None - ) - - student2 = Student.objects.create( - class_field=klass, user=student2_userprofile, pending_class_request=None - ) - - student3 = Student.objects.create( - class_field=None, user=student3_userprofile, pending_class_request=None - ) - - student4 = Student.objects.create( - class_field=class2, user=student4_userprofile, pending_class_request=None - ) - - student5 = Student.objects.create( - class_field=class2, user=student5_userprofile, pending_class_request=None - ) - - student6 = Student.objects.create( - class_field=class3, user=student6_userprofile, pending_class_request=None - ) - - indy_student = Student.objects.create( - user=indy_userprofile, pending_class_request=None - ) - - student7 = Student.objects.create( - class_field=class4, user=student7_userprofile, pending_class_request=None - ) - - student8 = Student.objects.create( - class_field=class4, user=student8_userprofile, pending_class_request=None - ) - - student9 = Student.objects.create( - class_field=class4, user=student9_userprofile, pending_class_request=None - ) - - student10 = Student.objects.create( - class_field=class4, user=student10_userprofile, pending_class_request=None - ) - - student11 = Student.objects.create( - class_field=class4, user=student11_userprofile, pending_class_request=None - ) - - student12 = Student.objects.create( - class_field=class4, user=student12_userprofile, pending_class_request=None - ) - - student13 = Student.objects.create( - class_field=class4, user=student13_userprofile, pending_class_request=None - ) - - student14 = Student.objects.create( - class_field=class4, user=student14_userprofile, pending_class_request=None - ) - - user = User.objects.create( - username="DATA_AGGREGATE", - first_name="DATA", - last_name="AGGREGATE", - email="aggregator@codeforlife.com", - password=make_password(os.getenv("DATA_AGGREGATE_PASSWORD", "Password1")), - ) - - user_profile = UserProfile.objects.create(user=user, can_view_aggregated_data=True) - - -def insert_news(apps, schema_editor): - FrontPageNews = apps.get_model("portal", "FrontPageNews") - - news1 = FrontPageNews.objects.create( - title="Teachers 'not confident on coding'", - text="Computer coding is being introduced to the school curriculum in less than six weeks' time, but many primary school teachers will be under-prepared to teach the new subject, according to a new poll.", - link="http://www.dailymail.co.uk/wires/pa/article-2700124/TEACHERS-NOT-CONFIDENT-ON-CODING.html?ITO=1490&ns_mchannel=rss&ns_campaign=1490", - link_text="Read more on www.dailymail.co.uk", - added_dstamp=timezone.now(), - ) - news2 = FrontPageNews.objects.create( - title="British schools are not prepared to teach coding", - text="With just six weeks until the new computing curriculum is introduced in UK schools, research has revealed that British primary school teachers are not fully prepared to teach their pupils how to code.", - link="http://www.cbronline.com/news/social/british-teachers-are-not-prepared-to-teach-coding-4322259", - link_text="Read more on www.cbronline.co.uk", - added_dstamp=timezone.now(), - ) - news3 = FrontPageNews.objects.create( - title="Ocado launches free Code For Life tool to help 130,000 apprehensive primary school teachers", - text="Thousands of primary school teachers aren’t ready to teach computer coding lessons that become part of the country’s national curriculum for computing before the start of the academic year in September.", - link="http://www.itproportal.com/2014/07/21/ocado-launches-free-code-life-tool-help-130000-apprehensive-primary-school-teachers/?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+itproportal%2Frss+(Latest+ITProPortal+News)", - link_text="Read more on itproportal.com", - added_dstamp=timezone.now(), - ) - news4 = FrontPageNews.objects.create( - title="Ocado Technology launches coding resources for teachers", - text="Ocado Technology is the latest business to get involved with preparing teachers for the new computing curriculum that comes into effect in September", - link="http://www.computerworlduk.com/news/careers/3531070/ocado-technology-launches-coding-resources-for-teachers/", - link_text="Read more on www.computerworlduk.com", - added_dstamp=timezone.now(), - ) - news5 = FrontPageNews.objects.create( - title="Not Enough Teachers Will Know Code By September", - text="A new curriculum brings coding into primary schools in September – and it looks as if teachers won’t be able to cope. ", - link="http://www.techweekeurope.co.uk/news/not-enough-teachers-know-code-149462", - link_text="Read more on www.techweekeurope.co.uk", - added_dstamp=timezone.now(), - ) - news6 = FrontPageNews.objects.create( - title="Ocado Technology readies primary school teachers with code initiative", - text="Ocado Technology has launched a coding initiative after finding that 73% of primary school teachers feel they have not been given the necessary resources to teach children to code.", - link="http://www.computerweekly.com/news/2240225117/Ocado-Technology-readies-primary-school-teachers-with-code-initiative", - link_text="Read more on www.computerweekly.com", - added_dstamp=timezone.now(), - ) - news7 = FrontPageNews.objects.create( - title="Curriculum countdown", - text="According to new research more than 130,000 primary school teachers don’t feel confident enough to teach computer coding", - link="http://edtechnology.co.uk/Featured-Content/curriculum_countdown", - link_text="Read more on www.edtechnology.co.uk", - added_dstamp=timezone.now(), - ) - news8 = FrontPageNews.objects.create( - title="Computing Curriculum Countdown: Over 130,000 Primary School Teachers Don’t Feel Confident Enough to Teach Computer Coding", - text="Many primary school teachers feel they haven’t been given the necessary resources to teach the new Computing curriculum from September. ", - link="http://www.primarytimes.net/news/2014/07/computing-curriculum-countdown-over-130-000-primary-school-teachers-don-t-feel-confident-enough-to-teach-computer-coding-", - link_text="Read more on www.primarytimes.net", - added_dstamp=timezone.now(), - ) - news9 = FrontPageNews.objects.create( - title="Ocado’s technology team to release primary school coding tool", - text="Ocado’s technology team is launching Code for Life, the online grocer’s initiative to help children learn to code.", - link="http://internetretailing.net/2014/07/ocados-technology-team-launch-primary-school-coding-tool/", - link_text="Read more on www.internetretailing.net", - added_dstamp=timezone.now(), - ) - news10 = FrontPageNews.objects.create( - title="Teachers 'not confident' over coding", - text="Computer coding is being introduced to the school curriculum in less than six weeks' time, but many primary school teachers will be under-prepared to teach the new subject, according to a new poll.", - link="http://www.dailyecho.co.uk/leisure/technology/11358091.Teachers__not_confident__over_coding/", - link_text="Read more on www.dailyecho.co.uk", - added_dstamp=timezone.now(), - ) - news11 = FrontPageNews.objects.create( - title="Ocado Technology launches coding resources for teachers", - text="Ocado Technology is a latest business to get concerned with scheming teachers for a new computing curriculum that comes into outcome in September.", - link="http://www.datacentremanagement.org/2014/07/ocado-technology-launches-coding-resources-for-teachers/", - link_text="Read more on www.datacentremanagement.org", - added_dstamp=timezone.now(), - ) - news12 = FrontPageNews.objects.create( - title="Teachers 'not confident enough' to teach code", - text="More than 70% of primary school teachers say they don't feel confident enough to teach the new coding syllabus being introduced this September.", - link="http://www.newelectronics.co.uk/electronics-news/teachers-not-confident-enough-to-teach-code/62697/", - link_text="Read more on www.newelectronics.co.uk", - added_dstamp=timezone.now(), - ) - - -def insert_news2(apps, schema_editor): - FrontPageNews = apps.get_model("portal", "FrontPageNews") - - news1 = FrontPageNews.objects.create( - title="Bletchley Park: From Code-Breaking to Kids Coding", - text="The National Museum of Computer has opened its free Weekend Codability Project. This is part of the Code for Life initiative which aims to inspire the next generation of computer scientists.", - link="http://www.idgconnect.com/blog-abstract/9056/bletchley-park-from-code-breaking-kids-coding", - link_text="Read more on idgconnect.com", - added_dstamp=timezone.now(), - ) - news2 = FrontPageNews.objects.create( - title="Giving kids Codability", - text="Weekend Codability aims to empower young people by introducing them to programming computers. Children will be taught how to give instructions to computers, change existing instructions in programs and create their own programs.", - link="http://edtechnology.co.uk/News/giving_kids_codability", - link_text="Read more on edtechnology.co.uk", - added_dstamp=timezone.now(), - ) - news3 = FrontPageNews.objects.create( - title="Try your hand at Bletchley Park codability project", - text="Following the introduction of computing to England’s school curriculum last month, young people across the country are being invited to try their hand at programming computers in Block H, the world's first purpose-built computer centre, on Bletchley Park.", - link="http://www.mkweb.co.uk/COMPUTERS-Try-hand-Bletchley-Park-codability/story-23860128-detail/story.html", - link_text="Read more on mkweb.co.uk", - added_dstamp=timezone.now(), - ) - - -class Migration(migrations.Migration): - - replaces = [ - ("portal", "0001_initial"), - ("portal", "0002_admin_user"), - ("portal", "0003_school_teacher_relationship"), - ("portal", "0004_class_access_code"), - ("portal", "0005_student_pin"), - ("portal", "0006_teacher_email_verification"), - ("portal", "0007_school_admin"), - ("portal", "0008_auto_20140729_0938"), - ("portal", "0009_remove_student_pin"), - ("portal", "0010_auto_20140729_1030"), - ("portal", "0009_teacher_pending_join_request"), - ("portal", "0011_merge"), - ("portal", "0012_auto_20140729_1359"), - ("portal", "0013_auto_20140730_1100"), - ("portal", "0014_auto_20140730_1459"), - ("portal", "0015_student_pending_class_request"), - ("portal", "0016_emailverification_email"), - ("portal", "0017_school_postcode"), - ("portal", "0018_auto_20140804_1246"), - ("portal", "0019_auto_20140804_1713"), - ("portal", "0020_auto_20140806_1632"), - ("portal", "0020_auto_20140806_1205"), - ("portal", "0021_merge"), - ("portal", "0022_remove_student_name"), - ("portal", "0023_class_classmates_data_viewable"), - ("portal", "0024_auto_20140813_1536"), - ("portal", "0025_trial_users"), - ("portal", "0026_fix_trial_users"), - ("portal", "0027_more_trial_users"), - ("portal", "0028_demoting_max_planck"), - ("portal", "0029_trial_independent_student"), - ("portal", "0030_media_users"), - ("portal", "0031_auto_20140903_1450"), - ("portal", "0032_userprofile_can_view_aggregated_data"), - ("portal", "0033_data_viewing_user"), - ("portal", "0034_data_viewing_user_fix"), - ("portal", "0035_data_viewing_user_hack"), - ("portal", "0036_data_viewing_user_fix"), - ("portal", "0037_userprofile_developer"), - ("portal", "0038_frontpagenews"), - ("portal", "0039_auto_20141109_1827"), - ("portal", "0040_initial_news"), - ("portal", "0041_new_news"), - ] - - dependencies = [ - ("auth", "0001_initial"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="UserProfile", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ( - "avatar", - models.ImageField( - default="static/portal/img/avatars/default-avatar.jpeg", - null=True, - upload_to="static/portal/img/avatars/", - blank=True, - ), - ), - ( - "user", - models.OneToOneField( - to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE - ), - ), - ("awaiting_email_verification", models.BooleanField(default=False)), - ("can_view_aggregated_data", models.BooleanField(default=False)), - ("developer", models.BooleanField(default=False)), - ], - ), - migrations.CreateModel( - name="School", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("name", models.CharField(max_length=200)), - ("postcode", models.CharField(max_length=10)), - ("latitude", models.CharField(max_length=20)), - ("longitude", models.CharField(max_length=20)), - ("town", models.CharField(max_length=200)), - ], - ), - migrations.CreateModel( - name="Teacher", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ( - "user", - models.OneToOneField( - to="portal.UserProfile", on_delete=models.CASCADE - ), - ), - ( - "pending_join_request", - models.ForeignKey( - related_name="join_request", - to="portal.School", - null=True, - on_delete=models.SET_NULL, - ), - ), - ("is_admin", models.BooleanField(default=False)), - ("title", models.CharField(max_length=35)), - ( - "school", - models.ForeignKey( - related_name="teacher_school", - to="portal.School", - null=True, - on_delete=models.SET_NULL, - ), - ), - ], - ), - migrations.CreateModel( - name="Class", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("name", models.CharField(max_length=200)), - ("access_code", models.CharField(max_length=5)), - ("classmates_data_viewable", models.BooleanField(default=False)), - ("accept_requests_until", models.DateTimeField(null=True)), - ("always_accept_requests", models.BooleanField(default=False)), - ( - "teacher", - models.ForeignKey( - related_name="class_teacher", - to="portal.Teacher", - on_delete=models.CASCADE, - ), - ), - ], - options={"verbose_name_plural": "classes"}, - ), - migrations.CreateModel( - name="Student", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ( - "class_field", - models.ForeignKey( - related_name="students", - to="portal.Class", - null=True, - on_delete=models.CASCADE, - ), - ), - ( - "user", - models.OneToOneField( - to="portal.UserProfile", on_delete=models.CASCADE - ), - ), - ( - "pending_class_request", - models.ForeignKey( - related_name="class_request", - to="portal.Class", - null=True, - on_delete=models.CASCADE, - ), - ), - ], - ), - migrations.CreateModel( - name="Guardian", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("name", models.CharField(max_length=200)), - ("children", models.ManyToManyField(to="portal.Student")), - ( - "user", - models.OneToOneField( - to="portal.UserProfile", on_delete=models.CASCADE - ), - ), - ], - ), - migrations.CreateModel( - name="EmailVerification", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("token", models.CharField(max_length=30)), - ("expiry", models.DateTimeField()), - ("used", models.BooleanField(default=False)), - ( - "user", - models.ForeignKey( - related_name="email_verifications", - to="portal.UserProfile", - on_delete=models.CASCADE, - ), - ), - ( - "email", - models.CharField( - default=None, max_length=200, null=True, blank=True - ), - ), - ], - ), - migrations.CreateModel( - name="FrontPageNews", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("title", models.CharField(max_length=200)), - ("text", models.CharField(max_length=1000)), - ("link", models.CharField(max_length=500)), - ("link_text", models.CharField(max_length=200)), - ("added_dstamp", models.DateTimeField()), - ], - ), - migrations.RunPython(code=insert_admin_user), - migrations.RunPython(code=insert_users), - migrations.RunPython(code=insert_news), - migrations.RunPython(code=insert_news2), - ] diff --git a/backend/portal/migrations/0042_school_country.py b/backend/portal/migrations/0042_school_country.py deleted file mode 100644 index 34d11d4..0000000 --- a/backend/portal/migrations/0042_school_country.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [("portal", "0001_squashed_0041_new_news")] - - operations = [ - migrations.AddField( - model_name="school", - name="country", - field=models.CharField(max_length=200, null=True, blank=True), - preserve_default=True, - ) - ] diff --git a/backend/portal/migrations/0043_auto_20150430_0952.py b/backend/portal/migrations/0043_auto_20150430_0952.py deleted file mode 100644 index d22dc3c..0000000 --- a/backend/portal/migrations/0043_auto_20150430_0952.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import unicode_literals - -from django.db import models, migrations -import django_countries.fields - - -class Migration(migrations.Migration): - - dependencies = [("portal", "0042_school_country")] - - operations = [ - migrations.AlterField( - model_name="school", - name="country", - field=django_countries.fields.CountryField( - max_length=2, null=True, blank=True - ), - ) - ] diff --git a/backend/portal/migrations/0044_auto_20150430_0959.py b/backend/portal/migrations/0044_auto_20150430_0959.py deleted file mode 100644 index 39a26aa..0000000 --- a/backend/portal/migrations/0044_auto_20150430_0959.py +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import unicode_literals - -from builtins import str -from django.db import models, migrations - - -class Migration(migrations.Migration): - - # It will guess the country of the school using postcode, UK will be set if no result is returned by the API - def populate_country(apps, schema_editor): - - School = apps.get_model("portal", "School") - for school in School.objects.all(): - country, town, lat, lng = ( - "GB", - "0", - "0", - "0", - ) - school.country = str(country) - school.town = town - school.lat = lat - school.lng = lng - school.save() - - def reset_country(apps, schema_editor): - School = apps.get_model("portal", "School") - for school in School.objects.all(): - school.country = "" - school.town = "0" - school.lat = "0" - school.lng = "0" - school.save() - - dependencies = [("portal", "0043_auto_20150430_0952")] - - operations = [migrations.RunPython(populate_country, reset_country)] diff --git a/backend/portal/migrations/0045_auto_20150430_1446.py b/backend/portal/migrations/0045_auto_20150430_1446.py deleted file mode 100644 index 9787d5d..0000000 --- a/backend/portal/migrations/0045_auto_20150430_1446.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import unicode_literals - -from django.db import models, migrations -import django_countries.fields - - -class Migration(migrations.Migration): - - dependencies = [("portal", "0044_auto_20150430_0959")] - - operations = [ - migrations.AlterField( - model_name="school", - name="country", - field=django_countries.fields.CountryField(max_length=2), - ) - ] diff --git a/backend/portal/migrations/0046_auto_20150723_1101.py b/backend/portal/migrations/0046_auto_20150723_1101.py deleted file mode 100644 index 2fee5b9..0000000 --- a/backend/portal/migrations/0046_auto_20150723_1101.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [("portal", "0045_auto_20150430_1446")] - - operations = [ - migrations.AlterModelOptions( - name="school", - options={ - "permissions": ( - ("view_aggregated_data", "Can see available aggregated data"), - ("view_map_data", "Can see schools' location displayed on map"), - ) - }, - ) - ] diff --git a/backend/portal/migrations/0047_remove_userprofile_avatar.py b/backend/portal/migrations/0047_remove_userprofile_avatar.py deleted file mode 100644 index f150268..0000000 --- a/backend/portal/migrations/0047_remove_userprofile_avatar.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [("portal", "0046_auto_20150723_1101")] - - operations = [migrations.RemoveField(model_name="userprofile", name="avatar")] diff --git a/backend/portal/migrations/0048_plural_management_frontnews.py b/backend/portal/migrations/0048_plural_management_frontnews.py deleted file mode 100644 index 23e3e7b..0000000 --- a/backend/portal/migrations/0048_plural_management_frontnews.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [("portal", "0047_remove_userprofile_avatar")] - - operations = [ - migrations.AlterModelOptions( - name="frontpagenews", options={"verbose_name_plural": "front page news"} - ) - ] diff --git a/backend/portal/migrations/0049_refactor_emailverifications.py b/backend/portal/migrations/0049_refactor_emailverifications.py deleted file mode 100644 index 7ce004a..0000000 --- a/backend/portal/migrations/0049_refactor_emailverifications.py +++ /dev/null @@ -1,32 +0,0 @@ -from __future__ import unicode_literals - -from django.db import models, migrations -from django.conf import settings - - -class Migration(migrations.Migration): - - dependencies = [("portal", "0048_plural_management_frontnews")] - - operations = [ - migrations.AddField( - model_name="emailverification", - name="new_user", - field=models.ForeignKey( - to=settings.AUTH_USER_MODEL, - related_name="email_verifications_old", - null=True, - blank=True, - on_delete=models.CASCADE, - ), - ), - migrations.AddField( - model_name="emailverification", - name="verified", - field=models.BooleanField(default=False), - ), - migrations.RunSQL( - "UPDATE portal_emailverification SET new_user_id = ( SELECT portal_userprofile.user_id FROM portal_userprofile WHERE portal_userprofile.id = portal_emailverification.user_id);" - ), - migrations.RunSQL("UPDATE portal_emailverification SET verified = used;"), - ] diff --git a/backend/portal/migrations/0050_refactor_emailverifications_2.py b/backend/portal/migrations/0050_refactor_emailverifications_2.py deleted file mode 100644 index 7dfa68d..0000000 --- a/backend/portal/migrations/0050_refactor_emailverifications_2.py +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import unicode_literals - -from django.db import models, migrations -from django.conf import settings - - -class Migration(migrations.Migration): - dependencies = [("portal", "0049_refactor_emailverifications")] - - operations = [ - migrations.RemoveField(model_name="emailverification", name="user"), - migrations.RemoveField(model_name="emailverification", name="used"), - migrations.AddField( - model_name="emailverification", - name="user", - field=models.ForeignKey( - to=settings.AUTH_USER_MODEL, - related_name="email_verifications", - null=True, - blank=True, - on_delete=models.CASCADE, - ), - ), - migrations.RunSQL( - ("UPDATE portal_emailverification" " SET user_id = new_user_id;") - ), - ] diff --git a/backend/portal/migrations/0051_add_missing_ev_records.py b/backend/portal/migrations/0051_add_missing_ev_records.py deleted file mode 100644 index ebeb8c1..0000000 --- a/backend/portal/migrations/0051_add_missing_ev_records.py +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import unicode_literals - -from uuid import uuid4 -from datetime import timedelta - -from django.db import migrations -from django.utils import timezone - - -def add_missing_ev_records(apps, schema_editor): - User = apps.get_model("auth", "User") - EmailVerification = apps.get_model("portal", "EmailVerification") - verified_users_without_ev = User.objects.filter( - userprofile__awaiting_email_verification=False - ).exclude(email_verifications__verified=True) - - records = [ - EmailVerification( - user=user, - email=user.email, - token=uuid4().hex[:30], - expiry=timezone.now() + timedelta(hours=1), - verified=True, - ) - for user in verified_users_without_ev - ] - - EmailVerification.objects.bulk_create(records) - - -def reverse(apps, schema_editor): - pass - - -class Migration(migrations.Migration): - dependencies = [("portal", "0050_refactor_emailverifications_2")] - - operations = [migrations.RunPython(add_missing_ev_records, reverse)] diff --git a/backend/portal/migrations/0052_refactor_emailverifications_3.py b/backend/portal/migrations/0052_refactor_emailverifications_3.py deleted file mode 100644 index 3c02bd3..0000000 --- a/backend/portal/migrations/0052_refactor_emailverifications_3.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [("portal", "0051_add_missing_ev_records")] - - operations = [ - migrations.RemoveField(model_name="emailverification", name="new_user") - ] diff --git a/backend/portal/migrations/0053_refactor_teacher_student_1.py b/backend/portal/migrations/0053_refactor_teacher_student_1.py deleted file mode 100644 index e3e409b..0000000 --- a/backend/portal/migrations/0053_refactor_teacher_student_1.py +++ /dev/null @@ -1,65 +0,0 @@ -from __future__ import unicode_literals - -from django.db import models, migrations -from django.conf import settings - - -class Migration(migrations.Migration): - dependencies = [("portal", "0052_refactor_emailverifications_3")] - - operations = [ - migrations.AddField( - model_name="teacher", - name="new_user", - field=models.OneToOneField( - to=settings.AUTH_USER_MODEL, - related_name="new_teacher", - null=True, - blank=True, - on_delete=models.CASCADE, - ), - ), - migrations.AddField( - model_name="student", - name="new_user", - field=models.OneToOneField( - to=settings.AUTH_USER_MODEL, - related_name="new_student", - null=True, - blank=True, - on_delete=models.CASCADE, - ), - ), - migrations.AddField( - model_name="guardian", - name="new_user", - field=models.OneToOneField( - to=settings.AUTH_USER_MODEL, - related_name="new_guardian", - null=True, - blank=True, - on_delete=models.CASCADE, - ), - ), - migrations.RunSQL( - ( - "UPDATE portal_teacher SET new_user_id = (" - " SELECT portal_userprofile.user_id FROM portal_userprofile " - " WHERE portal_userprofile.id = portal_teacher.user_id);" - ) - ), - migrations.RunSQL( - ( - "UPDATE portal_student SET new_user_id = (" - " SELECT portal_userprofile.user_id FROM portal_userprofile " - " WHERE portal_userprofile.id = portal_student.user_id);" - ) - ), - migrations.RunSQL( - ( - "UPDATE portal_guardian SET new_user_id = (" - " SELECT portal_userprofile.user_id FROM portal_userprofile " - " WHERE portal_userprofile.id = portal_guardian.user_id);" - ) - ), - ] diff --git a/backend/portal/migrations/0054_pending_join_request_can_be_blank.py b/backend/portal/migrations/0054_pending_join_request_can_be_blank.py deleted file mode 100644 index 4ef7717..0000000 --- a/backend/portal/migrations/0054_pending_join_request_can_be_blank.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [("portal", "0053_refactor_teacher_student_1")] - - operations = [ - migrations.AlterField( - model_name="teacher", - name="pending_join_request", - field=models.ForeignKey( - related_name="join_request", - blank=True, - to="portal.School", - null=True, - on_delete=models.SET_NULL, - ), - ) - ] diff --git a/backend/portal/migrations/0055_add_preview_user.py b/backend/portal/migrations/0055_add_preview_user.py deleted file mode 100644 index 1fb634c..0000000 --- a/backend/portal/migrations/0055_add_preview_user.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [("portal", "0054_pending_join_request_can_be_blank")] - - operations = [ - migrations.AddField( - model_name="school", - name="eligible_for_testing", - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name="userprofile", - name="preview_user", - field=models.BooleanField(default=False), - ), - ] diff --git a/backend/portal/migrations/0056_remove_preview_user.py b/backend/portal/migrations/0056_remove_preview_user.py deleted file mode 100644 index 1cb4b2d..0000000 --- a/backend/portal/migrations/0056_remove_preview_user.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 1.10.8 on 2019-08-27 08:37 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [("portal", "0055_add_preview_user")] - - operations = [ - migrations.RemoveField(model_name="userprofile", name="preview_user"), - migrations.RemoveField(model_name="school", name="eligible_for_testing"), - ] diff --git a/backend/portal/migrations/0057_delete_frontpagenews.py b/backend/portal/migrations/0057_delete_frontpagenews.py deleted file mode 100644 index 76ed4a9..0000000 --- a/backend/portal/migrations/0057_delete_frontpagenews.py +++ /dev/null @@ -1,11 +0,0 @@ -# Generated by Django 1.11.24 on 2020-03-02 13:10 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [("portal", "0056_remove_preview_user")] - - operations = [migrations.DeleteModel(name="FrontPageNews")] diff --git a/backend/portal/migrations/0058_move_to_common_models.py b/backend/portal/migrations/0058_move_to_common_models.py deleted file mode 100644 index ff9c1dd..0000000 --- a/backend/portal/migrations/0058_move_to_common_models.py +++ /dev/null @@ -1,217 +0,0 @@ -""" -This migration essentially "replaces" the majority of the models in models.py with -the models in common/models.py. -The models affected here are UserProfile, School, Teacher, Class and Student. -For each of these models, this migration: -- removes each of the model fields, -- updates Guardian's foreign keys to use the models in common/models.py, -- deletes the model, and updates the DB's references of these models to the new ones -created in common/models.py. - -The important thing to note here is the use of migrations.SeparateDatabaseAndState. -This operation makes it possible to makes different changes to the state and to the -database. Essentially this is used here to replace Portal's models with Common's -models, without moving or deleting anything from the database. -This migration has been made following the tutorial on how to move Django models: -https://realpython.com/move-django-model/ (following the Django way example). - -Since Rapid Router's models used to reference Portal's models, Rapid Router's migration -0071 needs to happen first, to avoid causing an error when migrating. -""" -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - atomic = False - - dependencies = [ - ("common", "0001_initial"), - ("game", "0071_use_common_models"), - ("portal", "0057_delete_frontpagenews"), - ] - - operations = [ - migrations.SeparateDatabaseAndState( - state_operations=[ - migrations.RemoveField( - model_name="class", - name="teacher", - ), - ], - database_operations=[], - ), - migrations.SeparateDatabaseAndState( - state_operations=[ - migrations.RemoveField( - model_name="student", - name="class_field", - ), - ], - database_operations=[], - ), - migrations.SeparateDatabaseAndState( - state_operations=[ - migrations.RemoveField( - model_name="student", - name="new_user", - ), - ], - database_operations=[], - ), - migrations.SeparateDatabaseAndState( - state_operations=[ - migrations.RemoveField( - model_name="student", - name="pending_class_request", - ), - ], - database_operations=[], - ), - migrations.SeparateDatabaseAndState( - state_operations=[ - migrations.RemoveField( - model_name="student", - name="user", - ), - ], - database_operations=[], - ), - migrations.SeparateDatabaseAndState( - state_operations=[ - migrations.RemoveField( - model_name="teacher", - name="new_user", - ), - ], - database_operations=[], - ), - migrations.SeparateDatabaseAndState( - state_operations=[ - migrations.RemoveField( - model_name="teacher", - name="pending_join_request", - ), - ], - database_operations=[], - ), - migrations.SeparateDatabaseAndState( - state_operations=[ - migrations.RemoveField( - model_name="teacher", - name="school", - ), - ], - database_operations=[], - ), - migrations.SeparateDatabaseAndState( - state_operations=[ - migrations.RemoveField( - model_name="teacher", - name="user", - ), - ], - database_operations=[], - ), - migrations.SeparateDatabaseAndState( - state_operations=[ - migrations.RemoveField( - model_name="userprofile", - name="user", - ), - ], - database_operations=[], - ), - migrations.SeparateDatabaseAndState( - state_operations=[ - migrations.AlterField( - model_name="guardian", - name="children", - field=models.ManyToManyField(to="common.Student"), - ), - ], - # You're reusing an existing table, so do nothing - database_operations=[], - ), - migrations.SeparateDatabaseAndState( - state_operations=[ - migrations.AlterField( - model_name="guardian", - name="user", - field=models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, - to="common.UserProfile", - ), - ), - ], - # You're reusing an existing table, so do nothing - database_operations=[], - ), - migrations.SeparateDatabaseAndState( - state_operations=[ - migrations.DeleteModel( - name="Class", - ), - ], - database_operations=[ - migrations.AlterModelTable( - name="Class", - table="common_class", - ), - ], - ), - migrations.SeparateDatabaseAndState( - state_operations=[ - migrations.DeleteModel( - name="School", - ), - ], - database_operations=[ - migrations.AlterModelTable( - name="School", - table="common_school", - ), - ], - ), - migrations.SeparateDatabaseAndState( - state_operations=[ - migrations.DeleteModel( - name="Student", - ), - ], - database_operations=[ - migrations.AlterModelTable( - name="Student", - table="common_student", - ), - ], - ), - migrations.SeparateDatabaseAndState( - state_operations=[ - migrations.DeleteModel( - name="Teacher", - ), - ], - database_operations=[ - migrations.AlterModelTable( - name="Teacher", - table="common_teacher", - ), - ], - ), - migrations.SeparateDatabaseAndState( - state_operations=[ - migrations.DeleteModel( - name="UserProfile", - ), - ], - database_operations=[ - migrations.AlterModelTable( - name="UserProfile", - table="common_userprofile", - ), - ], - ), - ] diff --git a/backend/portal/migrations/0059_move_email_verifications_to_common.py b/backend/portal/migrations/0059_move_email_verifications_to_common.py deleted file mode 100644 index bca5f00..0000000 --- a/backend/portal/migrations/0059_move_email_verifications_to_common.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 1.11.24 on 2020-09-10 08:46 -from __future__ import unicode_literals - -from django.db import migrations -from django.db.migrations.operations.special import SeparateDatabaseAndState - - -class Migration(migrations.Migration): - atomic = False - - dependencies = [ - ("portal", "0058_move_to_common_models"), - ("common", "0002_emailverification"), - ] - - operations = [ - migrations.SeparateDatabaseAndState( - state_operations=[ - migrations.RemoveField( - model_name="emailverification", - name="user", - ), - ], - database_operations=[], - ), - migrations.SeparateDatabaseAndState( - state_operations=[migrations.DeleteModel(name="EmailVerification")], - database_operations=[ - migrations.AlterModelTable( - name="EmailVerification", table="common_emailverification" - ) - ], - ), - ] diff --git a/backend/portal/migrations/0060_delete_guardian.py b/backend/portal/migrations/0060_delete_guardian.py deleted file mode 100644 index f6501c0..0000000 --- a/backend/portal/migrations/0060_delete_guardian.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 2.0 on 2020-10-28 17:36 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("portal", "0059_move_email_verifications_to_common"), - ] - - operations = [ - migrations.RemoveField( - model_name="guardian", - name="children", - ), - migrations.RemoveField( - model_name="guardian", - name="new_user", - ), - migrations.RemoveField( - model_name="guardian", - name="user", - ), - migrations.DeleteModel( - name="Guardian", - ), - ] diff --git a/backend/portal/migrations/0061_make_portaladmin_teacher.py b/backend/portal/migrations/0061_make_portaladmin_teacher.py deleted file mode 100644 index c239c14..0000000 --- a/backend/portal/migrations/0061_make_portaladmin_teacher.py +++ /dev/null @@ -1,114 +0,0 @@ -# Generated by Django 2.0 on 2020-10-28 17:36 - -import os - -from django.contrib.auth.hashers import make_password -from django.db import migrations - - -def give_portaladmin_teacher_profile(apps, schema_editor): - """ - This migration is so that we can still log in using the portaladmin User, but from - the teacher login form, which requires school data to be linked to the User. - """ - User = apps.get_model("auth", "User") - UserProfile = apps.get_model("common", "UserProfile") - School = apps.get_model("common", "School") - Teacher = apps.get_model("common", "Teacher") - Class = apps.get_model("common", "Class") - Student = apps.get_model("common", "Student") - - # Amend portaladmin details - portaladmin = User.objects.get(username="portaladmin") - portaladmin.first_name = "Portal" - portaladmin.last_name = "Admin" - portaladmin.email = "codeforlife-portal@ocado.com" - portaladmin.save() - - # Create portaladmin UserProfile - portaladmin_userprofile = UserProfile.objects.create(user=portaladmin) - - # Find test school - portaladmin_school, _ = School.objects.get_or_create( - name="Swiss Federal Polytechnic" - ) - - # Create Teacher object and link it to School - portaladmin_teacher = Teacher.objects.create( - title="Mr", - user=portaladmin_userprofile, - new_user=portaladmin, - school=portaladmin_school, - is_admin=True, - pending_join_request=None, - ) - - # Create a Class - portaladmin_class = Class.objects.create( - name="Portaladmin's class", - teacher=portaladmin_teacher, - access_code="PO123", - classmates_data_viewable=True, - always_accept_requests=True, - ) - - # Create the student User - portaladmin_student_user = User.objects.create( - username="portaladmin student", - first_name="Portaladmin", - last_name="Student", - email="adminstudent@codeforlife.com", - password=make_password(os.getenv("ADMIN_PASSWORD", "Password1")), - ) - - # Create the student UserProfile - portaladmin_student_userprofile = UserProfile.objects.create( - user=portaladmin_student_user - ) - - # Create the Student - Student.objects.create( - class_field=portaladmin_class, - user=portaladmin_student_userprofile, - new_user=portaladmin_student_user, - pending_class_request=None, - ) - - -def revert_portaladmin_data(apps, schema_editor): - User = apps.get_model("auth", "User") - UserProfile = apps.get_model("common", "UserProfile") - Teacher = apps.get_model("common", "Teacher") - Class = apps.get_model("common", "Class") - Student = apps.get_model("common", "Student") - - portaladmin = User.objects.get(username="portaladmin") - portaladmin_userprofile = UserProfile.objects.get(user=portaladmin) - portaladmin_teacher = Teacher.objects.get(new_user=portaladmin) - portaladmin_class = Class.objects.get(teacher=portaladmin_teacher) - portaladmin_student_user = User.objects.get(username="portaladmin student") - portaladmin_student_userprofile = UserProfile.objects.get( - user=portaladmin_student_user - ) - portaladmin_student = Student.objects.get(user=portaladmin_student_userprofile) - - portaladmin_student.delete() - portaladmin_student_userprofile.delete() - portaladmin_student_user.delete() - portaladmin_class.delete() - portaladmin_teacher.delete() - portaladmin_userprofile.delete() - - portaladmin.first_name = "" - portaladmin.last_name = "" - portaladmin.email = "('codeforlife-portal@ocado.com',)" - portaladmin.save() - - -class Migration(migrations.Migration): - - dependencies = [("portal", "0060_delete_guardian")] - - operations = [ - migrations.RunPython(give_portaladmin_teacher_profile, revert_portaladmin_data) - ] diff --git a/backend/portal/migrations/0062_verify_portaladmin.py b/backend/portal/migrations/0062_verify_portaladmin.py deleted file mode 100644 index 6b7997d..0000000 --- a/backend/portal/migrations/0062_verify_portaladmin.py +++ /dev/null @@ -1,44 +0,0 @@ -# Generated by Django 2.0 on 2020-10-28 17:36 - -import datetime -from uuid import uuid4 - -from django.db import migrations -from django.utils import timezone - - -def verify_portaladmin(apps, schema_editor): - """ - This migration is so that we can still log in using the portaladmin User, as the - login form now requires email verification. - """ - User = apps.get_model("auth", "User") - EmailVerification = apps.get_model("common", "EmailVerification") - - portaladmin = User.objects.get(username="portaladmin") - - EmailVerification.objects.create( - user=portaladmin, - email=portaladmin.email, - token=uuid4().hex[:30], - expiry=timezone.now() + datetime.timedelta(hours=1), - verified=True, - ) - - -def revert_portaladmin_verification(apps, schema_editor): - User = apps.get_model("auth", "User") - EmailVerification = apps.get_model("common", "EmailVerification") - - portaladmin = User.objects.get(username="portaladmin") - portaladmin_verification = EmailVerification.objects.get(user=portaladmin) - portaladmin_verification.delete() - - -class Migration(migrations.Migration): - - dependencies = [("portal", "0061_make_portaladmin_teacher")] - - operations = [ - migrations.RunPython(verify_portaladmin, revert_portaladmin_verification) - ] diff --git a/backend/portal/migrations/__init__.py b/backend/portal/migrations/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/backend/portal/migrations/__init__.py +++ /dev/null @@ -1 +0,0 @@ - From a22d25fe2f15673346706d518fb188088ad865be Mon Sep 17 00:00:00 2001 From: SKairinos Date: Wed, 13 Sep 2023 10:46:33 +0100 Subject: [PATCH 05/58] auth backends --- backend/api/auth_backends/__init__.py | 5 ++ .../api/auth_backends/dependent_student.py | 64 +++++++++++++++++++ .../teacher_or_independent_student.py | 25 ++++++++ backend/service/settings.py | 9 +++ 4 files changed, 103 insertions(+) create mode 100644 backend/api/auth_backends/__init__.py create mode 100644 backend/api/auth_backends/dependent_student.py create mode 100644 backend/api/auth_backends/teacher_or_independent_student.py diff --git a/backend/api/auth_backends/__init__.py b/backend/api/auth_backends/__init__.py new file mode 100644 index 0000000..9773c1b --- /dev/null +++ b/backend/api/auth_backends/__init__.py @@ -0,0 +1,5 @@ +from .dependent_student import ( + DependentStudentBackend, + DependentStudentTokenBackend, +) +from .teacher_or_independent_student import TeacherOrIndependentStudentBackend diff --git a/backend/api/auth_backends/dependent_student.py b/backend/api/auth_backends/dependent_student.py new file mode 100644 index 0000000..d00529e --- /dev/null +++ b/backend/api/auth_backends/dependent_student.py @@ -0,0 +1,64 @@ +import typing as t + +from common.helpers.generators import get_hashed_login_id +from common.models import Student +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import BaseBackend +from django.contrib.auth.base_user import AbstractBaseUser +from django.http.request import HttpRequest + +User = get_user_model() + + +class DependentStudentBackend(BaseBackend): + def authenticate( + self, + request: HttpRequest, + username: t.Optional[str] = None, + password: t.Optional[str] = None, + classId: t.Optional[str] = None, + **kwargs + ) -> t.Optional[AbstractBaseUser]: + if username is None or password is None or classId is None: + return + + try: + user = User.objects.get( + username=username, + new_student__class_field__access_code=classId, + ) + if user.check_password(password): + return user + except User.DoesNotExist: + return + + def get_user(self, user_id: int) -> t.Optional[AbstractBaseUser]: + try: + return User.objects.get(id=user_id) + except User.DoesNotExist: + return + + +class DependentStudentTokenBackend(BaseBackend): + def authenticate( + self, + request: HttpRequest, + user_id: t.Optional[int] = None, + login_id: t.Optional[str] = None, + **kwargs + ) -> t.Optional[AbstractBaseUser]: + user = self.get_user(user_id) + if user: + # Check the url against the student's stored hash. + student = Student.objects.get(new_user=user) + if ( + student.login_id + and get_hashed_login_id(login_id) == student.login_id + ): + return user + + def get_user(self, user_id: int) -> t.Optional[AbstractBaseUser]: + try: + return User.objects.get(id=user_id) + except User.DoesNotExist: + return diff --git a/backend/api/auth_backends/teacher_or_independent_student.py b/backend/api/auth_backends/teacher_or_independent_student.py new file mode 100644 index 0000000..08aa314 --- /dev/null +++ b/backend/api/auth_backends/teacher_or_independent_student.py @@ -0,0 +1,25 @@ +import typing as t + +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import BaseBackend +from django.contrib.auth.base_user import AbstractBaseUser +from django.http.request import HttpRequest + +User = get_user_model() + + +class TeacherOrIndependentStudentBackend(BaseBackend): + def authenticate( + self, + request: HttpRequest, + username: t.Optional[str] = None, + password: t.Optional[str] = None, + **kwargs + ) -> t.Optional[AbstractBaseUser]: + return super().authenticate(request, username, password, **kwargs) + + def get_user(self, user_id: int) -> t.Optional[AbstractBaseUser]: + try: + return User.objects.get(id=user_id) + except User.DoesNotExist: + return diff --git a/backend/service/settings.py b/backend/service/settings.py index 4d917ca..7edfae6 100644 --- a/backend/service/settings.py +++ b/backend/service/settings.py @@ -131,3 +131,12 @@ # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +# Authentication backend classes +# https://docs.djangoproject.com/en/4.2/ref/settings/#authentication-backends + +AUTHENTICATION_BACKENDS = [ + "api.auth_backends.DependentStudentBackend", + "api.auth_backends.DependentStudentTokenBackend", + "api.auth_backends.TeacherOrIndependentStudentBackend", +] From 3598cae83dfeb94ffdd2708af9ff391bb52fb5d7 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Wed, 13 Sep 2023 11:40:55 +0100 Subject: [PATCH 06/58] tidy up auth backends --- backend/api/auth_backends/__init__.py | 8 ++-- ...ndent_student.py => email_and_password.py} | 16 ++++++-- ...endent_student.py => user_id_and_token.py} | 34 ++-------------- .../username_and_password_and_class_id.py | 39 +++++++++++++++++++ 4 files changed, 59 insertions(+), 38 deletions(-) rename backend/api/auth_backends/{teacher_or_independent_student.py => email_and_password.py} (59%) rename backend/api/auth_backends/{dependent_student.py => user_id_and_token.py} (53%) create mode 100644 backend/api/auth_backends/username_and_password_and_class_id.py diff --git a/backend/api/auth_backends/__init__.py b/backend/api/auth_backends/__init__.py index 9773c1b..3807de3 100644 --- a/backend/api/auth_backends/__init__.py +++ b/backend/api/auth_backends/__init__.py @@ -1,5 +1,5 @@ -from .dependent_student import ( - DependentStudentBackend, - DependentStudentTokenBackend, +from .email_and_password import EmailAndPasswordBackend +from .user_id_and_token import UserIdAndTokenBackend +from .username_and_password_and_class_id import ( + UsernameAndPasswordAndClassIdBackend, ) -from .teacher_or_independent_student import TeacherOrIndependentStudentBackend diff --git a/backend/api/auth_backends/teacher_or_independent_student.py b/backend/api/auth_backends/email_and_password.py similarity index 59% rename from backend/api/auth_backends/teacher_or_independent_student.py rename to backend/api/auth_backends/email_and_password.py index 08aa314..374ad60 100644 --- a/backend/api/auth_backends/teacher_or_independent_student.py +++ b/backend/api/auth_backends/email_and_password.py @@ -8,15 +8,25 @@ User = get_user_model() -class TeacherOrIndependentStudentBackend(BaseBackend): +class EmailAndPasswordBackend(BaseBackend): def authenticate( self, request: HttpRequest, - username: t.Optional[str] = None, + email: t.Optional[str] = None, password: t.Optional[str] = None, **kwargs ) -> t.Optional[AbstractBaseUser]: - return super().authenticate(request, username, password, **kwargs) + if email is None or password is None: + return + + try: + user = User.objects.get(email=email) + if getattr(user, "is_active", True) and user.check_password( + password + ): + return user + except User.DoesNotExist: + return def get_user(self, user_id: int) -> t.Optional[AbstractBaseUser]: try: diff --git a/backend/api/auth_backends/dependent_student.py b/backend/api/auth_backends/user_id_and_token.py similarity index 53% rename from backend/api/auth_backends/dependent_student.py rename to backend/api/auth_backends/user_id_and_token.py index d00529e..b73bc66 100644 --- a/backend/api/auth_backends/dependent_student.py +++ b/backend/api/auth_backends/user_id_and_token.py @@ -10,36 +10,7 @@ User = get_user_model() -class DependentStudentBackend(BaseBackend): - def authenticate( - self, - request: HttpRequest, - username: t.Optional[str] = None, - password: t.Optional[str] = None, - classId: t.Optional[str] = None, - **kwargs - ) -> t.Optional[AbstractBaseUser]: - if username is None or password is None or classId is None: - return - - try: - user = User.objects.get( - username=username, - new_student__class_field__access_code=classId, - ) - if user.check_password(password): - return user - except User.DoesNotExist: - return - - def get_user(self, user_id: int) -> t.Optional[AbstractBaseUser]: - try: - return User.objects.get(id=user_id) - except User.DoesNotExist: - return - - -class DependentStudentTokenBackend(BaseBackend): +class UserIdAndTokenBackend(BaseBackend): def authenticate( self, request: HttpRequest, @@ -48,11 +19,12 @@ def authenticate( **kwargs ) -> t.Optional[AbstractBaseUser]: user = self.get_user(user_id) - if user: + if user and getattr(user, "is_active", True): # Check the url against the student's stored hash. student = Student.objects.get(new_user=user) if ( student.login_id + # TODO: refactor this and get_hashed_login_id(login_id) == student.login_id ): return user diff --git a/backend/api/auth_backends/username_and_password_and_class_id.py b/backend/api/auth_backends/username_and_password_and_class_id.py new file mode 100644 index 0000000..d56e63c --- /dev/null +++ b/backend/api/auth_backends/username_and_password_and_class_id.py @@ -0,0 +1,39 @@ +import typing as t + +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import BaseBackend +from django.contrib.auth.base_user import AbstractBaseUser +from django.http.request import HttpRequest + +User = get_user_model() + + +class UsernameAndPasswordAndClassIdBackend(BaseBackend): + def authenticate( + self, + request: HttpRequest, + username: t.Optional[str] = None, + password: t.Optional[str] = None, + class_id: t.Optional[str] = None, + **kwargs + ) -> t.Optional[AbstractBaseUser]: + if username is None or password is None or class_id is None: + return + + try: + user = User.objects.get( + username=username, + new_student__class_field__access_code=class_id, + ) + if getattr(user, "is_active", True) and user.check_password( + password + ): + return user + except User.DoesNotExist: + return + + def get_user(self, user_id: int) -> t.Optional[AbstractBaseUser]: + try: + return User.objects.get(id=user_id) + except User.DoesNotExist: + return From 5f1c537b08a24cf548d371aeece932b3dadfbcc6 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Wed, 13 Sep 2023 11:59:35 +0100 Subject: [PATCH 07/58] update auth backends --- backend/service/settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/service/settings.py b/backend/service/settings.py index 7edfae6..d03b038 100644 --- a/backend/service/settings.py +++ b/backend/service/settings.py @@ -136,7 +136,7 @@ # https://docs.djangoproject.com/en/4.2/ref/settings/#authentication-backends AUTHENTICATION_BACKENDS = [ - "api.auth_backends.DependentStudentBackend", - "api.auth_backends.DependentStudentTokenBackend", - "api.auth_backends.TeacherOrIndependentStudentBackend", + "api.auth_backends.EmailAndPasswordBackend", + "api.auth_backends.UserIdAndTokenBackend", + "api.auth_backends.UsernameAndPasswordAndClassIdBackend", ] From cc510c8c5ea27c5c0ed614e03ee779b68db7c61f Mon Sep 17 00:00:00 2001 From: SKairinos Date: Wed, 13 Sep 2023 13:06:36 +0100 Subject: [PATCH 08/58] quick save --- backend/api/forms.py | 29 +++++++++++++++++++++++++++++ backend/api/urls/session.py | 8 ++++++-- backend/api/views/session.py | 31 ++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 backend/api/forms.py diff --git a/backend/api/forms.py b/backend/api/forms.py new file mode 100644 index 0000000..3616b82 --- /dev/null +++ b/backend/api/forms.py @@ -0,0 +1,29 @@ +from django import forms +from django.contrib.auth.forms import UsernameField +from django.core.validators import RegexValidator + + +class CredentialsForm(forms.Form): + email = forms.EmailField() + password = forms.CharField(strip=False) + + +class OneTimePasswordForm(forms.Form): + otp = forms.CharField( + validators=[ + RegexValidator(r"^[0-9]{6}$", "Must be 6 digits"), + ], + ) + + +class DependentStudentAuthForm(forms.Form): + username = UsernameField() + password = forms.CharField(strip=False) + class_id = forms.CharField( + validators=[ + RegexValidator( + r"^[A-Z]{2}[0-9]{3}$", + "Must be 2 upper case letters followed by 3 digits", + ), + ], + ) diff --git a/backend/api/urls/session.py b/backend/api/urls/session.py index 3309039..6a3f37c 100644 --- a/backend/api/urls/session.py +++ b/backend/api/urls/session.py @@ -1,9 +1,13 @@ from django.contrib.auth.views import LogoutView -from django.urls import path +from django.urls import path, re_path from ..views.session import LoginView urlpatterns = [ - path("login/", LoginView.as_view(), name="login"), + re_path( + r"^(?Pteacher|dependent_student|independent_student)/login/$", + LoginView.as_view(), + name="login", + ), path("logout/", LogoutView.as_view(), name="logout"), ] diff --git a/backend/api/views/session.py b/backend/api/views/session.py index af18802..2c9cb5c 100644 --- a/backend/api/views/session.py +++ b/backend/api/views/session.py @@ -1,6 +1,35 @@ +import typing as t + +from common.models import UserSession +from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.views import LoginView as _LoginView +from django.http import HttpResponse + +from ..forms import ( + CredentialsForm, + DependentStudentAuthForm, + OneTimePasswordForm, +) # TODO: add 2FA logic class LoginView(_LoginView): - pass + def get_form_class(self): + user_type: str = self.kwargs["user_type"] + if user_type == "independent_student": + return CredentialsForm + elif user_type == "dependent_student": + return DependentStudentAuthForm + else: + return CredentialsForm + + def form_valid(self, form: AuthenticationForm) -> HttpResponse: + user = form.get_user() + + # teacher + if user.new_teacher is not None: + pass + + UserSession.objects.create(user=user) + + return super().form_valid(form) From a78f098cf320889836f9b522a823a8ed05ab1d3c Mon Sep 17 00:00:00 2001 From: SKairinos Date: Wed, 13 Sep 2023 16:22:54 +0100 Subject: [PATCH 09/58] deploy to gcloud --- .github/workflows/main.yaml | 88 +++++++++++++++++++++++++++++++++++++ backend/.gcloudignore | 4 ++ backend/app.yaml | 10 +++++ backend/main.py | 3 ++ backend/service/settings.py | 2 +- backend/service/urls.py | 4 +- 6 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/main.yaml create mode 100644 backend/.gcloudignore create mode 100644 backend/app.yaml create mode 100644 backend/main.py diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..db6307b --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,88 @@ +name: Main + +on: + push: + paths-ignore: + - "README.md" + - "CHANGELOG.md" + - "LICENSE" + - ".gitignore" + - ".vscode" + workflow_dispatch: + +env: + SERVICE: portal + PYTHON_VERSION: 3.8 + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: 🛫 Checkout + uses: actions/checkout@v3 + + - name: 🐍 Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install Python Packages + run: | + python -m pip install --upgrade pip + python -m pip install pipenv + pipenv install --dev + + - name: Check Code Format + run: if ! pipenv run black --check .; then exit 1; fi + + - name: Check Migrations + run: pipenv run python manage.py makemigrations --check --dry-run + + # TODO: assert code coverage target. + - name: Test Code Units + run: pipenv run pytest + + build-and-deploy: + runs-on: ubuntu-latest + # if: github.ref_name == 'production' || github.ref_name == 'development' || github.ref_name == 'staging' + # environment: ${{ github.ref_name }} + steps: + - name: 🛫 Checkout + uses: actions/checkout@v3 + + - name: 🐍 Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: 🗝 Authenticate with GCloud + uses: google-github-actions/auth@v1 + with: + credentials_json: ${{ secrets.GCP_CREDENTIALS }} + + - name: 🤖 Set up GCloud SDK + uses: google-github-actions/setup-gcloud@v1 + + - name: 🛠 Install Backend Dependencies + working-directory: ./backend + run: | + python -m pip install --upgrade pip + python -m pip install pipenv + pipenv install + + - name: 🛠 Generate requirements.txt + working-directory: ./backend + run: pipenv requirements > requirements.txt + + # https://mikefarah.gitbook.io/yq/ + - name: 🖊️ Configure App Deployment + uses: mikefarah/yq@master + with: + cmd: | + yq -i ' + .service = "development-kurono" + ' backend/app.yaml + + - name: 🚀 Deploy App on GCloud + working-directory: ./backend + run: gcloud app deploy diff --git a/backend/.gcloudignore b/backend/.gcloudignore new file mode 100644 index 0000000..30c9156 --- /dev/null +++ b/backend/.gcloudignore @@ -0,0 +1,4 @@ +Pipfile +Pipfile.lock +manage.py +pyproject.toml diff --git a/backend/app.yaml b/backend/app.yaml new file mode 100644 index 0000000..03c5a89 --- /dev/null +++ b/backend/app.yaml @@ -0,0 +1,10 @@ +runtime: python38 +instance_class: F2 +service: REPLACE_ME + +inbound_services: + - warmup + +env_variables: + DEBUG: "0" + DJANGO_SETTINGS_MODULE: "service.settings" diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..8f87c81 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,3 @@ +# This is the entrypoint to our app. +# https://cloud.google.com/appengine/docs/standard/python3/runtime#application_startup +from service.wsgi import application as app diff --git a/backend/service/settings.py b/backend/service/settings.py index d03b038..72dde95 100644 --- a/backend/service/settings.py +++ b/backend/service/settings.py @@ -24,7 +24,7 @@ SECRET_KEY = "replace-me-and-put-me-in-a-github-secret" # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = bool(int(os.getenv("DEBUG", "1"))) ALLOWED_HOSTS = ["*"] diff --git a/backend/service/urls.py b/backend/service/urls.py index 84352c8..5f6b787 100644 --- a/backend/service/urls.py +++ b/backend/service/urls.py @@ -14,9 +14,11 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import include, path +from django.urls import include, path, re_path +from django.http import HttpResponse urlpatterns = [ path("admin/", admin.site.urls), path("api/", include("api.urls")), + re_path(r".*", lambda request: HttpResponse("found me!")), ] From 1b1945b5f6001c3b380f3004cc2bb37b23c7e5d2 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Wed, 13 Sep 2023 16:30:32 +0100 Subject: [PATCH 10/58] fix pipeline --- .github/workflows/main.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index db6307b..8c4fc98 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -27,23 +27,28 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Install Python Packages + working-directory: ./backend run: | python -m pip install --upgrade pip python -m pip install pipenv pipenv install --dev - name: Check Code Format + working-directory: ./backend run: if ! pipenv run black --check .; then exit 1; fi - name: Check Migrations + working-directory: ./backend run: pipenv run python manage.py makemigrations --check --dry-run # TODO: assert code coverage target. - name: Test Code Units + working-directory: ./backend run: pipenv run pytest build-and-deploy: runs-on: ubuntu-latest + needs: [test] # if: github.ref_name == 'production' || github.ref_name == 'development' || github.ref_name == 'staging' # environment: ${{ github.ref_name }} steps: From ae05f021c65165c33e8a859d37c9f0cdb1c06eb3 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Wed, 13 Sep 2023 18:31:27 +0100 Subject: [PATCH 11/58] don't check migrations --- .github/workflows/main.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 8c4fc98..7f8d1dc 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -37,9 +37,9 @@ jobs: working-directory: ./backend run: if ! pipenv run black --check .; then exit 1; fi - - name: Check Migrations - working-directory: ./backend - run: pipenv run python manage.py makemigrations --check --dry-run + # - name: Check Migrations + # working-directory: ./backend + # run: pipenv run python manage.py makemigrations --check --dry-run # TODO: assert code coverage target. - name: Test Code Units From c44c8d053ae4b888f2a5317eb9c43bded35eea82 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Wed, 13 Sep 2023 18:35:00 +0100 Subject: [PATCH 12/58] use correct service name --- .github/workflows/main.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 7f8d1dc..66afb82 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -11,7 +11,7 @@ on: workflow_dispatch: env: - SERVICE: portal + SERVICE: sso PYTHON_VERSION: 3.8 jobs: @@ -50,7 +50,6 @@ jobs: runs-on: ubuntu-latest needs: [test] # if: github.ref_name == 'production' || github.ref_name == 'development' || github.ref_name == 'staging' - # environment: ${{ github.ref_name }} steps: - name: 🛫 Checkout uses: actions/checkout@v3 @@ -85,7 +84,7 @@ jobs: with: cmd: | yq -i ' - .service = "development-kurono" + .service = "development-${{ env.SERVICE }}" ' backend/app.yaml - name: 🚀 Deploy App on GCloud From dcea83ae6d45e840bdfb68ce5e7e1ec1110d2a34 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Wed, 13 Sep 2023 18:40:00 +0100 Subject: [PATCH 13/58] no pytest --- .github/workflows/main.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 66afb82..8741ef2 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -41,10 +41,10 @@ jobs: # working-directory: ./backend # run: pipenv run python manage.py makemigrations --check --dry-run - # TODO: assert code coverage target. - - name: Test Code Units - working-directory: ./backend - run: pipenv run pytest + # TODO: assert code coverage target. + # - name: Test Code Units + # working-directory: ./backend + # run: pipenv run pytest build-and-deploy: runs-on: ubuntu-latest From b80dccf4586264f6d75c860f9ef22814fc115015 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Wed, 13 Sep 2023 18:40:23 +0100 Subject: [PATCH 14/58] use latest cfl packages --- backend/Pipfile | 13 ++--- backend/Pipfile.lock | 119 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 110 insertions(+), 22 deletions(-) diff --git a/backend/Pipfile b/backend/Pipfile index 4711fe7..9dd0a4d 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -5,13 +5,14 @@ name = "pypi" [packages] # codeforlife = {ref = "v0.1.12", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} -cfl-common = "==6.33.1" # TODO: remove -codeforlife-portal = "*" # TODO: remove -aimmo = ">=2" # TODO: remove -rapid-router = ">=4" # TODO: remove -phonenumbers = "==8.12.12" # TODO: remove -django = "==3.2.19" +django = "==3.2.20" djangorestframework = "==3.13.1" +# https://pypi.org/user/codeforlife/ +cfl-common = "==6.36.0" # TODO: remove +codeforlife-portal = "==6.36.0" # TODO: remove +aimmo = "==2.10.6" # TODO: remove +rapid-router = "==5.11.3" # TODO: remove +phonenumbers = "==8.12.12" # TODO: remove [dev-packages] black = "*" diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 5a82cf2..d72dd9b 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "a91cf7cb4e37bda071cd9aff029fef9ce7e3949b5d477e8cba3c753b8dc76d77" + "sha256": "a2a7249a6cd5fd70a6653649885748f7e737f62141687e212ea45914d5b92ced" }, "pipfile-spec": 6, "requires": { @@ -18,11 +18,11 @@ "default": { "aimmo": { "hashes": [ - "sha256:42d01d0f5a0bfd9acf79242726e3e86d5643d6309188dd5951eabd79d5722401", - "sha256:c15924ca5719ae61f11ee8240eb40a63a1a18167f29bab45646f20f241fe7155" + "sha256:b89f83586412320b147ea61b4277599732c10e7668fba5b2d0a383db6a173145", + "sha256:bd2841b24d7830096b7cc81bdf7548377d30602f1a1b3d9e9084a58b11557413" ], "index": "pypi", - "version": "==2.9.0" + "version": "==2.10.6" }, "asgiref": { "hashes": [ @@ -58,11 +58,11 @@ }, "cfl-common": { "hashes": [ - "sha256:58a1c9b4a2938eb49e4c9c831cd3a220bc91ba2f1cc2504fd8de4cdb88eb500d", - "sha256:b19e630e1575f0ebb9c56fd4053555e0842b39a84debba6de4640c8d06f3804c" + "sha256:6eb1a349e770187c32e075b93b059df9eff0ae42e3abdd98b5fb6e05e9abb465", + "sha256:8ea2b387c91a14da0a03bf1808b5b002a76138c89f23723530911d1c0dc28730" ], "index": "pypi", - "version": "==6.33.1" + "version": "==6.36.0" }, "charset-normalizer": { "hashes": [ @@ -147,11 +147,11 @@ }, "codeforlife-portal": { "hashes": [ - "sha256:358ded433a127d8a067e0b6517bcd084e3af794b78e8b73c28c10fe195d09155", - "sha256:a0f210e13990bd1182d6960135022c5f0f85ee79ee410e3b98700f3f83ac8b20" + "sha256:5e1882e9adec7d767d922cec4cc893d969a08f1ec042394a2f607b31fb9cc087", + "sha256:6d7dfb1ab298e38de27a177598036ed57f1cfa8307481b34d02bf61070057e81" ], "index": "pypi", - "version": "==6.33.1" + "version": "==6.36.0" }, "defusedxml": { "hashes": [ @@ -171,11 +171,12 @@ }, "django": { "hashes": [ - "sha256:031365bae96814da19c10706218c44dff3b654cc4de20a98bd2d29b9bde469f0", - "sha256:21cc991466245d659ab79cb01204f9515690f8dae00e5eabde307f14d24d4d7d" + "sha256:a477ab326ae7d8807dc25c186b951ab8c7648a3a23f9497763c37307a2b5ef87", + "sha256:dec2a116787b8e14962014bf78e120bba454135108e1af9e9b91ade7b2964c40" ], "index": "pypi", - "version": "==3.2.19" + "markers": "python_version >= '3.6'", + "version": "==3.2.20" }, "django-classy-tags": { "hashes": [ @@ -290,6 +291,7 @@ "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa" ], "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==3.13.1" }, "dnspython": { @@ -450,6 +452,40 @@ "markers": "python_version >= '3.5'", "version": "==8.7.0" }, + "numpy": { + "hashes": [ + "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f", + "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61", + "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7", + "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400", + "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef", + "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2", + "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d", + "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc", + "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835", + "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706", + "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5", + "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4", + "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6", + "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463", + "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a", + "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f", + "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e", + "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e", + "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694", + "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8", + "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64", + "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d", + "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc", + "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254", + "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2", + "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1", + "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810", + "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9" + ], + "markers": "python_version >= '3.8'", + "version": "==1.24.4" + }, "oauthlib": { "hashes": [ "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", @@ -472,6 +508,45 @@ ], "version": "==3.1.2" }, + "pandas": { + "hashes": [ + "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682", + "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc", + "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b", + "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089", + "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5", + "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26", + "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210", + "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b", + "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641", + "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd", + "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78", + "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b", + "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e", + "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061", + "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0", + "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e", + "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8", + "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d", + "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0", + "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c", + "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183", + "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df", + "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8", + "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f", + "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02" + ], + "markers": "python_version >= '3.8'", + "version": "==2.0.3" + }, + "pgeocode": { + "hashes": [ + "sha256:07995d4cd2d7fec1f82afb14d6025e83bbc156b6f225fa3e0b3417da2ec020c8", + "sha256:60fc2bad60aa161c3cf46ace4fde607b77e016b1e2a25470534163305499e55e" + ], + "markers": "python_version >= '3.8'", + "version": "==0.4.0" + }, "phonenumbers": { "hashes": [ "sha256:23944f9e628f32a975d3b221b6d76e6ba8ae618d53cb3d82fc23d9e100a59b29", @@ -641,11 +716,11 @@ }, "rapid-router": { "hashes": [ - "sha256:2d4e1069755f2a96cedf670c3e544508a0764f47fcf9963f75247f99b070d894", - "sha256:7f617e37600f3a83be6dc6669786aa2582b9703f94a81cf316dfa44003a04f3e" + "sha256:b3b0b9dd449775aaac8b6dcc05601f8e0d3d1805100ba80a086cc8a3c2661526", + "sha256:bb88bb75e0f743ebedcfd44fbbfba5e1f63e5464c2d7d7bd02d3c838486f78ac" ], "index": "pypi", - "version": "==5.11.0" + "version": "==5.11.3" }, "reportlab": { "hashes": [ @@ -776,6 +851,14 @@ "markers": "python_version >= '3.7'", "version": "==4.7.1" }, + "tzdata": { + "hashes": [ + "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a", + "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda" + ], + "markers": "python_version >= '2'", + "version": "==2023.3" + }, "urllib3": { "hashes": [ "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f", @@ -842,6 +925,7 @@ "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301" ], "index": "pypi", + "markers": "python_version >= '3.8'", "version": "==23.9.1" }, "click": { @@ -914,6 +998,7 @@ "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069" ], "index": "pypi", + "markers": "python_version >= '3.7'", "version": "==7.4.2" }, "pytest-django": { @@ -922,6 +1007,7 @@ "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2" ], "index": "pypi", + "markers": "python_version >= '3.5'", "version": "==4.5.2" }, "pytest-env": { @@ -930,6 +1016,7 @@ "sha256:baed9b3b6bae77bd75b9238e0ed1ee6903a42806ae9d6aeffb8754cd5584d4ff" ], "index": "pypi", + "markers": "python_version >= '3.7'", "version": "==0.8.2" }, "tomli": { From d6230b4400ecfc34a23dc06185692218aad6124c Mon Sep 17 00:00:00 2001 From: SKairinos Date: Thu, 14 Sep 2023 10:41:29 +0100 Subject: [PATCH 15/58] quick save --- backend/api/auth_backends/__init__.py | 2 +- ...d_and_token.py => user_id_and_login_id.py} | 5 +- backend/api/forms.py | 80 ++++++++++++++++++- backend/api/views/session.py | 54 +++++++++---- backend/service/settings.py | 2 +- 5 files changed, 121 insertions(+), 22 deletions(-) rename backend/api/auth_backends/{user_id_and_token.py => user_id_and_login_id.py} (91%) diff --git a/backend/api/auth_backends/__init__.py b/backend/api/auth_backends/__init__.py index 3807de3..5fef85c 100644 --- a/backend/api/auth_backends/__init__.py +++ b/backend/api/auth_backends/__init__.py @@ -1,5 +1,5 @@ from .email_and_password import EmailAndPasswordBackend -from .user_id_and_token import UserIdAndTokenBackend +from .user_id_and_login_id import UserIdAndLoginIdBackend from .username_and_password_and_class_id import ( UsernameAndPasswordAndClassIdBackend, ) diff --git a/backend/api/auth_backends/user_id_and_token.py b/backend/api/auth_backends/user_id_and_login_id.py similarity index 91% rename from backend/api/auth_backends/user_id_and_token.py rename to backend/api/auth_backends/user_id_and_login_id.py index b73bc66..0a5a451 100644 --- a/backend/api/auth_backends/user_id_and_token.py +++ b/backend/api/auth_backends/user_id_and_login_id.py @@ -10,7 +10,7 @@ User = get_user_model() -class UserIdAndTokenBackend(BaseBackend): +class UserIdAndLoginIdBackend(BaseBackend): def authenticate( self, request: HttpRequest, @@ -18,6 +18,9 @@ def authenticate( login_id: t.Optional[str] = None, **kwargs ) -> t.Optional[AbstractBaseUser]: + if user_id is None or login_id is None: + return + user = self.get_user(user_id) if user and getattr(user, "is_active", True): # Check the url against the student's stored hash. diff --git a/backend/api/forms.py b/backend/api/forms.py index 3616b82..c7d0971 100644 --- a/backend/api/forms.py +++ b/backend/api/forms.py @@ -1,23 +1,73 @@ +import typing as t + from django import forms +from django.contrib.auth import authenticate +from django.contrib.auth.base_user import AbstractBaseUser from django.contrib.auth.forms import UsernameField from django.core.validators import RegexValidator +from django.core.exceptions import ValidationError +from django.http import HttpRequest + + +class BaseAuthForm(forms.Form): + def __init__(self, request: HttpRequest = None, *args, **kwargs): + self.request = request # Should this be optional? + self.user: t.Optional[AbstractBaseUser] = None + + def clean(self, **kwargs): + self.user = authenticate(self.request, **kwargs) + if self.user is None: + raise ValidationError( + self.get_invalid_login_error_message(), + code="invalid_login", + ) + # TODO: confirm if we should return error message if is_active=False + return self.cleaned_data -class CredentialsForm(forms.Form): + # Required by Django's LoginView + def get_user(self): + return self.user + + def get_invalid_login_error_message(self) -> str: + raise NotImplementedError() + + +class CredentialsForm(BaseAuthForm): email = forms.EmailField() + # TODO: use regex validator password = forms.CharField(strip=False) + def clean(self): + return super().clean( + email=self.cleaned_data["email"], + password=self.cleaned_data["password"], + ) + + def get_invalid_login_error_message(self): + return ( + "Please enter a correct username and password. Note that both" + " fields may be case-sensitive." + ) -class OneTimePasswordForm(forms.Form): + +class OneTimePasswordForm(BaseAuthForm): otp = forms.CharField( validators=[ RegexValidator(r"^[0-9]{6}$", "Must be 6 digits"), ], ) + def clean(self): + # TODO: implement 2FA flow + return super().clean( + otp=self.cleaned_data["otp"], + ) + -class DependentStudentAuthForm(forms.Form): +class DependentStudentUsernameCredentialsForm(BaseAuthForm): username = UsernameField() + # TODO: use regex validator password = forms.CharField(strip=False) class_id = forms.CharField( validators=[ @@ -27,3 +77,27 @@ class DependentStudentAuthForm(forms.Form): ), ], ) + + def clean(self): + username = self.cleaned_data.get("username") + password = self.cleaned_data.get("password") + + if username is not None and password: + return super().clean( + username=username, + password=password, + class_id=self.cleaned_data["class_id"], + ) + + return self.cleaned_data + + +class DependentStudentUserIdCredentialsForm(BaseAuthForm): + user_id = forms.IntegerField(min_value=1) + login_id = forms.CharField(min_length=32, max_length=32) + + def clean(self): + return super().clean( + userId=self.cleaned_data["user_id"], + login_id=self.cleaned_data["login_id"], + ) diff --git a/backend/api/views/session.py b/backend/api/views/session.py index 2c9cb5c..300f8f8 100644 --- a/backend/api/views/session.py +++ b/backend/api/views/session.py @@ -1,35 +1,57 @@ import typing as t +from enum import Enum +from functools import cached_property from common.models import UserSession -from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.views import LoginView as _LoginView -from django.http import HttpResponse from ..forms import ( + BaseAuthForm, CredentialsForm, - DependentStudentAuthForm, + DependentStudentUserIdCredentialsForm, + DependentStudentUsernameCredentialsForm, OneTimePasswordForm, ) +# TODO: move to codeforlife package +class UserType(str, Enum): + TEACHER = "teacher" + DEP_STUDENT = "dependent_student" + INDEP_STUDENT = "independent_student" + + # TODO: add 2FA logic class LoginView(_LoginView): + @cached_property + def user_type(self) -> UserType: + return UserType(self.kwargs["user_type"]) + def get_form_class(self): - user_type: str = self.kwargs["user_type"] - if user_type == "independent_student": - return CredentialsForm - elif user_type == "dependent_student": - return DependentStudentAuthForm - else: + if self.user_type == UserType.INDEP_STUDENT: return CredentialsForm - def form_valid(self, form: AuthenticationForm) -> HttpResponse: - user = form.get_user() + elif self.user_type == UserType.DEP_STUDENT: + if "user_id" in self.request.POST: + return DependentStudentUserIdCredentialsForm + return DependentStudentUsernameCredentialsForm - # teacher - if user.new_teacher is not None: - pass + else: # user_type == UserType.TEACHER + if False: # TODO: add 2fa logic. + return OneTimePasswordForm + return CredentialsForm - UserSession.objects.create(user=user) + def form_valid(self, form: BaseAuthForm): + response = super().form_valid(form) - return super().form_valid(form) + # TODO: use google analytics + user = form.get_user() + user_session = {"user": user} + if self.user_type == UserType.DEP_STUDENT: + user_session["class_field"] = user.new_student.class_field + user_session["login_type"] = ( + "direct" if "user_id" in self.request.POST else "classform" + ) + UserSession.objects.create(**user_session) + + return response diff --git a/backend/service/settings.py b/backend/service/settings.py index 72dde95..ff28105 100644 --- a/backend/service/settings.py +++ b/backend/service/settings.py @@ -137,6 +137,6 @@ AUTHENTICATION_BACKENDS = [ "api.auth_backends.EmailAndPasswordBackend", - "api.auth_backends.UserIdAndTokenBackend", + "api.auth_backends.UserIdAndLoginIdBackend", "api.auth_backends.UsernameAndPasswordAndClassIdBackend", ] From f5fb6eb3a0c6514968b5a8565306dd32e0a108e1 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Thu, 14 Sep 2023 11:22:09 +0100 Subject: [PATCH 16/58] include a base url for service routing --- backend/api/urls/__init__.py | 11 ++++++++++- backend/service/settings.py | 9 +++++++++ backend/service/urls.py | 32 ++++++++++++++++++++++++++++---- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/backend/api/urls/__init__.py b/backend/api/urls/__init__.py index f7b9a83..c917b03 100644 --- a/backend/api/urls/__init__.py +++ b/backend/api/urls/__init__.py @@ -1,4 +1,6 @@ -from django.urls import include, path +from django.http import HttpResponse +from django.urls import include, path, re_path +from rest_framework import status from .cron import urlpatterns as cron_urlpatterns from .csrf import urlpatterns as csrf_urlpatterns @@ -8,4 +10,11 @@ path("cron/", include(cron_urlpatterns)), path("csrf/", include(csrf_urlpatterns)), path("session/", include(session_urlpatterns)), + re_path( + r".*", + lambda request: HttpResponse( + "API endpoint not found", + status=status.HTTP_404_NOT_FOUND, + ), + ), ] diff --git a/backend/service/settings.py b/backend/service/settings.py index ff28105..7c1c040 100644 --- a/backend/service/settings.py +++ b/backend/service/settings.py @@ -140,3 +140,12 @@ "api.auth_backends.UserIdAndLoginIdBackend", "api.auth_backends.UsernameAndPasswordAndClassIdBackend", ] + + +# ------------------------------------------------------------------------------ +# Custom +# ------------------------------------------------------------------------------ + +# Service + +SERVICE_BASE_URL = os.getenv("SERVICE_BASE_URL", "sso/") diff --git a/backend/service/urls.py b/backend/service/urls.py index 5f6b787..4706c94 100644 --- a/backend/service/urls.py +++ b/backend/service/urls.py @@ -13,12 +13,36 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ +from django.conf import settings from django.contrib import admin -from django.urls import include, path, re_path from django.http import HttpResponse +from django.urls import include, path, re_path +from rest_framework import status urlpatterns = [ - path("admin/", admin.site.urls), - path("api/", include("api.urls")), - re_path(r".*", lambda request: HttpResponse("found me!")), + path( + settings.SERVICE_BASE_URL, + include( + [ + path("admin/", admin.site.urls), + path("api/", include("api.urls")), + re_path( + r".*", + # lambda request: render(request, "frontend.html"), + lambda request: HttpResponse( + "TODO: relocate login pages to sso service and redirect to them" + ), + name="frontend", + ), + ] + ), + ), + re_path( + r".*", + lambda request: HttpResponse( + f'Service not found. The base URL is "{settings.SERVICE_BASE_URL}".', + status=status.HTTP_404_NOT_FOUND, + ), + name="no-service", + ), ] From b46d78910ae44e50dd0936a801fdb10fdb788be8 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Thu, 14 Sep 2023 11:55:05 +0100 Subject: [PATCH 17/58] set base route --- .github/workflows/main.yaml | 15 ++++++++++++++- backend/app.yaml | 1 + backend/service/settings.py | 2 +- backend/service/urls.py | 4 ++-- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 8741ef2..d905a17 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -51,6 +51,18 @@ jobs: needs: [test] # if: github.ref_name == 'production' || github.ref_name == 'development' || github.ref_name == 'staging' steps: + # - name: ⚙️ Set up Environment Variables + # run: | + # echo "SERVICE_BASE_ROUTE=$( + # if [ "${{ github.ref_name }}" == "production" ] + # then echo "sso" + # else echo "${{ github.ref_name }}-sso/" + # fi + # )" >> $GITHUB_ENV + - name: ⚙️ Set up Environment Variables + run: | + echo "SERVICE_BASE_ROUTE=sso/" >> $GITHUB_ENV + - name: 🛫 Checkout uses: actions/checkout@v3 @@ -84,7 +96,8 @@ jobs: with: cmd: | yq -i ' - .service = "development-${{ env.SERVICE }}" + .service = "production-${{ env.SERVICE }}" | + .env_variables.SERVICE_BASE_ROUTE = "${{ env.SERVICE_BASE_ROUTE }}" ' backend/app.yaml - name: 🚀 Deploy App on GCloud diff --git a/backend/app.yaml b/backend/app.yaml index 03c5a89..eb9317e 100644 --- a/backend/app.yaml +++ b/backend/app.yaml @@ -8,3 +8,4 @@ inbound_services: env_variables: DEBUG: "0" DJANGO_SETTINGS_MODULE: "service.settings" + SERVICE_BASE_ROUTE: REPLACE_ME diff --git a/backend/service/settings.py b/backend/service/settings.py index 7c1c040..cf0e87c 100644 --- a/backend/service/settings.py +++ b/backend/service/settings.py @@ -148,4 +148,4 @@ # Service -SERVICE_BASE_URL = os.getenv("SERVICE_BASE_URL", "sso/") +SERVICE_BASE_ROUTE = os.getenv("SERVICE_BASE_ROUTE", "sso/") diff --git a/backend/service/urls.py b/backend/service/urls.py index 4706c94..c6967a4 100644 --- a/backend/service/urls.py +++ b/backend/service/urls.py @@ -21,7 +21,7 @@ urlpatterns = [ path( - settings.SERVICE_BASE_URL, + settings.SERVICE_BASE_ROUTE, include( [ path("admin/", admin.site.urls), @@ -40,7 +40,7 @@ re_path( r".*", lambda request: HttpResponse( - f'Service not found. The base URL is "{settings.SERVICE_BASE_URL}".', + f'The base route is "{settings.SERVICE_BASE_ROUTE}".', status=status.HTTP_404_NOT_FOUND, ), name="no-service", From 22de360c1eb762baa185e3785bd57ff51b1affe3 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Thu, 14 Sep 2023 15:14:51 +0100 Subject: [PATCH 18/58] use cfl package --- backend/Pipfile | 2 +- backend/Pipfile.lock | 168 +++++++++++++++++++++-- backend/api/tests/views/cron/__init__.py | 0 backend/api/tests/views/cron/session.py | 0 backend/api/urls/__init__.py | 2 - backend/api/urls/cron/__init__.py | 7 - backend/api/urls/cron/session.py | 5 - backend/api/urls/session.py | 15 +- backend/api/views/cron/__init__.py | 0 backend/api/views/cron/session.py | 0 backend/api/views/session.py | 48 +++++-- 11 files changed, 203 insertions(+), 44 deletions(-) delete mode 100644 backend/api/tests/views/cron/__init__.py delete mode 100644 backend/api/tests/views/cron/session.py delete mode 100644 backend/api/urls/cron/__init__.py delete mode 100644 backend/api/urls/cron/session.py delete mode 100644 backend/api/views/cron/__init__.py delete mode 100644 backend/api/views/cron/session.py diff --git a/backend/Pipfile b/backend/Pipfile index 9dd0a4d..a172611 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -# codeforlife = {ref = "v0.1.12", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} +codeforlife = {ref = "v0.6.6", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} django = "==3.2.20" djangorestframework = "==3.13.1" # https://pypi.org/user/codeforlife/ diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index d72dd9b..ca7263f 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "a2a7249a6cd5fd70a6653649885748f7e737f62141687e212ea45914d5b92ced" + "sha256": "e75cd8c76bd367b49981ad343a20a6ed661ece7cef252ddfbcf257e7ca6437d0" }, "pipfile-spec": 6, "requires": { @@ -145,6 +145,18 @@ "markers": "python_full_version >= '3.7.0'", "version": "==3.2.0" }, + "click": { + "hashes": [ + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.7" + }, + "codeforlife": { + "git": "https://github.com/ocadotechnology/codeforlife-package-python.git", + "ref": "a063609d36ae2d0d549fdb1ad8f6b21cafedf543" + }, "codeforlife-portal": { "hashes": [ "sha256:5e1882e9adec7d767d922cec4cc893d969a08f1ec042394a2f607b31fb9cc087", @@ -175,7 +187,6 @@ "sha256:dec2a116787b8e14962014bf78e120bba454135108e1af9e9b91ade7b2964c40" ], "index": "pypi", - "markers": "python_version >= '3.6'", "version": "==3.2.20" }, "django-classy-tags": { @@ -209,11 +220,11 @@ }, "django-import-export": { "hashes": [ - "sha256:1d3f2cb2ee3cca0386ed60651fa1623be989f130d9fbdf98a67f7dc3a94b8a37", - "sha256:38fd7b9439b9e3aa1a4747421c1087a5bc194e915a28d795fb8429a5f8028f2d" + "sha256:88ecaf06be06bd95d97cf34f3c911c56c012a7a81712a8956740e5bfc2465162", + "sha256:d02e31908c965d512cc6f7ef6e72935177647b15d3846050d0f094177fca0d86" ], - "markers": "python_version >= '3.7'", - "version": "==3.2.0" + "markers": "python_version >= '3.8'", + "version": "==3.3.1" }, "django-js-reverse": { "hashes": [ @@ -291,7 +302,6 @@ "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa" ], "index": "pypi", - "markers": "python_version >= '3.6'", "version": "==3.13.1" }, "dnspython": { @@ -317,6 +327,14 @@ ], "version": "==0.31.0" }, + "flask": { + "hashes": [ + "sha256:7eb373984bf1c770023fce9db164ed0c3353cd0b53f130f4693da0ca756a2e6d", + "sha256:c0bec9477df1cb867e5a67c9e1ab758de9cb4a3e52dd70681f59fa40a62b3f2d" + ], + "markers": "python_version >= '3.7'", + "version": "==2.2.3" + }, "google-auth": { "hashes": [ "sha256:2cec41407bd1e207f5b802638e32bb837df968bb5c05f413d0fa526fac4cf7a7", @@ -419,6 +437,22 @@ "markers": "python_version >= '3.7'", "version": "==4.13.0" }, + "itsdangerous": { + "hashes": [ + "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", + "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.2" + }, + "jinja2": { + "hashes": [ + "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", + "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.2" + }, "kubernetes": { "hashes": [ "sha256:5854b0c508e8d217ca205591384ab58389abdae608576f9c9afc35a3c76a366c", @@ -444,6 +478,72 @@ ], "version": "==1.14" }, + "markupsafe": { + "hashes": [ + "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e", + "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e", + "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", + "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", + "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c", + "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", + "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc", + "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", + "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939", + "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c", + "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0", + "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4", + "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", + "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", + "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba", + "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d", + "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd", + "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3", + "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", + "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155", + "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", + "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52", + "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", + "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8", + "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b", + "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007", + "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24", + "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea", + "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198", + "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0", + "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee", + "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be", + "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2", + "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1", + "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707", + "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", + "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c", + "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58", + "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823", + "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", + "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636", + "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", + "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", + "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", + "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc", + "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", + "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48", + "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7", + "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e", + "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b", + "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa", + "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5", + "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e", + "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb", + "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", + "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", + "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", + "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc", + "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2", + "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.3" + }, "more-itertools": { "hashes": [ "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced", @@ -633,6 +733,48 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", "version": "==0.3.0" }, + "pydantic": { + "hashes": [ + "sha256:01aea3a42c13f2602b7ecbbea484a98169fb568ebd9e247593ea05f01b884b2e", + "sha256:0cd181f1d0b1d00e2b705f1bf1ac7799a2d938cce3376b8007df62b29be3c2c6", + "sha256:10a86d8c8db68086f1e30a530f7d5f83eb0685e632e411dbbcf2d5c0150e8dcd", + "sha256:193924c563fae6ddcb71d3f06fa153866423ac1b793a47936656e806b64e24ca", + "sha256:464855a7ff7f2cc2cf537ecc421291b9132aa9c79aef44e917ad711b4a93163b", + "sha256:516f1ed9bc2406a0467dd777afc636c7091d71f214d5e413d64fef45174cfc7a", + "sha256:6434b49c0b03a51021ade5c4daa7d70c98f7a79e95b551201fff682fc1661245", + "sha256:64d34ab766fa056df49013bb6e79921a0265204c071984e75a09cbceacbbdd5d", + "sha256:670bb4683ad1e48b0ecb06f0cfe2178dcf74ff27921cdf1606e527d2617a81ee", + "sha256:68792151e174a4aa9e9fc1b4e653e65a354a2fa0fed169f7b3d09902ad2cb6f1", + "sha256:701daea9ffe9d26f97b52f1d157e0d4121644f0fcf80b443248434958fd03dc3", + "sha256:7d45fc99d64af9aaf7e308054a0067fdcd87ffe974f2442312372dfa66e1001d", + "sha256:80b1fab4deb08a8292d15e43a6edccdffa5377a36a4597bb545b93e79c5ff0a5", + "sha256:82dffb306dd20bd5268fd6379bc4bfe75242a9c2b79fec58e1041fbbdb1f7914", + "sha256:8c7f51861d73e8b9ddcb9916ae7ac39fb52761d9ea0df41128e81e2ba42886cd", + "sha256:950ce33857841f9a337ce07ddf46bc84e1c4946d2a3bba18f8280297157a3fd1", + "sha256:976cae77ba6a49d80f461fd8bba183ff7ba79f44aa5cfa82f1346b5626542f8e", + "sha256:9f6f0fd68d73257ad6685419478c5aece46432f4bdd8d32c7345f1986496171e", + "sha256:a7cd2251439988b413cb0a985c4ed82b6c6aac382dbaff53ae03c4b23a70e80a", + "sha256:abfb7d4a7cd5cc4e1d1887c43503a7c5dd608eadf8bc615413fc498d3e4645cd", + "sha256:ae150a63564929c675d7f2303008d88426a0add46efd76c3fc797cd71cb1b46f", + "sha256:b0f85904f73161817b80781cc150f8b906d521fa11e3cdabae19a581c3606209", + "sha256:b4a849d10f211389502059c33332e91327bc154acc1845f375a99eca3afa802d", + "sha256:c15582f9055fbc1bfe50266a19771bbbef33dd28c45e78afbe1996fd70966c2a", + "sha256:c230c0d8a322276d6e7b88c3f7ce885f9ed16e0910354510e0bae84d54991143", + "sha256:cc1dde4e50a5fc1336ee0581c1612215bc64ed6d28d2c7c6f25d2fe3e7c3e918", + "sha256:cf135c46099ff3f919d2150a948ce94b9ce545598ef2c6c7bf55dca98a304b52", + "sha256:cfc83c0678b6ba51b0532bea66860617c4cd4251ecf76e9846fa5a9f3454e97e", + "sha256:d2a5ebb48958754d386195fe9e9c5106f11275867051bf017a8059410e9abf1f", + "sha256:d71e69699498b020ea198468e2480a2f1e7433e32a3a99760058c6520e2bea7e", + "sha256:d75ae19d2a3dbb146b6f324031c24f8a3f52ff5d6a9f22f0683694b3afcb16fb", + "sha256:dfe2507b8ef209da71b6fb5f4e597b50c5a34b78d7e857c4f8f3115effaef5fe", + "sha256:e0cfe895a504c060e5d36b287ee696e2fdad02d89e0d895f83037245218a87fe", + "sha256:e79e999e539872e903767c417c897e729e015872040e56b96e67968c3b918b2d", + "sha256:ecbbc51391248116c0a055899e6c3e7ffbb11fb5e2a4cd6f2d0b93272118a209", + "sha256:f4a2b50e2b03d5776e7f21af73e2070e1b5c0d0df255a827e7c632962f8315af" + ], + "markers": "python_version >= '3.7'", + "version": "==1.10.7" + }, "pyhamcrest": { "hashes": [ "sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316", @@ -875,6 +1017,14 @@ "markers": "python_version >= '3.8'", "version": "==1.6.3" }, + "werkzeug": { + "hashes": [ + "sha256:2b8c0e447b4b9dbcc85dd97b6eeb4dcbaf6c8b6c3be0bd654e25553e0a2157d8", + "sha256:effc12dba7f3bd72e605ce49807bbe692bd729c3bb122a3b91747a6ae77df528" + ], + "markers": "python_version >= '3.8'", + "version": "==2.3.7" + }, "xlrd": { "hashes": [ "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd", @@ -925,7 +1075,6 @@ "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301" ], "index": "pypi", - "markers": "python_version >= '3.8'", "version": "==23.9.1" }, "click": { @@ -998,7 +1147,6 @@ "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069" ], "index": "pypi", - "markers": "python_version >= '3.7'", "version": "==7.4.2" }, "pytest-django": { @@ -1007,7 +1155,6 @@ "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2" ], "index": "pypi", - "markers": "python_version >= '3.5'", "version": "==4.5.2" }, "pytest-env": { @@ -1016,7 +1163,6 @@ "sha256:baed9b3b6bae77bd75b9238e0ed1ee6903a42806ae9d6aeffb8754cd5584d4ff" ], "index": "pypi", - "markers": "python_version >= '3.7'", "version": "==0.8.2" }, "tomli": { diff --git a/backend/api/tests/views/cron/__init__.py b/backend/api/tests/views/cron/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/api/tests/views/cron/session.py b/backend/api/tests/views/cron/session.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/api/urls/__init__.py b/backend/api/urls/__init__.py index c917b03..f46e04c 100644 --- a/backend/api/urls/__init__.py +++ b/backend/api/urls/__init__.py @@ -2,12 +2,10 @@ from django.urls import include, path, re_path from rest_framework import status -from .cron import urlpatterns as cron_urlpatterns from .csrf import urlpatterns as csrf_urlpatterns from .session import urlpatterns as session_urlpatterns urlpatterns = [ - path("cron/", include(cron_urlpatterns)), path("csrf/", include(csrf_urlpatterns)), path("session/", include(session_urlpatterns)), re_path( diff --git a/backend/api/urls/cron/__init__.py b/backend/api/urls/cron/__init__.py deleted file mode 100644 index d73459e..0000000 --- a/backend/api/urls/cron/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.urls import path, include - -from .session import urlpatterns as session_urlpatterns - -urlpatterns = [ - path("session/", include(session_urlpatterns)), -] diff --git a/backend/api/urls/cron/session.py b/backend/api/urls/cron/session.py deleted file mode 100644 index 365d176..0000000 --- a/backend/api/urls/cron/session.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.urls import path - -urlpatterns = [ - # path(""), -] diff --git a/backend/api/urls/session.py b/backend/api/urls/session.py index 6a3f37c..41d463c 100644 --- a/backend/api/urls/session.py +++ b/backend/api/urls/session.py @@ -1,13 +1,22 @@ from django.contrib.auth.views import LogoutView from django.urls import path, re_path -from ..views.session import LoginView +from ..views.session import ClearExpiredView, LoginView urlpatterns = [ re_path( - r"^(?Pteacher|dependent_student|independent_student)/login/$", + r"^(?Pteacher|dependent-student|independent-student)/login/$", LoginView.as_view(), name="login", ), - path("logout/", LogoutView.as_view(), name="logout"), + path( + "logout/", + LogoutView.as_view(), + name="logout", + ), + path( + "clear-expired/", + ClearExpiredView.as_view(), + name="clear-expired-sessions", + ), ] diff --git a/backend/api/views/cron/__init__.py b/backend/api/views/cron/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/api/views/cron/session.py b/backend/api/views/cron/session.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/api/views/session.py b/backend/api/views/session.py index 300f8f8..6292d35 100644 --- a/backend/api/views/session.py +++ b/backend/api/views/session.py @@ -1,9 +1,14 @@ -import typing as t -from enum import Enum +import logging from functools import cached_property +from codeforlife.mixins import CronMixin +from codeforlife.user.models import User from common.models import UserSession from django.contrib.auth.views import LoginView as _LoginView +from django.contrib.sessions.models import Session, SessionManager +from django.core.management import call_command +from rest_framework.response import Response +from rest_framework.views import APIView from ..forms import ( BaseAuthForm, @@ -14,29 +19,22 @@ ) -# TODO: move to codeforlife package -class UserType(str, Enum): - TEACHER = "teacher" - DEP_STUDENT = "dependent_student" - INDEP_STUDENT = "independent_student" - - # TODO: add 2FA logic class LoginView(_LoginView): @cached_property - def user_type(self) -> UserType: - return UserType(self.kwargs["user_type"]) + def user_type(self): + return User.Type(self.kwargs["user_type"]) def get_form_class(self): - if self.user_type == UserType.INDEP_STUDENT: + if self.user_type == User.Type.INDEP_STUDENT: return CredentialsForm - elif self.user_type == UserType.DEP_STUDENT: + elif self.user_type == User.Type.DEP_STUDENT: if "user_id" in self.request.POST: return DependentStudentUserIdCredentialsForm return DependentStudentUsernameCredentialsForm - else: # user_type == UserType.TEACHER + else: # user_type == User.Type.TEACHER if False: # TODO: add 2fa logic. return OneTimePasswordForm return CredentialsForm @@ -47,7 +45,7 @@ def form_valid(self, form: BaseAuthForm): # TODO: use google analytics user = form.get_user() user_session = {"user": user} - if self.user_type == UserType.DEP_STUDENT: + if self.user_type == User.Type.DEP_STUDENT: user_session["class_field"] = user.new_student.class_field user_session["login_type"] = ( "direct" if "user_id" in self.request.POST else "classform" @@ -55,3 +53,23 @@ def form_valid(self, form: BaseAuthForm): UserSession.objects.create(**user_session) return response + + +class ClearExpiredView(CronMixin, APIView): + def get(self, request): + # objects is missing type SessionManager + session_objects: SessionManager = Session.objects + + before_session_count = session_objects.all().count() + logging.info(f"Session count before clearance: {before_session_count}") + + # Clears expired sessions. + # https://docs.djangoproject.com/en/3.2/ref/django-admin/#clearsessions + call_command("clearsessions") + + after_session_count = session_objects.all().count() + logging.info(f"Session count after clearance: {after_session_count}") + session_clearance_count = before_session_count - after_session_count + logging.info(f"Session clearance count: {session_clearance_count}") + + return Response() From 5bd781ac57895b04743ff567f8bbd1f743191fc7 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Thu, 14 Sep 2023 15:46:00 +0100 Subject: [PATCH 19/58] tests and remove user import --- .github/workflows/main.yaml | 6 +++--- backend/Pipfile | 2 +- backend/Pipfile.lock | 4 ++-- backend/api/forms.py | 4 ++-- backend/api/tests/views/session.py | 9 --------- backend/api/tests/views/test_session.py | 12 ++++++++++++ backend/api/views/session.py | 20 ++++++++++++++------ 7 files changed, 34 insertions(+), 23 deletions(-) delete mode 100644 backend/api/tests/views/session.py create mode 100644 backend/api/tests/views/test_session.py diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index d905a17..3bc3651 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -42,9 +42,9 @@ jobs: # run: pipenv run python manage.py makemigrations --check --dry-run # TODO: assert code coverage target. - # - name: Test Code Units - # working-directory: ./backend - # run: pipenv run pytest + - name: Test Code Units + working-directory: ./backend + run: pipenv run pytest build-and-deploy: runs-on: ubuntu-latest diff --git a/backend/Pipfile b/backend/Pipfile index a172611..b55d7bf 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -codeforlife = {ref = "v0.6.6", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} +codeforlife = {ref = "v0.6.7", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} django = "==3.2.20" djangorestframework = "==3.13.1" # https://pypi.org/user/codeforlife/ diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index ca7263f..f963365 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e75cd8c76bd367b49981ad343a20a6ed661ece7cef252ddfbcf257e7ca6437d0" + "sha256": "d1fae618ac43cb52ec2173b50ad6bb0d87a2883c7b73d9e02faacb3f3b66641e" }, "pipfile-spec": 6, "requires": { @@ -155,7 +155,7 @@ }, "codeforlife": { "git": "https://github.com/ocadotechnology/codeforlife-package-python.git", - "ref": "a063609d36ae2d0d549fdb1ad8f6b21cafedf543" + "ref": "0c81bc9d811f6f7d0f01d868ac8a3289ff2c94e8" }, "codeforlife-portal": { "hashes": [ diff --git a/backend/api/forms.py b/backend/api/forms.py index c7d0971..e7e8d09 100644 --- a/backend/api/forms.py +++ b/backend/api/forms.py @@ -10,8 +10,8 @@ class BaseAuthForm(forms.Form): - def __init__(self, request: HttpRequest = None, *args, **kwargs): - self.request = request # Should this be optional? + def __init__(self, request: HttpRequest, *args, **kwargs): + self.request = request self.user: t.Optional[AbstractBaseUser] = None def clean(self, **kwargs): diff --git a/backend/api/tests/views/session.py b/backend/api/tests/views/session.py deleted file mode 100644 index 8c7e4c5..0000000 --- a/backend/api/tests/views/session.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.test import TestCase - - -class LoginViewTests(TestCase): - fixtures = [] - - def test_otp(self): - # TODO: implement and test otp login - pass diff --git a/backend/api/tests/views/test_session.py b/backend/api/tests/views/test_session.py new file mode 100644 index 0000000..ceee7bf --- /dev/null +++ b/backend/api/tests/views/test_session.py @@ -0,0 +1,12 @@ +from unittest.mock import Mock, patch + +from codeforlife.tests import CronTestCase +from django.urls import reverse + + +class TestClearExpiredView(CronTestCase): + @patch("django.core.management.call_command") + def test_clear_expired_view(self, call_command: Mock): + self.client.get(reverse("clear-expired-sessions")) + + call_command.assert_called_once_with("clearsessions") diff --git a/backend/api/views/session.py b/backend/api/views/session.py index 6292d35..55626d4 100644 --- a/backend/api/views/session.py +++ b/backend/api/views/session.py @@ -1,8 +1,9 @@ import logging +from enum import Enum from functools import cached_property +# from codeforlife.user.models import User from codeforlife.mixins import CronMixin -from codeforlife.user.models import User from common.models import UserSession from django.contrib.auth.views import LoginView as _LoginView from django.contrib.sessions.models import Session, SessionManager @@ -19,22 +20,29 @@ ) +# TODO: use User.Type from cfl package +class UserType(str, Enum): + TEACHER = "teacher" + DEP_STUDENT = "dependent-student" + INDEP_STUDENT = "independent-student" + + # TODO: add 2FA logic class LoginView(_LoginView): @cached_property def user_type(self): - return User.Type(self.kwargs["user_type"]) + return UserType(self.kwargs["user_type"]) def get_form_class(self): - if self.user_type == User.Type.INDEP_STUDENT: + if self.user_type == UserType.INDEP_STUDENT: return CredentialsForm - elif self.user_type == User.Type.DEP_STUDENT: + elif self.user_type == UserType.DEP_STUDENT: if "user_id" in self.request.POST: return DependentStudentUserIdCredentialsForm return DependentStudentUsernameCredentialsForm - else: # user_type == User.Type.TEACHER + else: # user_type == UserType.TEACHER if False: # TODO: add 2fa logic. return OneTimePasswordForm return CredentialsForm @@ -45,7 +53,7 @@ def form_valid(self, form: BaseAuthForm): # TODO: use google analytics user = form.get_user() user_session = {"user": user} - if self.user_type == User.Type.DEP_STUDENT: + if self.user_type == UserType.DEP_STUDENT: user_session["class_field"] = user.new_student.class_field user_session["login_type"] = ( "direct" if "user_id" in self.request.POST else "classform" From 393eb70f931a5320a3f317b9d64693370585b9b4 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Thu, 14 Sep 2023 16:52:15 +0100 Subject: [PATCH 20/58] session config --- backend/service/settings.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/backend/service/settings.py b/backend/service/settings.py index cf0e87c..6e965ab 100644 --- a/backend/service/settings.py +++ b/backend/service/settings.py @@ -132,7 +132,7 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" -# Authentication backend classes +# Authentication backends # https://docs.djangoproject.com/en/4.2/ref/settings/#authentication-backends AUTHENTICATION_BACKENDS = [ @@ -141,6 +141,15 @@ "api.auth_backends.UsernameAndPasswordAndClassIdBackend", ] +# Sessions +# https://docs.djangoproject.com/en/3.2/topics/http/sessions/ + +SESSION_COOKIE_AGE = 60 * 60 +SESSION_SAVE_EVERY_REQUEST = True +SESSION_EXPIRE_AT_BROWSER_CLOSE = True +SESSION_COOKIE_SECURE = True +SESSION_COOKIE_SAMESITE = "None" +SESSION_COOKIE_DOMAIN = "codeforlife.education" # ------------------------------------------------------------------------------ # Custom From 14a30e2afe5a49badc41001a2ae449203401ace9 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Fri, 15 Sep 2023 10:58:34 +0100 Subject: [PATCH 21/58] quick save --- backend/api/forms.py | 34 ++++++++++++++-------------- backend/api/urls/session.py | 6 ++--- backend/api/views/session.py | 44 ++++++++++++------------------------ backend/service/settings.py | 7 ++++++ 4 files changed, 41 insertions(+), 50 deletions(-) diff --git a/backend/api/forms.py b/backend/api/forms.py index e7e8d09..854f862 100644 --- a/backend/api/forms.py +++ b/backend/api/forms.py @@ -33,7 +33,21 @@ def get_invalid_login_error_message(self) -> str: raise NotImplementedError() -class CredentialsForm(BaseAuthForm): +class OtpAuthForm(BaseAuthForm): + otp = forms.CharField( + validators=[ + RegexValidator(r"^[0-9]{6}$", "Must be 6 digits"), + ], + ) + + def clean(self): + # TODO: implement 2FA flow + return super().clean( + otp=self.cleaned_data["otp"], + ) + + +class EmailAuthForm(BaseAuthForm): email = forms.EmailField() # TODO: use regex validator password = forms.CharField(strip=False) @@ -51,21 +65,7 @@ def get_invalid_login_error_message(self): ) -class OneTimePasswordForm(BaseAuthForm): - otp = forms.CharField( - validators=[ - RegexValidator(r"^[0-9]{6}$", "Must be 6 digits"), - ], - ) - - def clean(self): - # TODO: implement 2FA flow - return super().clean( - otp=self.cleaned_data["otp"], - ) - - -class DependentStudentUsernameCredentialsForm(BaseAuthForm): +class UsernameAuthForm(BaseAuthForm): username = UsernameField() # TODO: use regex validator password = forms.CharField(strip=False) @@ -92,7 +92,7 @@ def clean(self): return self.cleaned_data -class DependentStudentUserIdCredentialsForm(BaseAuthForm): +class UserIdAuthForm(BaseAuthForm): user_id = forms.IntegerField(min_value=1) login_id = forms.CharField(min_length=32, max_length=32) diff --git a/backend/api/urls/session.py b/backend/api/urls/session.py index 41d463c..fef1bf8 100644 --- a/backend/api/urls/session.py +++ b/backend/api/urls/session.py @@ -1,11 +1,11 @@ from django.contrib.auth.views import LogoutView -from django.urls import path, re_path +from django.urls import path from ..views.session import ClearExpiredView, LoginView urlpatterns = [ - re_path( - r"^(?Pteacher|dependent-student|independent-student)/login/$", + path( + "login/", LoginView.as_view(), name="login", ), diff --git a/backend/api/views/session.py b/backend/api/views/session.py index 55626d4..cec5fff 100644 --- a/backend/api/views/session.py +++ b/backend/api/views/session.py @@ -1,6 +1,4 @@ import logging -from enum import Enum -from functools import cached_property # from codeforlife.user.models import User from codeforlife.mixins import CronMixin @@ -13,39 +11,25 @@ from ..forms import ( BaseAuthForm, - CredentialsForm, - DependentStudentUserIdCredentialsForm, - DependentStudentUsernameCredentialsForm, - OneTimePasswordForm, + EmailAuthForm, + OtpAuthForm, + UserIdAuthForm, + UsernameAuthForm, ) -# TODO: use User.Type from cfl package -class UserType(str, Enum): - TEACHER = "teacher" - DEP_STUDENT = "dependent-student" - INDEP_STUDENT = "independent-student" - - # TODO: add 2FA logic class LoginView(_LoginView): - @cached_property - def user_type(self): - return UserType(self.kwargs["user_type"]) - def get_form_class(self): - if self.user_type == UserType.INDEP_STUDENT: - return CredentialsForm - - elif self.user_type == UserType.DEP_STUDENT: - if "user_id" in self.request.POST: - return DependentStudentUserIdCredentialsForm - return DependentStudentUsernameCredentialsForm - - else: # user_type == UserType.TEACHER - if False: # TODO: add 2fa logic. - return OneTimePasswordForm - return CredentialsForm + if "email" in self.request.POST: + return EmailAuthForm + elif "username" in self.request.POST: + return UsernameAuthForm + elif "user_id" in self.request.POST: + return UserIdAuthForm + elif "otp" in self.request.POST: # TODO: add 2fa logic. + return OtpAuthForm + raise Exception() # TODO: handle this def form_valid(self, form: BaseAuthForm): response = super().form_valid(form) @@ -53,7 +37,7 @@ def form_valid(self, form: BaseAuthForm): # TODO: use google analytics user = form.get_user() user_session = {"user": user} - if self.user_type == UserType.DEP_STUDENT: + if self.get_form_class() in [UsernameAuthForm, UserIdAuthForm]: user_session["class_field"] = user.new_student.class_field user_session["login_type"] = ( "direct" if "user_id" in self.request.POST else "classform" diff --git a/backend/service/settings.py b/backend/service/settings.py index 6e965ab..7f668b0 100644 --- a/backend/service/settings.py +++ b/backend/service/settings.py @@ -151,6 +151,13 @@ SESSION_COOKIE_SAMESITE = "None" SESSION_COOKIE_DOMAIN = "codeforlife.education" +# CSRF +# https://docs.djangoproject.com/en/3.2/ref/csrf/ + +CSRF_COOKIE_NAME = "sso_csrftoken" +CSRF_COOKIE_SAMESITE = "None" +CSRF_COOKIE_SECURE = True + # ------------------------------------------------------------------------------ # Custom # ------------------------------------------------------------------------------ From 681750f90e6670a497d99609caf2e9c570fa7357 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Fri, 15 Sep 2023 11:37:36 +0100 Subject: [PATCH 22/58] remove logout endpoint --- backend/api/urls/session.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/backend/api/urls/session.py b/backend/api/urls/session.py index fef1bf8..e893400 100644 --- a/backend/api/urls/session.py +++ b/backend/api/urls/session.py @@ -1,4 +1,3 @@ -from django.contrib.auth.views import LogoutView from django.urls import path from ..views.session import ClearExpiredView, LoginView @@ -9,11 +8,6 @@ LoginView.as_view(), name="login", ), - path( - "logout/", - LogoutView.as_view(), - name="logout", - ), path( "clear-expired/", ClearExpiredView.as_view(), From 6ed3e50d6bda9cf596ad17965d70f489737563fa Mon Sep 17 00:00:00 2001 From: SKairinos Date: Fri, 15 Sep 2023 15:34:33 +0100 Subject: [PATCH 23/58] login working --- backend/Pipfile | 1 + backend/Pipfile.lock | 122 +++++++++--------- .../api/auth_backends/email_and_password.py | 4 +- .../api/auth_backends/user_id_and_login_id.py | 4 +- .../username_and_password_and_class_id.py | 4 +- backend/api/forms.py | 53 ++------ backend/api/views/session.py | 11 +- backend/service/settings.py | 9 ++ 8 files changed, 96 insertions(+), 112 deletions(-) diff --git a/backend/Pipfile b/backend/Pipfile index b55d7bf..f929e84 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -7,6 +7,7 @@ name = "pypi" codeforlife = {ref = "v0.6.7", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} django = "==3.2.20" djangorestframework = "==3.13.1" +django-cors-headers = "==4.1.0" # https://pypi.org/user/codeforlife/ cfl-common = "==6.36.0" # TODO: remove codeforlife-portal = "==6.36.0" # TODO: remove diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index f963365..1b7aee8 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d1fae618ac43cb52ec2173b50ad6bb0d87a2883c7b73d9e02faacb3f3b66641e" + "sha256": "5f0e61b16945f346ca4f955a671e7f9c783c1fdd5bb33e7c2a02a8e538e30f7c" }, "pipfile-spec": 6, "requires": { @@ -196,6 +196,14 @@ ], "version": "==2.0.0" }, + "django-cors-headers": { + "hashes": [ + "sha256:36a8d7a6dee6a85f872fe5916cc878a36d0812043866355438dfeda0b20b6b78", + "sha256:88a4bfae24b6404dd0e0640203cb27704a2a57fd546a429e5d821dfa53dd1acf" + ], + "index": "pypi", + "version": "==4.1.0" + }, "django-countries": { "hashes": [ "sha256:5a4ee958f77810bcc38ae96605e47d76a707e81f53cf2938743ef45faafd2fce", @@ -657,65 +665,63 @@ }, "pillow": { "hashes": [ - "sha256:00e65f5e822decd501e374b0650146063fbb30a7264b4d2744bdd7b913e0cab5", - "sha256:040586f7d37b34547153fa383f7f9aed68b738992380ac911447bb78f2abe530", - "sha256:0b6eb5502f45a60a3f411c63187db83a3d3107887ad0d036c13ce836f8a36f1d", - "sha256:1ce91b6ec08d866b14413d3f0bbdea7e24dfdc8e59f562bb77bc3fe60b6144ca", - "sha256:1f62406a884ae75fb2f818694469519fb685cc7eaff05d3451a9ebe55c646891", - "sha256:22c10cc517668d44b211717fd9775799ccec4124b9a7f7b3635fc5386e584992", - "sha256:3400aae60685b06bb96f99a21e1ada7bc7a413d5f49bce739828ecd9391bb8f7", - "sha256:349930d6e9c685c089284b013478d6f76e3a534e36ddfa912cde493f235372f3", - "sha256:368ab3dfb5f49e312231b6f27b8820c823652b7cd29cfbd34090565a015e99ba", - "sha256:38250a349b6b390ee6047a62c086d3817ac69022c127f8a5dc058c31ccef17f3", - "sha256:3a684105f7c32488f7153905a4e3015a3b6c7182e106fe3c37fbb5ef3e6994c3", - "sha256:3a82c40d706d9aa9734289740ce26460a11aeec2d9c79b7af87bb35f0073c12f", - "sha256:3b08d4cc24f471b2c8ca24ec060abf4bebc6b144cb89cba638c720546b1cf538", - "sha256:3ed64f9ca2f0a95411e88a4efbd7a29e5ce2cea36072c53dd9d26d9c76f753b3", - "sha256:3f07ea8d2f827d7d2a49ecf1639ec02d75ffd1b88dcc5b3a61bbb37a8759ad8d", - "sha256:520f2a520dc040512699f20fa1c363eed506e94248d71f85412b625026f6142c", - "sha256:5c6e3df6bdd396749bafd45314871b3d0af81ff935b2d188385e970052091017", - "sha256:608bfdee0d57cf297d32bcbb3c728dc1da0907519d1784962c5f0c68bb93e5a3", - "sha256:685ac03cc4ed5ebc15ad5c23bc555d68a87777586d970c2c3e216619a5476223", - "sha256:76de421f9c326da8f43d690110f0e79fe3ad1e54be811545d7d91898b4c8493e", - "sha256:76edb0a1fa2b4745fb0c99fb9fb98f8b180a1bbceb8be49b087e0b21867e77d3", - "sha256:7be600823e4c8631b74e4a0d38384c73f680e6105a7d3c6824fcf226c178c7e6", - "sha256:81ff539a12457809666fef6624684c008e00ff6bf455b4b89fd00a140eecd640", - "sha256:88af2003543cc40c80f6fca01411892ec52b11021b3dc22ec3bc9d5afd1c5334", - "sha256:8c11160913e3dd06c8ffdb5f233a4f254cb449f4dfc0f8f4549eda9e542c93d1", - "sha256:8f8182b523b2289f7c415f589118228d30ac8c355baa2f3194ced084dac2dbba", - "sha256:9211e7ad69d7c9401cfc0e23d49b69ca65ddd898976d660a2fa5904e3d7a9baa", - "sha256:92be919bbc9f7d09f7ae343c38f5bb21c973d2576c1d45600fce4b74bafa7ac0", - "sha256:9c82b5b3e043c7af0d95792d0d20ccf68f61a1fec6b3530e718b688422727396", - "sha256:9f7c16705f44e0504a3a2a14197c1f0b32a95731d251777dcb060aa83022cb2d", - "sha256:9fb218c8a12e51d7ead2a7c9e101a04982237d4855716af2e9499306728fb485", - "sha256:a74ba0c356aaa3bb8e3eb79606a87669e7ec6444be352870623025d75a14a2bf", - "sha256:b4f69b3700201b80bb82c3a97d5e9254084f6dd5fb5b16fc1a7b974260f89f43", - "sha256:bc2ec7c7b5d66b8ec9ce9f720dbb5fa4bace0f545acd34870eff4a369b44bf37", - "sha256:c189af0545965fa8d3b9613cfdb0cd37f9d71349e0f7750e1fd704648d475ed2", - "sha256:c1fbe7621c167ecaa38ad29643d77a9ce7311583761abf7836e1510c580bf3dd", - "sha256:c7cf14a27b0d6adfaebb3ae4153f1e516df54e47e42dcc073d7b3d76111a8d86", - "sha256:c9f72a021fbb792ce98306ffb0c348b3c9cb967dce0f12a49aa4c3d3fdefa967", - "sha256:cd25d2a9d2b36fcb318882481367956d2cf91329f6892fe5d385c346c0649629", - "sha256:ce543ed15570eedbb85df19b0a1a7314a9c8141a36ce089c0a894adbfccb4568", - "sha256:ce7b031a6fc11365970e6a5686d7ba8c63e4c1cf1ea143811acbb524295eabed", - "sha256:d35e3c8d9b1268cbf5d3670285feb3528f6680420eafe35cccc686b73c1e330f", - "sha256:d50b6aec14bc737742ca96e85d6d0a5f9bfbded018264b3b70ff9d8c33485551", - "sha256:d5d0dae4cfd56969d23d94dc8e89fb6a217be461c69090768227beb8ed28c0a3", - "sha256:d5db32e2a6ccbb3d34d87c87b432959e0db29755727afb37290e10f6e8e62614", - "sha256:d72e2ecc68a942e8cf9739619b7f408cc7b272b279b56b2c83c6123fcfa5cdff", - "sha256:d737a602fbd82afd892ca746392401b634e278cb65d55c4b7a8f48e9ef8d008d", - "sha256:d80cf684b541685fccdd84c485b31ce73fc5c9b5d7523bf1394ce134a60c6883", - "sha256:db24668940f82321e746773a4bc617bfac06ec831e5c88b643f91f122a785684", - "sha256:dbc02381779d412145331789b40cc7b11fdf449e5d94f6bc0b080db0a56ea3f0", - "sha256:dffe31a7f47b603318c609f378ebcd57f1554a3a6a8effbc59c3c69f804296de", - "sha256:edf4392b77bdc81f36e92d3a07a5cd072f90253197f4a52a55a8cec48a12483b", - "sha256:efe8c0681042536e0d06c11f48cebe759707c9e9abf880ee213541c5b46c5bf3", - "sha256:f31f9fdbfecb042d046f9d91270a0ba28368a723302786c0009ee9b9f1f60199", - "sha256:f88a0b92277de8e3ca715a0d79d68dc82807457dae3ab8699c758f07c20b3c51", - "sha256:faaf07ea35355b01a35cb442dd950d8f1bb5b040a7787791a535de13db15ed90" + "sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff", + "sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f", + "sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21", + "sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635", + "sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a", + "sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f", + "sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1", + "sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d", + "sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db", + "sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849", + "sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7", + "sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876", + "sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3", + "sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317", + "sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91", + "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d", + "sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b", + "sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd", + "sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed", + "sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500", + "sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7", + "sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a", + "sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a", + "sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0", + "sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf", + "sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f", + "sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1", + "sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088", + "sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971", + "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a", + "sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205", + "sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54", + "sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08", + "sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21", + "sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d", + "sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08", + "sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e", + "sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf", + "sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b", + "sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145", + "sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2", + "sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d", + "sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d", + "sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf", + "sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad", + "sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d", + "sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1", + "sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4", + "sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2", + "sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19", + "sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37", + "sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4", + "sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68", + "sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1" ], "markers": "python_version >= '3.8'", - "version": "==10.0.0" + "version": "==10.0.1" }, "pyasn1": { "hashes": [ diff --git a/backend/api/auth_backends/email_and_password.py b/backend/api/auth_backends/email_and_password.py index 374ad60..d2c5311 100644 --- a/backend/api/auth_backends/email_and_password.py +++ b/backend/api/auth_backends/email_and_password.py @@ -3,7 +3,7 @@ from django.contrib.auth import get_user_model from django.contrib.auth.backends import BaseBackend from django.contrib.auth.base_user import AbstractBaseUser -from django.http.request import HttpRequest +from django.core.handlers.wsgi import WSGIRequest User = get_user_model() @@ -11,7 +11,7 @@ class EmailAndPasswordBackend(BaseBackend): def authenticate( self, - request: HttpRequest, + request: WSGIRequest, email: t.Optional[str] = None, password: t.Optional[str] = None, **kwargs diff --git a/backend/api/auth_backends/user_id_and_login_id.py b/backend/api/auth_backends/user_id_and_login_id.py index 0a5a451..9048bf0 100644 --- a/backend/api/auth_backends/user_id_and_login_id.py +++ b/backend/api/auth_backends/user_id_and_login_id.py @@ -5,7 +5,7 @@ from django.contrib.auth import get_user_model from django.contrib.auth.backends import BaseBackend from django.contrib.auth.base_user import AbstractBaseUser -from django.http.request import HttpRequest +from django.core.handlers.wsgi import WSGIRequest User = get_user_model() @@ -13,7 +13,7 @@ class UserIdAndLoginIdBackend(BaseBackend): def authenticate( self, - request: HttpRequest, + request: WSGIRequest, user_id: t.Optional[int] = None, login_id: t.Optional[str] = None, **kwargs diff --git a/backend/api/auth_backends/username_and_password_and_class_id.py b/backend/api/auth_backends/username_and_password_and_class_id.py index d56e63c..0162f6c 100644 --- a/backend/api/auth_backends/username_and_password_and_class_id.py +++ b/backend/api/auth_backends/username_and_password_and_class_id.py @@ -3,7 +3,7 @@ from django.contrib.auth import get_user_model from django.contrib.auth.backends import BaseBackend from django.contrib.auth.base_user import AbstractBaseUser -from django.http.request import HttpRequest +from django.core.handlers.wsgi import WSGIRequest User = get_user_model() @@ -11,7 +11,7 @@ class UsernameAndPasswordAndClassIdBackend(BaseBackend): def authenticate( self, - request: HttpRequest, + request: WSGIRequest, username: t.Optional[str] = None, password: t.Optional[str] = None, class_id: t.Optional[str] = None, diff --git a/backend/api/forms.py b/backend/api/forms.py index 854f862..2d82ac5 100644 --- a/backend/api/forms.py +++ b/backend/api/forms.py @@ -1,21 +1,23 @@ -import typing as t - from django import forms from django.contrib.auth import authenticate from django.contrib.auth.base_user import AbstractBaseUser from django.contrib.auth.forms import UsernameField -from django.core.validators import RegexValidator from django.core.exceptions import ValidationError -from django.http import HttpRequest +from django.core.handlers.wsgi import WSGIRequest +from django.core.validators import RegexValidator class BaseAuthForm(forms.Form): - def __init__(self, request: HttpRequest, *args, **kwargs): + def __init__(self, request: WSGIRequest, *args, **kwargs): self.request = request - self.user: t.Optional[AbstractBaseUser] = None + self.user: AbstractBaseUser = None + super().__init__(*args, **kwargs) - def clean(self, **kwargs): - self.user = authenticate(self.request, **kwargs) + def clean(self): + self.user = authenticate( + self.request, + **{key: self.cleaned_data[key] for key in self.fields.keys()} + ) if self.user is None: raise ValidationError( self.get_invalid_login_error_message(), @@ -25,10 +27,6 @@ def clean(self, **kwargs): return self.cleaned_data - # Required by Django's LoginView - def get_user(self): - return self.user - def get_invalid_login_error_message(self) -> str: raise NotImplementedError() @@ -40,24 +38,12 @@ class OtpAuthForm(BaseAuthForm): ], ) - def clean(self): - # TODO: implement 2FA flow - return super().clean( - otp=self.cleaned_data["otp"], - ) - class EmailAuthForm(BaseAuthForm): email = forms.EmailField() # TODO: use regex validator password = forms.CharField(strip=False) - def clean(self): - return super().clean( - email=self.cleaned_data["email"], - password=self.cleaned_data["password"], - ) - def get_invalid_login_error_message(self): return ( "Please enter a correct username and password. Note that both" @@ -78,26 +64,7 @@ class UsernameAuthForm(BaseAuthForm): ], ) - def clean(self): - username = self.cleaned_data.get("username") - password = self.cleaned_data.get("password") - - if username is not None and password: - return super().clean( - username=username, - password=password, - class_id=self.cleaned_data["class_id"], - ) - - return self.cleaned_data - class UserIdAuthForm(BaseAuthForm): user_id = forms.IntegerField(min_value=1) login_id = forms.CharField(min_length=32, max_length=32) - - def clean(self): - return super().clean( - userId=self.cleaned_data["user_id"], - login_id=self.cleaned_data["login_id"], - ) diff --git a/backend/api/views/session.py b/backend/api/views/session.py index cec5fff..4fb9bbf 100644 --- a/backend/api/views/session.py +++ b/backend/api/views/session.py @@ -3,9 +3,11 @@ # from codeforlife.user.models import User from codeforlife.mixins import CronMixin from common.models import UserSession +from django.contrib.auth import login from django.contrib.auth.views import LoginView as _LoginView from django.contrib.sessions.models import Session, SessionManager from django.core.management import call_command +from django.http import HttpResponse from rest_framework.response import Response from rest_framework.views import APIView @@ -32,19 +34,18 @@ def get_form_class(self): raise Exception() # TODO: handle this def form_valid(self, form: BaseAuthForm): - response = super().form_valid(form) + login(self.request, form.user) # TODO: use google analytics - user = form.get_user() - user_session = {"user": user} + user_session = {"user": form.user} if self.get_form_class() in [UsernameAuthForm, UserIdAuthForm]: - user_session["class_field"] = user.new_student.class_field + user_session["class_field"] = form.user.new_student.class_field user_session["login_type"] = ( "direct" if "user_id" in self.request.POST else "classform" ) UserSession.objects.create(**user_session) - return response + return HttpResponse() class ClearExpiredView(CronMixin, APIView): diff --git a/backend/service/settings.py b/backend/service/settings.py index 7f668b0..2789b26 100644 --- a/backend/service/settings.py +++ b/backend/service/settings.py @@ -43,9 +43,11 @@ "game", # TODO: remove this "common", # TODO: remove this "portal", # TODO: remove this + "corsheaders", ] MIDDLEWARE = [ + "corsheaders.middleware.CorsMiddleware", "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", @@ -158,6 +160,13 @@ CSRF_COOKIE_SAMESITE = "None" CSRF_COOKIE_SECURE = True +# CORS +# https://pypi.org/project/django-cors-headers/ + +CORS_ALLOW_ALL_ORIGINS = DEBUG +CORS_ALLOW_CREDENTIALS = True +CORS_ALLOWED_ORIGINS = [] + # ------------------------------------------------------------------------------ # Custom # ------------------------------------------------------------------------------ From 3201d5f064482b63908d9532868f1a798d3b1993 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Fri, 15 Sep 2023 15:39:29 +0100 Subject: [PATCH 24/58] set session cookie domain --- backend/service/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/service/settings.py b/backend/service/settings.py index 2789b26..e1120ed 100644 --- a/backend/service/settings.py +++ b/backend/service/settings.py @@ -151,7 +151,7 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = True SESSION_COOKIE_SECURE = True SESSION_COOKIE_SAMESITE = "None" -SESSION_COOKIE_DOMAIN = "codeforlife.education" +SESSION_COOKIE_DOMAIN = "localhost" if DEBUG else "codeforlife.education" # CSRF # https://docs.djangoproject.com/en/3.2/ref/csrf/ From 0d5029f6ca10f224dc46ef3f60ae14575feaca3d Mon Sep 17 00:00:00 2001 From: SKairinos Date: Fri, 15 Sep 2023 15:58:16 +0100 Subject: [PATCH 25/58] return invalid form errors --- backend/api/views/session.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/api/views/session.py b/backend/api/views/session.py index 4fb9bbf..a43b047 100644 --- a/backend/api/views/session.py +++ b/backend/api/views/session.py @@ -7,7 +7,8 @@ from django.contrib.auth.views import LoginView as _LoginView from django.contrib.sessions.models import Session, SessionManager from django.core.management import call_command -from django.http import HttpResponse +from django.http import HttpResponse, JsonResponse +from rest_framework import status from rest_framework.response import Response from rest_framework.views import APIView @@ -47,6 +48,9 @@ def form_valid(self, form: BaseAuthForm): return HttpResponse() + def form_invalid(self, form: BaseAuthForm): + return JsonResponse(form.errors, status=status.HTTP_400_BAD_REQUEST) + class ClearExpiredView(CronMixin, APIView): def get(self, request): From d740dbd21e23335eacf2b0a29129364f9b46e4e0 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Fri, 15 Sep 2023 16:37:32 +0100 Subject: [PATCH 26/58] login middleware --- backend/api/middlewares/__init__.py | 1 + backend/api/middlewares/login.py | 30 +++++++++++++++++++++++++++++ backend/api/views/session.py | 21 +++++++++++++++----- backend/service/settings.py | 1 + 4 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 backend/api/middlewares/__init__.py create mode 100644 backend/api/middlewares/login.py diff --git a/backend/api/middlewares/__init__.py b/backend/api/middlewares/__init__.py new file mode 100644 index 0000000..75ee330 --- /dev/null +++ b/backend/api/middlewares/__init__.py @@ -0,0 +1 @@ +from .login import LoginMiddleware diff --git a/backend/api/middlewares/login.py b/backend/api/middlewares/login.py new file mode 100644 index 0000000..7aed8ae --- /dev/null +++ b/backend/api/middlewares/login.py @@ -0,0 +1,30 @@ +from django.http import JsonResponse +from rest_framework import status + + +class MissingUniqueFormKey(Exception): + pass + + +class TooManyUniqueFormKeys(Exception): + pass + + +class LoginMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + return self.get_response(request) + + def process_exception(self, request, exception): + if isinstance(exception, MissingUniqueFormKey): + return JsonResponse( + {"__all__": ["Missing unique form key."]}, + status=status.HTTP_400_BAD_REQUEST, + ) + elif isinstance(exception, TooManyUniqueFormKeys): + return JsonResponse( + {"__all__": ["Too many unique form keys."]}, + status=status.HTTP_400_BAD_REQUEST, + ) diff --git a/backend/api/views/session.py b/backend/api/views/session.py index a43b047..6cc6eb4 100644 --- a/backend/api/views/session.py +++ b/backend/api/views/session.py @@ -19,20 +19,31 @@ UserIdAuthForm, UsernameAuthForm, ) +from ..middlewares.login import MissingUniqueFormKey, TooManyUniqueFormKeys # TODO: add 2FA logic class LoginView(_LoginView): def get_form_class(self): - if "email" in self.request.POST: + unique_form_keys = [ + key + for key in ["email", "username", "user_id", "otp"] + if key in self.request.POST + ] + if len(unique_form_keys) == 0: + raise MissingUniqueFormKey() + elif len(unique_form_keys) >= 2: + raise TooManyUniqueFormKeys() + + unique_form_key = unique_form_keys[0] + if unique_form_key == "email": return EmailAuthForm - elif "username" in self.request.POST: + elif unique_form_key == "username": return UsernameAuthForm - elif "user_id" in self.request.POST: + elif unique_form_key == "user_id": return UserIdAuthForm - elif "otp" in self.request.POST: # TODO: add 2fa logic. + elif unique_form_key == "otp": # TODO: add 2fa logic. return OtpAuthForm - raise Exception() # TODO: handle this def form_valid(self, form: BaseAuthForm): login(self.request, form.user) diff --git a/backend/service/settings.py b/backend/service/settings.py index e1120ed..89ed883 100644 --- a/backend/service/settings.py +++ b/backend/service/settings.py @@ -55,6 +55,7 @@ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", + "api.middlewares.LoginMiddleware" ] ROOT_URLCONF = "service.urls" From 51d62d7772ee4d033261228a32d0b0851508450a Mon Sep 17 00:00:00 2001 From: SKairinos Date: Fri, 15 Sep 2023 17:14:23 +0100 Subject: [PATCH 27/58] simplify code --- backend/api/middlewares/__init__.py | 1 - backend/api/middlewares/login.py | 30 ----------------------------- backend/api/urls/session.py | 6 +++--- backend/api/views/session.py | 21 +++++--------------- 4 files changed, 8 insertions(+), 50 deletions(-) delete mode 100644 backend/api/middlewares/__init__.py delete mode 100644 backend/api/middlewares/login.py diff --git a/backend/api/middlewares/__init__.py b/backend/api/middlewares/__init__.py deleted file mode 100644 index 75ee330..0000000 --- a/backend/api/middlewares/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .login import LoginMiddleware diff --git a/backend/api/middlewares/login.py b/backend/api/middlewares/login.py deleted file mode 100644 index 7aed8ae..0000000 --- a/backend/api/middlewares/login.py +++ /dev/null @@ -1,30 +0,0 @@ -from django.http import JsonResponse -from rest_framework import status - - -class MissingUniqueFormKey(Exception): - pass - - -class TooManyUniqueFormKeys(Exception): - pass - - -class LoginMiddleware: - def __init__(self, get_response): - self.get_response = get_response - - def __call__(self, request): - return self.get_response(request) - - def process_exception(self, request, exception): - if isinstance(exception, MissingUniqueFormKey): - return JsonResponse( - {"__all__": ["Missing unique form key."]}, - status=status.HTTP_400_BAD_REQUEST, - ) - elif isinstance(exception, TooManyUniqueFormKeys): - return JsonResponse( - {"__all__": ["Too many unique form keys."]}, - status=status.HTTP_400_BAD_REQUEST, - ) diff --git a/backend/api/urls/session.py b/backend/api/urls/session.py index e893400..492770b 100644 --- a/backend/api/urls/session.py +++ b/backend/api/urls/session.py @@ -1,10 +1,10 @@ -from django.urls import path +from django.urls import path, re_path from ..views.session import ClearExpiredView, LoginView urlpatterns = [ - path( - "login/", + re_path( + r"^login/(?P
email|username|user-id|otp)/$", LoginView.as_view(), name="login", ), diff --git a/backend/api/views/session.py b/backend/api/views/session.py index 6cc6eb4..62f7a2d 100644 --- a/backend/api/views/session.py +++ b/backend/api/views/session.py @@ -19,30 +19,19 @@ UserIdAuthForm, UsernameAuthForm, ) -from ..middlewares.login import MissingUniqueFormKey, TooManyUniqueFormKeys # TODO: add 2FA logic class LoginView(_LoginView): def get_form_class(self): - unique_form_keys = [ - key - for key in ["email", "username", "user_id", "otp"] - if key in self.request.POST - ] - if len(unique_form_keys) == 0: - raise MissingUniqueFormKey() - elif len(unique_form_keys) >= 2: - raise TooManyUniqueFormKeys() - - unique_form_key = unique_form_keys[0] - if unique_form_key == "email": + form = self.kwargs["form"] + if form == "email": return EmailAuthForm - elif unique_form_key == "username": + elif form == "username": return UsernameAuthForm - elif unique_form_key == "user_id": + elif form == "user-id": return UserIdAuthForm - elif unique_form_key == "otp": # TODO: add 2fa logic. + elif form == "otp": # TODO: add 2fa logic. return OtpAuthForm def form_valid(self, form: BaseAuthForm): From e68808597c8a2ea15f7ef777fa6c0b0a3e6e9f18 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Fri, 15 Sep 2023 17:15:53 +0100 Subject: [PATCH 28/58] remove login middleware --- backend/service/settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/service/settings.py b/backend/service/settings.py index 89ed883..e1120ed 100644 --- a/backend/service/settings.py +++ b/backend/service/settings.py @@ -55,7 +55,6 @@ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", - "api.middlewares.LoginMiddleware" ] ROOT_URLCONF = "service.urls" From 4210534babf653ca7379f16e04079cefd9afe42b Mon Sep 17 00:00:00 2001 From: SKairinos Date: Fri, 15 Sep 2023 17:17:24 +0100 Subject: [PATCH 29/58] remove extra white spacing --- backend/service/settings.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/backend/service/settings.py b/backend/service/settings.py index e1120ed..18874d9 100644 --- a/backend/service/settings.py +++ b/backend/service/settings.py @@ -77,7 +77,6 @@ WSGI_APPLICATION = "service.wsgi.application" - # Database # https://docs.djangoproject.com/en/3.2/ref/settings/#databases @@ -88,7 +87,6 @@ } } - # Password validation # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators @@ -107,21 +105,15 @@ }, ] - # Internationalization # https://docs.djangoproject.com/en/3.2/topics/i18n/ LANGUAGE_CODE = "en-us" - TIME_ZONE = "UTC" - USE_I18N = True - USE_L10N = True - USE_TZ = True - # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.2/howto/static-files/ From 56ec08ad32c1d6a20033e8a0bf13369854639bca Mon Sep 17 00:00:00 2001 From: SKairinos Date: Mon, 18 Sep 2023 15:42:25 +0100 Subject: [PATCH 30/58] update launch --- .vscode/launch.json | 5 ++++- .vscode/tasks.json | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index ab4afc1..42be01e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,10 @@ "runserver", "localhost:8001" ], - "preLaunchTask": "migrate-db" + "preLaunchTask": "migrate-db", + "env": { + "SERVICE_NAME": "sso" + } }, { "name": "Pytest", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index e2eb22b..a06e253 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,12 +13,15 @@ "label": "migrate-db", "type": "shell", "options": { - "cwd": "${workspaceFolder}/backend" + "cwd": "${workspaceFolder}/backend", + "env": { + "SERVICE_NAME": "sso" + } }, "dependsOn": [ "install-dev-deps" ], - "command": "pipenv run ./manage.py migrate" + "command": "pipenv run python ./manage.py migrate" } ] } \ No newline at end of file From d8a6067f03797b002c7f74dd3382e24e2b6af1f4 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Mon, 18 Sep 2023 15:46:01 +0100 Subject: [PATCH 31/58] use new cfl package --- backend/Pipfile | 2 +- backend/Pipfile.lock | 36 +++++----- backend/api/auth_backends/__init__.py | 5 -- .../api/auth_backends/email_and_password.py | 35 ---------- .../api/auth_backends/user_id_and_login_id.py | 39 ----------- .../username_and_password_and_class_id.py | 39 ----------- .../{views/test_session.py => test_views.py} | 0 backend/api/tests/views/__init__.py | 0 backend/api/urls.py | 23 +++++++ backend/api/urls/__init__.py | 18 ----- backend/api/urls/csrf.py | 8 --- backend/api/urls/session.py | 16 ----- backend/api/{views/session.py => views.py} | 3 +- backend/api/views/__init__.py | 0 backend/api/views/csrf.py | 14 ---- backend/pyproject.toml | 2 +- backend/service/settings.py | 66 +------------------ backend/service/urls.py | 35 +--------- 18 files changed, 49 insertions(+), 292 deletions(-) delete mode 100644 backend/api/auth_backends/__init__.py delete mode 100644 backend/api/auth_backends/email_and_password.py delete mode 100644 backend/api/auth_backends/user_id_and_login_id.py delete mode 100644 backend/api/auth_backends/username_and_password_and_class_id.py rename backend/api/tests/{views/test_session.py => test_views.py} (100%) delete mode 100644 backend/api/tests/views/__init__.py create mode 100644 backend/api/urls.py delete mode 100644 backend/api/urls/__init__.py delete mode 100644 backend/api/urls/csrf.py delete mode 100644 backend/api/urls/session.py rename backend/api/{views/session.py => views.py} (97%) delete mode 100644 backend/api/views/__init__.py delete mode 100644 backend/api/views/csrf.py diff --git a/backend/Pipfile b/backend/Pipfile index f929e84..b9627c7 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -codeforlife = {ref = "v0.6.7", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} +codeforlife = {ref = "v0.7.5", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} django = "==3.2.20" djangorestframework = "==3.13.1" django-cors-headers = "==4.1.0" diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 1b7aee8..a62fa16 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "5f0e61b16945f346ca4f955a671e7f9c783c1fdd5bb33e7c2a02a8e538e30f7c" + "sha256": "c2bb1ce3b680fc3a63e3014b9d97edef2b7a4aad2f84ab2242162a87a709d8d8" }, "pipfile-spec": 6, "requires": { @@ -155,7 +155,7 @@ }, "codeforlife": { "git": "https://github.com/ocadotechnology/codeforlife-package-python.git", - "ref": "0c81bc9d811f6f7d0f01d868ac8a3289ff2c94e8" + "ref": "b9b351dce23870134180473afc2a7633d6bb1c78" }, "codeforlife-portal": { "hashes": [ @@ -345,11 +345,11 @@ }, "google-auth": { "hashes": [ - "sha256:2cec41407bd1e207f5b802638e32bb837df968bb5c05f413d0fa526fac4cf7a7", - "sha256:753a26312e6f1eaeec20bc6f2644a10926697da93446e1f8e24d6d32d45a922a" + "sha256:ce311e2bc58b130fddf316df57c9b3943c2a7b4f6ec31de9663a9333e4064efc", + "sha256:f586b274d3eb7bd932ea424b1c702a30e0393a2e2bc4ca3eae8263ffd8be229f" ], - "markers": "python_version >= '3.7'", - "version": "==2.23.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==2.17.3" }, "greenlet": { "hashes": [ @@ -993,11 +993,11 @@ }, "typing-extensions": { "hashes": [ - "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", - "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2" + "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0", + "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef" ], - "markers": "python_version >= '3.7'", - "version": "==4.7.1" + "markers": "python_version >= '3.8'", + "version": "==4.8.0" }, "tzdata": { "hashes": [ @@ -1009,11 +1009,11 @@ }, "urllib3": { "hashes": [ - "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f", - "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14" + "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11", + "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.26.16" + "markers": "python_version >= '3.7'", + "version": "==2.0.4" }, "websocket-client": { "hashes": [ @@ -1181,11 +1181,11 @@ }, "typing-extensions": { "hashes": [ - "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", - "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2" + "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0", + "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef" ], - "markers": "python_version >= '3.7'", - "version": "==4.7.1" + "markers": "python_version >= '3.8'", + "version": "==4.8.0" } } } diff --git a/backend/api/auth_backends/__init__.py b/backend/api/auth_backends/__init__.py deleted file mode 100644 index 5fef85c..0000000 --- a/backend/api/auth_backends/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .email_and_password import EmailAndPasswordBackend -from .user_id_and_login_id import UserIdAndLoginIdBackend -from .username_and_password_and_class_id import ( - UsernameAndPasswordAndClassIdBackend, -) diff --git a/backend/api/auth_backends/email_and_password.py b/backend/api/auth_backends/email_and_password.py deleted file mode 100644 index d2c5311..0000000 --- a/backend/api/auth_backends/email_and_password.py +++ /dev/null @@ -1,35 +0,0 @@ -import typing as t - -from django.contrib.auth import get_user_model -from django.contrib.auth.backends import BaseBackend -from django.contrib.auth.base_user import AbstractBaseUser -from django.core.handlers.wsgi import WSGIRequest - -User = get_user_model() - - -class EmailAndPasswordBackend(BaseBackend): - def authenticate( - self, - request: WSGIRequest, - email: t.Optional[str] = None, - password: t.Optional[str] = None, - **kwargs - ) -> t.Optional[AbstractBaseUser]: - if email is None or password is None: - return - - try: - user = User.objects.get(email=email) - if getattr(user, "is_active", True) and user.check_password( - password - ): - return user - except User.DoesNotExist: - return - - def get_user(self, user_id: int) -> t.Optional[AbstractBaseUser]: - try: - return User.objects.get(id=user_id) - except User.DoesNotExist: - return diff --git a/backend/api/auth_backends/user_id_and_login_id.py b/backend/api/auth_backends/user_id_and_login_id.py deleted file mode 100644 index 9048bf0..0000000 --- a/backend/api/auth_backends/user_id_and_login_id.py +++ /dev/null @@ -1,39 +0,0 @@ -import typing as t - -from common.helpers.generators import get_hashed_login_id -from common.models import Student -from django.contrib.auth import get_user_model -from django.contrib.auth.backends import BaseBackend -from django.contrib.auth.base_user import AbstractBaseUser -from django.core.handlers.wsgi import WSGIRequest - -User = get_user_model() - - -class UserIdAndLoginIdBackend(BaseBackend): - def authenticate( - self, - request: WSGIRequest, - user_id: t.Optional[int] = None, - login_id: t.Optional[str] = None, - **kwargs - ) -> t.Optional[AbstractBaseUser]: - if user_id is None or login_id is None: - return - - user = self.get_user(user_id) - if user and getattr(user, "is_active", True): - # Check the url against the student's stored hash. - student = Student.objects.get(new_user=user) - if ( - student.login_id - # TODO: refactor this - and get_hashed_login_id(login_id) == student.login_id - ): - return user - - def get_user(self, user_id: int) -> t.Optional[AbstractBaseUser]: - try: - return User.objects.get(id=user_id) - except User.DoesNotExist: - return diff --git a/backend/api/auth_backends/username_and_password_and_class_id.py b/backend/api/auth_backends/username_and_password_and_class_id.py deleted file mode 100644 index 0162f6c..0000000 --- a/backend/api/auth_backends/username_and_password_and_class_id.py +++ /dev/null @@ -1,39 +0,0 @@ -import typing as t - -from django.contrib.auth import get_user_model -from django.contrib.auth.backends import BaseBackend -from django.contrib.auth.base_user import AbstractBaseUser -from django.core.handlers.wsgi import WSGIRequest - -User = get_user_model() - - -class UsernameAndPasswordAndClassIdBackend(BaseBackend): - def authenticate( - self, - request: WSGIRequest, - username: t.Optional[str] = None, - password: t.Optional[str] = None, - class_id: t.Optional[str] = None, - **kwargs - ) -> t.Optional[AbstractBaseUser]: - if username is None or password is None or class_id is None: - return - - try: - user = User.objects.get( - username=username, - new_student__class_field__access_code=class_id, - ) - if getattr(user, "is_active", True) and user.check_password( - password - ): - return user - except User.DoesNotExist: - return - - def get_user(self, user_id: int) -> t.Optional[AbstractBaseUser]: - try: - return User.objects.get(id=user_id) - except User.DoesNotExist: - return diff --git a/backend/api/tests/views/test_session.py b/backend/api/tests/test_views.py similarity index 100% rename from backend/api/tests/views/test_session.py rename to backend/api/tests/test_views.py diff --git a/backend/api/tests/views/__init__.py b/backend/api/tests/views/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/api/urls.py b/backend/api/urls.py new file mode 100644 index 0000000..8113e66 --- /dev/null +++ b/backend/api/urls.py @@ -0,0 +1,23 @@ +from django.urls import include, path, re_path + +from .views import ClearExpiredView, LoginView + +urlpatterns = [ + path( + "session/", + include( + [ + re_path( + r"^login/(?Pemail|username|user-id|otp)/$", + LoginView.as_view(), + name="login", + ), + path( + "clear-expired/", + ClearExpiredView.as_view(), + name="clear-expired-sessions", + ), + ] + ), + ), +] diff --git a/backend/api/urls/__init__.py b/backend/api/urls/__init__.py deleted file mode 100644 index f46e04c..0000000 --- a/backend/api/urls/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.http import HttpResponse -from django.urls import include, path, re_path -from rest_framework import status - -from .csrf import urlpatterns as csrf_urlpatterns -from .session import urlpatterns as session_urlpatterns - -urlpatterns = [ - path("csrf/", include(csrf_urlpatterns)), - path("session/", include(session_urlpatterns)), - re_path( - r".*", - lambda request: HttpResponse( - "API endpoint not found", - status=status.HTTP_404_NOT_FOUND, - ), - ), -] diff --git a/backend/api/urls/csrf.py b/backend/api/urls/csrf.py deleted file mode 100644 index bd25177..0000000 --- a/backend/api/urls/csrf.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.urls import path - -from ..views.csrf import CookieView - -# TODO: import these url patterns form codeforlife package. -urlpatterns = [ - path("cookie/", CookieView.as_view(), name="get-csrf-cookie"), -] diff --git a/backend/api/urls/session.py b/backend/api/urls/session.py deleted file mode 100644 index 492770b..0000000 --- a/backend/api/urls/session.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.urls import path, re_path - -from ..views.session import ClearExpiredView, LoginView - -urlpatterns = [ - re_path( - r"^login/(?Pemail|username|user-id|otp)/$", - LoginView.as_view(), - name="login", - ), - path( - "clear-expired/", - ClearExpiredView.as_view(), - name="clear-expired-sessions", - ), -] diff --git a/backend/api/views/session.py b/backend/api/views.py similarity index 97% rename from backend/api/views/session.py rename to backend/api/views.py index 62f7a2d..e3d4b18 100644 --- a/backend/api/views/session.py +++ b/backend/api/views.py @@ -1,6 +1,5 @@ import logging -# from codeforlife.user.models import User from codeforlife.mixins import CronMixin from common.models import UserSession from django.contrib.auth import login @@ -12,7 +11,7 @@ from rest_framework.response import Response from rest_framework.views import APIView -from ..forms import ( +from .forms import ( BaseAuthForm, EmailAuthForm, OtpAuthForm, diff --git a/backend/api/views/__init__.py b/backend/api/views/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/api/views/csrf.py b/backend/api/views/csrf.py deleted file mode 100644 index 355aea1..0000000 --- a/backend/api/views/csrf.py +++ /dev/null @@ -1,14 +0,0 @@ -from django.utils.decorators import method_decorator -from django.views.decorators.csrf import ensure_csrf_cookie -from rest_framework.request import Request -from rest_framework.response import Response -from rest_framework.views import APIView - - -# TODO: import this view form codeforlife package. -class CookieView(APIView): - http_method_names = ["get"] - - @method_decorator(ensure_csrf_cookie) - def get(self, request: Request): - return Response() diff --git a/backend/pyproject.toml b/backend/pyproject.toml index e843634..e194012 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -2,4 +2,4 @@ line-length = 80 [tool.pytest.ini_options] -env = ["DJANGO_SETTINGS_MODULE=service.settings"] +env = ["DJANGO_SETTINGS_MODULE=service.settings", "SERVICE_NAME=sso"] diff --git a/backend/service/settings.py b/backend/service/settings.py index 18874d9..6af3975 100644 --- a/backend/service/settings.py +++ b/backend/service/settings.py @@ -16,16 +16,6 @@ # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "replace-me-and-put-me-in-a-github-secret" - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = bool(int(os.getenv("DEBUG", "1"))) - ALLOWED_HOSTS = ["*"] # Application definition @@ -37,7 +27,7 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", - # "codeforlife", + "codeforlife", "api", "aimmo", # TODO: remove this "game", # TODO: remove this @@ -105,15 +95,6 @@ }, ] -# Internationalization -# https://docs.djangoproject.com/en/3.2/topics/i18n/ - -LANGUAGE_CODE = "en-us" -TIME_ZONE = "UTC" -USE_I18N = True -USE_L10N = True -USE_TZ = True - # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.2/howto/static-files/ @@ -121,48 +102,5 @@ STATIC_URL = "/static/" STATICFILES_DIRS = [BASE_DIR / "myapp/static"] -# Default primary key field type -# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field - -DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" - -# Authentication backends -# https://docs.djangoproject.com/en/4.2/ref/settings/#authentication-backends - -AUTHENTICATION_BACKENDS = [ - "api.auth_backends.EmailAndPasswordBackend", - "api.auth_backends.UserIdAndLoginIdBackend", - "api.auth_backends.UsernameAndPasswordAndClassIdBackend", -] - -# Sessions -# https://docs.djangoproject.com/en/3.2/topics/http/sessions/ - -SESSION_COOKIE_AGE = 60 * 60 -SESSION_SAVE_EVERY_REQUEST = True -SESSION_EXPIRE_AT_BROWSER_CLOSE = True -SESSION_COOKIE_SECURE = True -SESSION_COOKIE_SAMESITE = "None" -SESSION_COOKIE_DOMAIN = "localhost" if DEBUG else "codeforlife.education" - -# CSRF -# https://docs.djangoproject.com/en/3.2/ref/csrf/ - -CSRF_COOKIE_NAME = "sso_csrftoken" -CSRF_COOKIE_SAMESITE = "None" -CSRF_COOKIE_SECURE = True - -# CORS -# https://pypi.org/project/django-cors-headers/ - -CORS_ALLOW_ALL_ORIGINS = DEBUG -CORS_ALLOW_CREDENTIALS = True -CORS_ALLOWED_ORIGINS = [] - -# ------------------------------------------------------------------------------ -# Custom -# ------------------------------------------------------------------------------ - -# Service -SERVICE_BASE_ROUTE = os.getenv("SERVICE_BASE_ROUTE", "sso/") +from codeforlife.settings import * diff --git a/backend/service/urls.py b/backend/service/urls.py index c6967a4..2c582ea 100644 --- a/backend/service/urls.py +++ b/backend/service/urls.py @@ -13,36 +13,7 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ -from django.conf import settings -from django.contrib import admin -from django.http import HttpResponse -from django.urls import include, path, re_path -from rest_framework import status -urlpatterns = [ - path( - settings.SERVICE_BASE_ROUTE, - include( - [ - path("admin/", admin.site.urls), - path("api/", include("api.urls")), - re_path( - r".*", - # lambda request: render(request, "frontend.html"), - lambda request: HttpResponse( - "TODO: relocate login pages to sso service and redirect to them" - ), - name="frontend", - ), - ] - ), - ), - re_path( - r".*", - lambda request: HttpResponse( - f'The base route is "{settings.SERVICE_BASE_ROUTE}".', - status=status.HTTP_404_NOT_FOUND, - ), - name="no-service", - ), -] +from codeforlife.urls import service_urlpatterns + +urlpatterns = service_urlpatterns() From 78f5ef06fae0755c917885f869711aac8a5778c0 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Mon, 18 Sep 2023 16:01:52 +0100 Subject: [PATCH 32/58] fix pipeline --- .github/workflows/main.yaml | 28 ++++++++++++---------------- backend/app.yaml | 2 +- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 3bc3651..b895220 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -11,7 +11,7 @@ on: workflow_dispatch: env: - SERVICE: sso + SERVICE_NAME: sso PYTHON_VERSION: 3.8 jobs: @@ -37,6 +37,7 @@ jobs: working-directory: ./backend run: if ! pipenv run black --check .; then exit 1; fi + # TODO: figure out why migrations are out of sync in old repos. # - name: Check Migrations # working-directory: ./backend # run: pipenv run python manage.py makemigrations --check --dry-run @@ -49,20 +50,8 @@ jobs: build-and-deploy: runs-on: ubuntu-latest needs: [test] - # if: github.ref_name == 'production' || github.ref_name == 'development' || github.ref_name == 'staging' + if: github.ref_name == 'production' || github.ref_name == 'development' || github.ref_name == 'staging' steps: - # - name: ⚙️ Set up Environment Variables - # run: | - # echo "SERVICE_BASE_ROUTE=$( - # if [ "${{ github.ref_name }}" == "production" ] - # then echo "sso" - # else echo "${{ github.ref_name }}-sso/" - # fi - # )" >> $GITHUB_ENV - - name: ⚙️ Set up Environment Variables - run: | - echo "SERVICE_BASE_ROUTE=sso/" >> $GITHUB_ENV - - name: 🛫 Checkout uses: actions/checkout@v3 @@ -95,9 +84,16 @@ jobs: uses: mikefarah/yq@master with: cmd: | + SERVICE_NAME=$( + if [ ${{ github.ref_name }} == "production" ] + then echo ${{ env.SERVICE_NAME }} + else echo ${{ github.ref_name }}-${{ env.SERVICE_NAME }} + fi + ) + yq -i ' - .service = "production-${{ env.SERVICE }}" | - .env_variables.SERVICE_BASE_ROUTE = "${{ env.SERVICE_BASE_ROUTE }}" + .service = "${{ github.ref_name }}-${{ env.SERVICE_NAME }}" | + .env_variables.SERVICE_NAME = "$SERVICE_NAME" ' backend/app.yaml - name: 🚀 Deploy App on GCloud diff --git a/backend/app.yaml b/backend/app.yaml index eb9317e..3fa31ad 100644 --- a/backend/app.yaml +++ b/backend/app.yaml @@ -8,4 +8,4 @@ inbound_services: env_variables: DEBUG: "0" DJANGO_SETTINGS_MODULE: "service.settings" - SERVICE_BASE_ROUTE: REPLACE_ME + SERVICE_NAME: REPLACE_ME From fe4edb2205ae271d262a18308735b4b166ecce21 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Mon, 18 Sep 2023 18:59:37 +0100 Subject: [PATCH 33/58] raise validation errors --- backend/api/forms.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/backend/api/forms.py b/backend/api/forms.py index 2d82ac5..ca4ecee 100644 --- a/backend/api/forms.py +++ b/backend/api/forms.py @@ -14,6 +14,12 @@ def __init__(self, request: WSGIRequest, *args, **kwargs): super().__init__(*args, **kwargs) def clean(self): + if self.errors: + raise ValidationError( + "Found form errors. Skipping authentication.", + code="form_errors", + ) + self.user = authenticate( self.request, **{key: self.cleaned_data[key] for key in self.fields.keys()} @@ -23,7 +29,11 @@ def clean(self): self.get_invalid_login_error_message(), code="invalid_login", ) - # TODO: confirm if we should return error message if is_active=False + elif not self.user.is_active: + raise ValidationError( + "User is not active", + code="user_not_active", + ) return self.cleaned_data From 8e63cef8a8e08aef720c5a6c36127899d2d9eaa3 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Mon, 18 Sep 2023 19:00:43 +0100 Subject: [PATCH 34/58] remove todos --- backend/api/forms.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/api/forms.py b/backend/api/forms.py index ca4ecee..cef01d5 100644 --- a/backend/api/forms.py +++ b/backend/api/forms.py @@ -51,7 +51,6 @@ class OtpAuthForm(BaseAuthForm): class EmailAuthForm(BaseAuthForm): email = forms.EmailField() - # TODO: use regex validator password = forms.CharField(strip=False) def get_invalid_login_error_message(self): @@ -63,7 +62,6 @@ def get_invalid_login_error_message(self): class UsernameAuthForm(BaseAuthForm): username = UsernameField() - # TODO: use regex validator password = forms.CharField(strip=False) class_id = forms.CharField( validators=[ From 2c3140bcf497324a73bf742e4eae107a849a5ffb Mon Sep 17 00:00:00 2001 From: SKairinos Date: Tue, 19 Sep 2023 10:11:42 +0100 Subject: [PATCH 35/58] use latest package version --- backend/Pipfile | 2 +- backend/Pipfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/Pipfile b/backend/Pipfile index b9627c7..7521f08 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -codeforlife = {ref = "v0.7.5", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} +codeforlife = {ref = "v0.7.6", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} django = "==3.2.20" djangorestframework = "==3.13.1" django-cors-headers = "==4.1.0" diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index a62fa16..6b53d3a 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "c2bb1ce3b680fc3a63e3014b9d97edef2b7a4aad2f84ab2242162a87a709d8d8" + "sha256": "5d625aa39b3b580c7791419f9b8e7f25f7e9be3db5ca2fe13b926bc457f98fef" }, "pipfile-spec": 6, "requires": { @@ -155,7 +155,7 @@ }, "codeforlife": { "git": "https://github.com/ocadotechnology/codeforlife-package-python.git", - "ref": "b9b351dce23870134180473afc2a7633d6bb1c78" + "ref": "4662402563b34010c599c63056d1cdbe0c0afa27" }, "codeforlife-portal": { "hashes": [ From a910e0571e4920cc227f0a4f7e80a0c4630d56a0 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Tue, 19 Sep 2023 10:27:26 +0100 Subject: [PATCH 36/58] use latest cfl package --- backend/Pipfile | 2 +- backend/Pipfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/Pipfile b/backend/Pipfile index 7521f08..11278f5 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -codeforlife = {ref = "v0.7.6", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} +codeforlife = {ref = "v0.7.7", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} django = "==3.2.20" djangorestframework = "==3.13.1" django-cors-headers = "==4.1.0" diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 6b53d3a..71cfcda 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "5d625aa39b3b580c7791419f9b8e7f25f7e9be3db5ca2fe13b926bc457f98fef" + "sha256": "51ab713273fb9b712c7236631761b7399e149fce5dfd79e59fe3c24f09cd4efa" }, "pipfile-spec": 6, "requires": { @@ -155,7 +155,7 @@ }, "codeforlife": { "git": "https://github.com/ocadotechnology/codeforlife-package-python.git", - "ref": "4662402563b34010c599c63056d1cdbe0c0afa27" + "ref": "a8951a9e835d4beb78d1f96531ee65e008106956" }, "codeforlife-portal": { "hashes": [ From a86a15bbf2bb8cccd050e881d6692d51d5270039 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Tue, 19 Sep 2023 11:01:30 +0100 Subject: [PATCH 37/58] set secret key --- .github/workflows/main.yaml | 2 ++ backend/app.yaml | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index b895220..eb131ec 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -51,6 +51,7 @@ jobs: runs-on: ubuntu-latest needs: [test] if: github.ref_name == 'production' || github.ref_name == 'development' || github.ref_name == 'staging' + environment: ${{ github.ref_name }} steps: - name: 🛫 Checkout uses: actions/checkout@v3 @@ -93,6 +94,7 @@ jobs: yq -i ' .service = "${{ github.ref_name }}-${{ env.SERVICE_NAME }}" | + .env_variables.SECRET_KEY = "${{ vars.SECRET_KEY }}" | .env_variables.SERVICE_NAME = "$SERVICE_NAME" ' backend/app.yaml diff --git a/backend/app.yaml b/backend/app.yaml index 3fa31ad..6274c41 100644 --- a/backend/app.yaml +++ b/backend/app.yaml @@ -8,4 +8,5 @@ inbound_services: env_variables: DEBUG: "0" DJANGO_SETTINGS_MODULE: "service.settings" - SERVICE_NAME: REPLACE_ME + SECRET_KEY: "REPLACE_ME" + SERVICE_NAME: "REPLACE_ME" From 429bd25297cb0587b604f22fa35d5d625d587ea8 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Tue, 19 Sep 2023 11:22:07 +0100 Subject: [PATCH 38/58] new cfl package --- backend/Pipfile | 2 +- backend/Pipfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/Pipfile b/backend/Pipfile index 11278f5..9e06a37 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -codeforlife = {ref = "v0.7.7", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} +codeforlife = {ref = "v0.7.8", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} django = "==3.2.20" djangorestframework = "==3.13.1" django-cors-headers = "==4.1.0" diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 71cfcda..eb5ecc1 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "51ab713273fb9b712c7236631761b7399e149fce5dfd79e59fe3c24f09cd4efa" + "sha256": "5795605c309a108370388dbfecb72567f389b3c6d818a22efb5b9bc57d97913c" }, "pipfile-spec": 6, "requires": { @@ -155,7 +155,7 @@ }, "codeforlife": { "git": "https://github.com/ocadotechnology/codeforlife-package-python.git", - "ref": "a8951a9e835d4beb78d1f96531ee65e008106956" + "ref": "99c70bbf9a9dd1c65a55c1a0188a067d7fcf0d63" }, "codeforlife-portal": { "hashes": [ From c0a223f1e6f11d3e15b9afef1358f020457fa04a Mon Sep 17 00:00:00 2001 From: SKairinos Date: Tue, 19 Sep 2023 11:37:30 +0100 Subject: [PATCH 39/58] fix: set env vars --- .vscode/launch.json | 3 ++- backend/Pipfile | 2 +- backend/Pipfile.lock | 4 ++-- backend/app.yaml | 3 +++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 42be01e..90d8aa3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,7 +17,8 @@ ], "preLaunchTask": "migrate-db", "env": { - "SERVICE_NAME": "sso" + "SERVICE_NAME": "sso", + "SERVICE_PORT": "8001" } }, { diff --git a/backend/Pipfile b/backend/Pipfile index 9e06a37..1738bda 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -codeforlife = {ref = "v0.7.8", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} +codeforlife = {ref = "v0.7.9", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} django = "==3.2.20" djangorestframework = "==3.13.1" django-cors-headers = "==4.1.0" diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index eb5ecc1..4c3ab6a 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "5795605c309a108370388dbfecb72567f389b3c6d818a22efb5b9bc57d97913c" + "sha256": "ccf7cbfe76df991491b5affd246869d1c4323d446b496b839f707ccd4f0aef53" }, "pipfile-spec": 6, "requires": { @@ -155,7 +155,7 @@ }, "codeforlife": { "git": "https://github.com/ocadotechnology/codeforlife-package-python.git", - "ref": "99c70bbf9a9dd1c65a55c1a0188a067d7fcf0d63" + "ref": "3b1b50ca886e5d860093fd3cfa55be204843dba1" }, "codeforlife-portal": { "hashes": [ diff --git a/backend/app.yaml b/backend/app.yaml index 6274c41..c89dca0 100644 --- a/backend/app.yaml +++ b/backend/app.yaml @@ -10,3 +10,6 @@ env_variables: DJANGO_SETTINGS_MODULE: "service.settings" SECRET_KEY: "REPLACE_ME" SERVICE_NAME: "REPLACE_ME" + SERVICE_PROTOCOL: "https" + SERVICE_DOMAIN: "codeforlife.education" + SERVICE_PORT: "443" From f2a1a68e52ce8754151fa1250bcaffe2809e18d2 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Tue, 19 Sep 2023 12:20:22 +0100 Subject: [PATCH 40/58] use new cfl package --- backend/Pipfile | 2 +- backend/Pipfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/Pipfile b/backend/Pipfile index 1738bda..8b3f3dd 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -codeforlife = {ref = "v0.7.9", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} +codeforlife = {ref = "v0.7.10", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} django = "==3.2.20" djangorestframework = "==3.13.1" django-cors-headers = "==4.1.0" diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 4c3ab6a..df2a347 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "ccf7cbfe76df991491b5affd246869d1c4323d446b496b839f707ccd4f0aef53" + "sha256": "5dda424ef60b78cf356a5179a08f00e61cd15709c3a5887fe028dd935bdbeadf" }, "pipfile-spec": 6, "requires": { @@ -155,7 +155,7 @@ }, "codeforlife": { "git": "https://github.com/ocadotechnology/codeforlife-package-python.git", - "ref": "3b1b50ca886e5d860093fd3cfa55be204843dba1" + "ref": "0acc651119ed7e0c2403188b2e4d0ba4fbcbe76c" }, "codeforlife-portal": { "hashes": [ From 7439decb66e485a3032f32dd5ae51060923994c4 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Tue, 19 Sep 2023 15:28:19 +0100 Subject: [PATCH 41/58] user new cfl-common package --- .github/workflows/main.yaml | 7 +++---- backend/Pipfile | 6 +++--- backend/Pipfile.lock | 22 +++++++++++----------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index eb131ec..6c00f0e 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -37,10 +37,9 @@ jobs: working-directory: ./backend run: if ! pipenv run black --check .; then exit 1; fi - # TODO: figure out why migrations are out of sync in old repos. - # - name: Check Migrations - # working-directory: ./backend - # run: pipenv run python manage.py makemigrations --check --dry-run + - name: Check Migrations + working-directory: ./backend + run: pipenv run python manage.py makemigrations --check --dry-run # TODO: assert code coverage target. - name: Test Code Units diff --git a/backend/Pipfile b/backend/Pipfile index 8b3f3dd..e425c9c 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -4,13 +4,13 @@ verify_ssl = true name = "pypi" [packages] -codeforlife = {ref = "v0.7.10", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} +codeforlife = {ref = "v0.7.11", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} django = "==3.2.20" djangorestframework = "==3.13.1" django-cors-headers = "==4.1.0" # https://pypi.org/user/codeforlife/ -cfl-common = "==6.36.0" # TODO: remove -codeforlife-portal = "==6.36.0" # TODO: remove +cfl-common = "==6.36.2" # TODO: remove +codeforlife-portal = "==6.36.2" # TODO: remove aimmo = "==2.10.6" # TODO: remove rapid-router = "==5.11.3" # TODO: remove phonenumbers = "==8.12.12" # TODO: remove diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index df2a347..b6dcfe8 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "5dda424ef60b78cf356a5179a08f00e61cd15709c3a5887fe028dd935bdbeadf" + "sha256": "e74bb8111e2f3b8700fced5ce7daacc63acee1f5fa28338e407d4f907f015f9b" }, "pipfile-spec": 6, "requires": { @@ -58,11 +58,11 @@ }, "cfl-common": { "hashes": [ - "sha256:6eb1a349e770187c32e075b93b059df9eff0ae42e3abdd98b5fb6e05e9abb465", - "sha256:8ea2b387c91a14da0a03bf1808b5b002a76138c89f23723530911d1c0dc28730" + "sha256:171e5607e7704e7f979d947226c681c1672c1f038116c0ed69b1fe57c2eb1eac", + "sha256:8fd9b61f1f6b70a1f8957ed65c5ff42fa6c6b6fedbf1b34cdeae0df563844d9f" ], "index": "pypi", - "version": "==6.36.0" + "version": "==6.36.2" }, "charset-normalizer": { "hashes": [ @@ -155,15 +155,15 @@ }, "codeforlife": { "git": "https://github.com/ocadotechnology/codeforlife-package-python.git", - "ref": "0acc651119ed7e0c2403188b2e4d0ba4fbcbe76c" + "ref": "51fd3eb37aa2d880172255972df44c00a1963699" }, "codeforlife-portal": { "hashes": [ - "sha256:5e1882e9adec7d767d922cec4cc893d969a08f1ec042394a2f607b31fb9cc087", - "sha256:6d7dfb1ab298e38de27a177598036ed57f1cfa8307481b34d02bf61070057e81" + "sha256:be6a245d3a7156d6ae019874f31e57a6a73666c7f44bef5ae84bfe4f43279876", + "sha256:cb54c8373997d73af8fe548ee84a363427d865206614452c55d2fb0b146d7caf" ], "index": "pypi", - "version": "==6.36.0" + "version": "==6.36.2" }, "defusedxml": { "hashes": [ @@ -1047,11 +1047,11 @@ }, "zipp": { "hashes": [ - "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0", - "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147" + "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31", + "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0" ], "markers": "python_version >= '3.8'", - "version": "==3.16.2" + "version": "==3.17.0" } }, "develop": { From 006c487baf2777f519fa24dfef296a25dd39fb8f Mon Sep 17 00:00:00 2001 From: SKairinos Date: Tue, 19 Sep 2023 15:29:19 +0100 Subject: [PATCH 42/58] house keeping [skip ci] --- .github/workflows/main.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 6c00f0e..478ece4 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -36,11 +36,11 @@ jobs: - name: Check Code Format working-directory: ./backend run: if ! pipenv run black --check .; then exit 1; fi - + - name: Check Migrations working-directory: ./backend run: pipenv run python manage.py makemigrations --check --dry-run - + # TODO: assert code coverage target. - name: Test Code Units working-directory: ./backend From ba679e7f07cf60d74da13a70de90c3da12419c95 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Tue, 19 Sep 2023 15:53:36 +0100 Subject: [PATCH 43/58] use latest cfl package --- .vscode/tasks.json | 5 +---- backend/Pipfile | 2 +- backend/Pipfile.lock | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index a06e253..e70337c 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,10 +13,7 @@ "label": "migrate-db", "type": "shell", "options": { - "cwd": "${workspaceFolder}/backend", - "env": { - "SERVICE_NAME": "sso" - } + "cwd": "${workspaceFolder}/backend" }, "dependsOn": [ "install-dev-deps" diff --git a/backend/Pipfile b/backend/Pipfile index e425c9c..a525f6b 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -codeforlife = {ref = "v0.7.11", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} +codeforlife = {ref = "v0.7.12", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} django = "==3.2.20" djangorestframework = "==3.13.1" django-cors-headers = "==4.1.0" diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index b6dcfe8..b4a264c 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e74bb8111e2f3b8700fced5ce7daacc63acee1f5fa28338e407d4f907f015f9b" + "sha256": "9846ce1d0191a67625fc58bfa1ebbd29bcf9a5a7da472966cdc69723fc5f5a74" }, "pipfile-spec": 6, "requires": { @@ -155,7 +155,7 @@ }, "codeforlife": { "git": "https://github.com/ocadotechnology/codeforlife-package-python.git", - "ref": "51fd3eb37aa2d880172255972df44c00a1963699" + "ref": "4516d806d2e863dab24d621c75308a4fae21c983" }, "codeforlife-portal": { "hashes": [ From 792fa710cbb85b665a79a7d70dafeeaf54ea895d Mon Sep 17 00:00:00 2001 From: SKairinos Date: Thu, 21 Sep 2023 13:08:29 +0100 Subject: [PATCH 44/58] feedback --- .github/workflows/main.yaml | 2 +- backend/Pipfile | 2 +- backend/Pipfile.lock | 4 ++-- backend/api/forms.py | 25 ++++++++++++++++++++++--- backend/api/views.py | 4 ++-- backend/service/settings.py | 22 ---------------------- 6 files changed, 28 insertions(+), 31 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 478ece4..bb2909a 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -25,7 +25,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION }} - + - name: Install Python Packages working-directory: ./backend run: | diff --git a/backend/Pipfile b/backend/Pipfile index a525f6b..ee85a5b 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -codeforlife = {ref = "v0.7.12", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} +codeforlife = {ref = "v0.7.14", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} django = "==3.2.20" djangorestframework = "==3.13.1" django-cors-headers = "==4.1.0" diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index b4a264c..022bd2c 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "9846ce1d0191a67625fc58bfa1ebbd29bcf9a5a7da472966cdc69723fc5f5a74" + "sha256": "7d15e3872ed95caf61ff18fd285883eb2509ce0bdbe6f908328de72924b53036" }, "pipfile-spec": 6, "requires": { @@ -155,7 +155,7 @@ }, "codeforlife": { "git": "https://github.com/ocadotechnology/codeforlife-package-python.git", - "ref": "4516d806d2e863dab24d621c75308a4fae21c983" + "ref": "d8a3f6963507996d862011604786b2e0c3d63d70" }, "codeforlife-portal": { "hashes": [ diff --git a/backend/api/forms.py b/backend/api/forms.py index cef01d5..123c4d1 100644 --- a/backend/api/forms.py +++ b/backend/api/forms.py @@ -48,6 +48,9 @@ class OtpAuthForm(BaseAuthForm): ], ) + def get_invalid_login_error_message(self) -> str: + return "Please enter the correct one-time password." + class EmailAuthForm(BaseAuthForm): email = forms.EmailField() @@ -56,7 +59,7 @@ class EmailAuthForm(BaseAuthForm): def get_invalid_login_error_message(self): return ( "Please enter a correct username and password. Note that both" - " fields may be case-sensitive." + " fields are case-sensitive." ) @@ -66,13 +69,29 @@ class UsernameAuthForm(BaseAuthForm): class_id = forms.CharField( validators=[ RegexValidator( - r"^[A-Z]{2}[0-9]{3}$", - "Must be 2 upper case letters followed by 3 digits", + r"^[A-Z]{2}([0-9]{3}|[A-Z]{3})$", + ( + "Must be 5 upper case letters or 2 upper case letters" + " followed by 3 digits" + ), ), ], ) + def get_invalid_login_error_message(self): + return ( + "Please enter a correct username and password for a class." + " Double check your class ID is correct and remember that your" + " username and password are case-sensitive." + ) + class UserIdAuthForm(BaseAuthForm): user_id = forms.IntegerField(min_value=1) login_id = forms.CharField(min_length=32, max_length=32) + + def get_invalid_login_error_message(self): + return ( + "Your login link is invalid. Please contact your teacher or the" + " Code for Life team for support." + ) diff --git a/backend/api/views.py b/backend/api/views.py index e3d4b18..a8b6cde 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -56,14 +56,14 @@ def get(self, request): # objects is missing type SessionManager session_objects: SessionManager = Session.objects - before_session_count = session_objects.all().count() + before_session_count = session_objects.count() logging.info(f"Session count before clearance: {before_session_count}") # Clears expired sessions. # https://docs.djangoproject.com/en/3.2/ref/django-admin/#clearsessions call_command("clearsessions") - after_session_count = session_objects.all().count() + after_session_count = session_objects.count() logging.info(f"Session count after clearance: {after_session_count}") session_clearance_count = before_session_count - after_session_count logging.info(f"Session clearance count: {session_clearance_count}") diff --git a/backend/service/settings.py b/backend/service/settings.py index 6af3975..fcab0ea 100644 --- a/backend/service/settings.py +++ b/backend/service/settings.py @@ -47,8 +47,6 @@ "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = "service.urls" - TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", @@ -65,8 +63,6 @@ }, ] -WSGI_APPLICATION = "service.wsgi.application" - # Database # https://docs.djangoproject.com/en/3.2/ref/settings/#databases @@ -77,24 +73,6 @@ } } -# Password validation -# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", - }, -] - # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.2/howto/static-files/ From 91314252d9f4bbc3e7d9f1bf82e17a74cb82f4fb Mon Sep 17 00:00:00 2001 From: SKairinos Date: Thu, 21 Sep 2023 16:03:51 +0100 Subject: [PATCH 45/58] remove unnecessary return types --- backend/api/forms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/api/forms.py b/backend/api/forms.py index 123c4d1..e6cb0b1 100644 --- a/backend/api/forms.py +++ b/backend/api/forms.py @@ -37,7 +37,7 @@ def clean(self): return self.cleaned_data - def get_invalid_login_error_message(self) -> str: + def get_invalid_login_error_message(self): raise NotImplementedError() @@ -48,7 +48,7 @@ class OtpAuthForm(BaseAuthForm): ], ) - def get_invalid_login_error_message(self) -> str: + def get_invalid_login_error_message(self): return "Please enter the correct one-time password." From 8140725949564a70eb7a007fcb62214dec2bf932 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Wed, 27 Sep 2023 15:54:11 +0100 Subject: [PATCH 46/58] return remaining session auth factors --- backend/Pipfile | 8 +- backend/Pipfile.lock | 542 +------------------------------------------ backend/api/views.py | 15 +- 3 files changed, 15 insertions(+), 550 deletions(-) diff --git a/backend/Pipfile b/backend/Pipfile index ee85a5b..b01817c 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -4,16 +4,10 @@ verify_ssl = true name = "pypi" [packages] -codeforlife = {ref = "v0.7.14", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} +codeforlife = {ref = "otp", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} django = "==3.2.20" djangorestframework = "==3.13.1" django-cors-headers = "==4.1.0" -# https://pypi.org/user/codeforlife/ -cfl-common = "==6.36.2" # TODO: remove -codeforlife-portal = "==6.36.2" # TODO: remove -aimmo = "==2.10.6" # TODO: remove -rapid-router = "==5.11.3" # TODO: remove -phonenumbers = "==8.12.12" # TODO: remove [dev-packages] black = "*" diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 022bd2c..4b7a332 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "7d15e3872ed95caf61ff18fd285883eb2509ce0bdbe6f908328de72924b53036" + "sha256": "82b242655141e9c20e3d45f61628ec01233301b6f18912d81516956d7b9f0fad" }, "pipfile-spec": 6, "requires": { @@ -16,14 +16,6 @@ ] }, "default": { - "aimmo": { - "hashes": [ - "sha256:b89f83586412320b147ea61b4277599732c10e7668fba5b2d0a383db6a173145", - "sha256:bd2841b24d7830096b7cc81bdf7548377d30602f1a1b3d9e9084a58b11557413" - ], - "index": "pypi", - "version": "==2.10.6" - }, "asgiref": { "hashes": [ "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e", @@ -32,22 +24,6 @@ "markers": "python_version >= '3.7'", "version": "==3.7.2" }, - "attrs": { - "hashes": [ - "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", - "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015" - ], - "markers": "python_version >= '3.7'", - "version": "==23.1.0" - }, - "cachetools": { - "hashes": [ - "sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590", - "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b" - ], - "markers": "python_version >= '3.7'", - "version": "==5.3.1" - }, "certifi": { "hashes": [ "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", @@ -61,7 +37,6 @@ "sha256:171e5607e7704e7f979d947226c681c1672c1f038116c0ed69b1fe57c2eb1eac", "sha256:8fd9b61f1f6b70a1f8957ed65c5ff42fa6c6b6fedbf1b34cdeae0df563844d9f" ], - "index": "pypi", "version": "==6.36.2" }, "charset-normalizer": { @@ -155,31 +130,7 @@ }, "codeforlife": { "git": "https://github.com/ocadotechnology/codeforlife-package-python.git", - "ref": "d8a3f6963507996d862011604786b2e0c3d63d70" - }, - "codeforlife-portal": { - "hashes": [ - "sha256:be6a245d3a7156d6ae019874f31e57a6a73666c7f44bef5ae84bfe4f43279876", - "sha256:cb54c8373997d73af8fe548ee84a363427d865206614452c55d2fb0b146d7caf" - ], - "index": "pypi", - "version": "==6.36.2" - }, - "defusedxml": { - "hashes": [ - "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", - "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==0.7.1" - }, - "diff-match-patch": { - "hashes": [ - "sha256:953019cdb9c9d2c9e47b5b12bcff3cf4746fc4598eb406076fa1fc27e6a1f15c", - "sha256:dce43505fb7b1b317de7195579388df0746d90db07015ed47a85e5e44930ef93" - ], - "markers": "python_version >= '3.7'", - "version": "==20230430" + "ref": "ef8b4951699afb5bd58d5fbf0aaf061cda566152" }, "django": { "hashes": [ @@ -189,13 +140,6 @@ "index": "pypi", "version": "==3.2.20" }, - "django-classy-tags": { - "hashes": [ - "sha256:25eb4f95afee396148683bfb4811b83b3f5729218d73ad0a3399271a6f9fcc49", - "sha256:d59d98bdf96a764dcf7a2929a86439d023b283a9152492811c7e44fc47555bc9" - ], - "version": "==2.0.0" - }, "django-cors-headers": { "hashes": [ "sha256:36a8d7a6dee6a85f872fe5916cc878a36d0812043866355438dfeda0b20b6b78", @@ -211,42 +155,18 @@ ], "version": "==7.3.1" }, - "django-csp": { - "hashes": [ - "sha256:01443a07723f9a479d498bd7bb63571aaa771e690f64bde515db6cdb76e8041a", - "sha256:01eda02ad3f10261c74131cdc0b5a6a62b7c7ad4fd017fbefb7a14776e0a9727" - ], - "version": "==3.7" - }, "django-formtools": { "hashes": [ "sha256:304fa777b8ef9e0693ce7833f885cb89ba46b0e46fc23b01176900a93f46742f", "sha256:c5272c03c1cd51b2375abf7397a199a3148a9fbbf2f100e186467a84025d13b2" ], - "markers": "python_version >= '3.7'", "version": "==2.2" }, - "django-import-export": { - "hashes": [ - "sha256:88ecaf06be06bd95d97cf34f3c911c56c012a7a81712a8956740e5bfc2465162", - "sha256:d02e31908c965d512cc6f7ef6e72935177647b15d3846050d0f094177fca0d86" - ], - "markers": "python_version >= '3.8'", - "version": "==3.3.1" - }, - "django-js-reverse": { - "hashes": [ - "sha256:2a392d169f44e30b883c30dfcfd917a14167ce8fe196c99d2385b31c90d77aa0", - "sha256:8134c2ab6307c945edfa90671ca65e85d6c1754d48566bdd6464be259cc80c30" - ], - "version": "==0.9.1" - }, "django-otp": { "hashes": [ "sha256:8ba5ab9bd2738c7321376c349d7cce49cf4404e79f6804e0a3cc462a91728e18", "sha256:f523fb9dec420f28a29d3e2ad72ac06f64588956ed4f2b5b430d8e957ebb8287" ], - "markers": "python_version >= '3.7'", "version": "==1.0.2" }, "django-phonenumber-field": { @@ -257,46 +177,6 @@ "markers": "python_version >= '3.7'", "version": "==6.4.0" }, - "django-pipeline": { - "hashes": [ - "sha256:26f1d344a7bf39bc92c9dc520093471d912de53abd7d22ac715e77d779a831c8", - "sha256:56c299cec0e644e77d5f928f4cebfff804b919cc10ff5c0bfaa070ff57e8da44" - ], - "version": "==2.0.8" - }, - "django-preventconcurrentlogins": { - "hashes": [ - "sha256:9cb45fcd63edeec55e5ac29bbd2ee96974dc2a72d74ab88088dbf6a1f52978e9" - ], - "version": "==0.8.2" - }, - "django-ratelimit": { - "hashes": [ - "sha256:73223d860abd5c5d7b9a807fabb39a6220068129b514be8d78044b52607ab154", - "sha256:857e797f23de948b204a31dba9d88aea3ce731b7a5d926d0240c772e19b5486f" - ], - "markers": "python_version >= '3.4'", - "version": "==3.0.1" - }, - "django-recaptcha": { - "hashes": [ - "sha256:567784963fd5400feaf92e8951d8dbbbdb4b4c48a76e225d4baa63a2c9d2cd8c" - ], - "version": "==2.0.6" - }, - "django-sekizai": { - "hashes": [ - "sha256:5c5e16845d37ce822fc655ce79ec02715191b3d03330b550997bcb842cf24fdf", - "sha256:e829f09b0d6bf01ee5cde05de1fb3faf2fbc5df66dc4dc280fbaac224ca4336f" - ], - "version": "==2.0.0" - }, - "django-treebeard": { - "hashes": [ - "sha256:83aebc34a9f06de7daaec330d858d1c47887e81be3da77e3541fe7368196dd8a" - ], - "version": "==4.3.1" - }, "django-two-factor-auth": { "hashes": [ "sha256:3fac266d12472ac66475dd737bb18f2992484313bf56acf5a2eea5e824291ee6", @@ -312,29 +192,6 @@ "index": "pypi", "version": "==3.13.1" }, - "dnspython": { - "hashes": [ - "sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01", - "sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" - }, - "et-xmlfile": { - "hashes": [ - "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c", - "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada" - ], - "markers": "python_version >= '3.6'", - "version": "==1.1.0" - }, - "eventlet": { - "hashes": [ - "sha256:27ae41fad9deed9bbf4166f3e3b65acc15d524d42210a518e5877da85a6b8c5d", - "sha256:b36ec2ecc003de87fc87b93197d77fea528aa0f9204a34fdf3b2f8d0f01e017b" - ], - "version": "==0.31.0" - }, "flask": { "hashes": [ "sha256:7eb373984bf1c770023fce9db164ed0c3353cd0b53f130f4693da0ca756a2e6d", @@ -343,92 +200,6 @@ "markers": "python_version >= '3.7'", "version": "==2.2.3" }, - "google-auth": { - "hashes": [ - "sha256:ce311e2bc58b130fddf316df57c9b3943c2a7b4f6ec31de9663a9333e4064efc", - "sha256:f586b274d3eb7bd932ea424b1c702a30e0393a2e2bc4ca3eae8263ffd8be229f" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==2.17.3" - }, - "greenlet": { - "hashes": [ - "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a", - "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a", - "sha256:1087300cf9700bbf455b1b97e24db18f2f77b55302a68272c56209d5587c12d1", - "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43", - "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33", - "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8", - "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088", - "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca", - "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343", - "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645", - "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db", - "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df", - "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3", - "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86", - "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2", - "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a", - "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf", - "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7", - "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394", - "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40", - "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3", - "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6", - "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74", - "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0", - "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3", - "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91", - "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5", - "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9", - "sha256:8512a0c38cfd4e66a858ddd1b17705587900dd760c6003998e9472b77b56d417", - "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8", - "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b", - "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6", - "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb", - "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73", - "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b", - "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df", - "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9", - "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f", - "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0", - "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857", - "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a", - "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249", - "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30", - "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292", - "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b", - "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d", - "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b", - "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c", - "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca", - "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7", - "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75", - "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae", - "sha256:d4606a527e30548153be1a9f155f4e283d109ffba663a15856089fb55f933e47", - "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b", - "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470", - "sha256:d967650d3f56af314b72df7089d96cda1083a7fc2da05b375d2bc48c82ab3f3c", - "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564", - "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9", - "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099", - "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0", - "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5", - "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19", - "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1", - "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==2.0.2" - }, - "hypothesis": { - "hashes": [ - "sha256:6a3471ff74864ab04a0650c75500ef15f2f4a901d49ccbb7cbec668365736688", - "sha256:989162a9e0715c624b99ad9b2b4206765879b40eb51eef17b1e37de3e898370a" - ], - "markers": "python_version >= '3.6'", - "version": "==5.41.3" - }, "idna": { "hashes": [ "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", @@ -461,31 +232,6 @@ "markers": "python_version >= '3.7'", "version": "==3.1.2" }, - "kubernetes": { - "hashes": [ - "sha256:5854b0c508e8d217ca205591384ab58389abdae608576f9c9afc35a3c76a366c", - "sha256:e3db6800abf7e36c38d2629b5cb6b74d10988ee0cba6fba45595a7cbe60c0042" - ], - "markers": "python_version >= '3.6'", - "version": "==26.1.0" - }, - "libsass": { - "hashes": [ - "sha256:081e256ab3c5f3f09c7b8dea3bf3bf5e64a97c6995fd9eea880639b3f93a9f9a", - "sha256:3ab5ad18e47db560f4f0c09e3d28cf3bb1a44711257488ac2adad69f4f7f8425", - "sha256:65455a2728b696b62100eb5932604aa13a29f4ac9a305d95773c14aaa7200aaf", - "sha256:89c5ce497fcf3aba1dd1b19aae93b99f68257e5f2026b731b00a872f13324c7f", - "sha256:f1efc1b612299c88aec9e39d6ca0c266d360daa5b19d9430bdeaffffa86993f9" - ], - "markers": "python_version >= '3.6'", - "version": "==0.22.0" - }, - "markuppy": { - "hashes": [ - "sha256:1adee2c0a542af378fe84548ff6f6b0168f3cb7f426b46961038a2bcfaad0d5f" - ], - "version": "==1.14" - }, "markupsafe": { "hashes": [ "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e", @@ -552,14 +298,6 @@ "markers": "python_version >= '3.7'", "version": "==2.1.3" }, - "more-itertools": { - "hashes": [ - "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced", - "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713" - ], - "markers": "python_version >= '3.5'", - "version": "==8.7.0" - }, "numpy": { "hashes": [ "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f", @@ -594,28 +332,6 @@ "markers": "python_version >= '3.8'", "version": "==1.24.4" }, - "oauthlib": { - "hashes": [ - "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", - "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918" - ], - "markers": "python_version >= '3.6'", - "version": "==3.2.2" - }, - "odfpy": { - "hashes": [ - "sha256:db766a6e59c5103212f3cc92ec8dd50a0f3a02790233ed0b52148b70d3c438ec", - "sha256:fc3b8d1bc098eba4a0fda865a76d9d1e577c4ceec771426bcb169a82c5e9dfe0" - ], - "version": "==1.4.1" - }, - "openpyxl": { - "hashes": [ - "sha256:a6f5977418eff3b2d5500d54d9db50c8277a368436f4e4f8ddb1be3422870184", - "sha256:f91456ead12ab3c6c2e9491cf33ba6d08357d802192379bb482f1033ade496f5" - ], - "version": "==3.1.2" - }, "pandas": { "hashes": [ "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682", @@ -655,90 +371,6 @@ "markers": "python_version >= '3.8'", "version": "==0.4.0" }, - "phonenumbers": { - "hashes": [ - "sha256:23944f9e628f32a975d3b221b6d76e6ba8ae618d53cb3d82fc23d9e100a59b29", - "sha256:70aa98a50ba7bc7f6bf17851f806c927107e7c44e7d21eb46bdbec07b99d23ae" - ], - "index": "pypi", - "version": "==8.12.12" - }, - "pillow": { - "hashes": [ - "sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff", - "sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f", - "sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21", - "sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635", - "sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a", - "sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f", - "sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1", - "sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d", - "sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db", - "sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849", - "sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7", - "sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876", - "sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3", - "sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317", - "sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91", - "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d", - "sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b", - "sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd", - "sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed", - "sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500", - "sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7", - "sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a", - "sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a", - "sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0", - "sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf", - "sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f", - "sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1", - "sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088", - "sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971", - "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a", - "sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205", - "sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54", - "sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08", - "sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21", - "sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d", - "sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08", - "sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e", - "sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf", - "sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b", - "sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145", - "sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2", - "sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d", - "sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d", - "sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf", - "sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad", - "sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d", - "sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1", - "sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4", - "sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2", - "sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19", - "sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37", - "sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4", - "sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68", - "sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1" - ], - "markers": "python_version >= '3.8'", - "version": "==10.0.1" - }, - "pyasn1": { - "hashes": [ - "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57", - "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==0.5.0" - }, - "pyasn1-modules": { - "hashes": [ - "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c", - "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==0.3.0" - }, "pydantic": { "hashes": [ "sha256:01aea3a42c13f2602b7ecbbea484a98169fb568ebd9e247593ea05f01b884b2e", @@ -781,14 +413,6 @@ "markers": "python_version >= '3.7'", "version": "==1.10.7" }, - "pyhamcrest": { - "hashes": [ - "sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316", - "sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29" - ], - "markers": "python_version >= '3.5'", - "version": "==2.0.2" - }, "pyjwt": { "hashes": [ "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd", @@ -819,41 +443,6 @@ ], "version": "==2023.3.post1" }, - "pyyaml": { - "hashes": [ - "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", - "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696", - "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393", - "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77", - "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922", - "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5", - "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8", - "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10", - "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc", - "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", - "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", - "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", - "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347", - "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", - "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541", - "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", - "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", - "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc", - "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", - "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa", - "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", - "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122", - "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", - "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", - "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", - "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc", - "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247", - "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6", - "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==5.4.1" - }, "qrcode": { "hashes": [ "sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a", @@ -862,65 +451,6 @@ "markers": "python_version >= '3.7'", "version": "==7.4.2" }, - "rapid-router": { - "hashes": [ - "sha256:b3b0b9dd449775aaac8b6dcc05601f8e0d3d1805100ba80a086cc8a3c2661526", - "sha256:bb88bb75e0f743ebedcfd44fbbfba5e1f63e5464c2d7d7bd02d3c838486f78ac" - ], - "index": "pypi", - "version": "==5.11.3" - }, - "reportlab": { - "hashes": [ - "sha256:0b94e4f65a5f77a631cc010c9a7892d69e33f3251b760639dcc76420e138ce95", - "sha256:11a71c314183532d889ad4b3941f61c3fe4bfdda769c768a7f02d93cb69dd1bb", - "sha256:149718c3eaee937f28094325f0dd9ae1add3172c2dacbb93ff5403f37c9d3c57", - "sha256:21d6b6bcdecee9c7ce047156d0553a30d736b8172629e4c0fcacab35ba261f3b", - "sha256:269c59e508df08be498ab9e5278addb2cc16989677a03f800b17f8a31f8c5cc7", - "sha256:36568d3cb4101a210c4d821d9101635c2ef6e06bd649335938c01eb197f50c5d", - "sha256:3cb0da4975dbade6cc2ea6b0b0b17578af266dc3f669e959648f3306af993369", - "sha256:48eadd93237c7e2739525c74cf6615dd6c1a767c839f4b0d7c12167dc0b09911", - "sha256:57add04824bca89a130f9d428ace1b003cce4061386e0ec2a1b45b554ffe7aa3", - "sha256:58ea3471b9b4b8e7952bd357e8487789da11213470be328ffb3e5b7d7690c2c7", - "sha256:5a460f4c0c30bdf9d7bef46a816671a4386a9253670a53d35c694c666544261f", - "sha256:6172481e8acffcf72042653e977281fbd807a41705a39456d92d2606d8b8c5e2", - "sha256:65b441e22d8fe93154567a30662d8539e639b78142815afcaf92b388846eb3c1", - "sha256:6ea46fef07c588fef84d1164d4788fef322b39feb2bfb2df0a0706181dff79b8", - "sha256:6f75d33f7a3720cf47371ab63ced0f0ebd1aeb6db19386ae92f8977a09be9611", - "sha256:6fdac930dfdc6227720545ec44fdb396e92d53ec227a6f5ae58cc8cb9a6cbe89", - "sha256:701290747662d2b3be49fc0de33898ecc9ce3fafe0e2887d406e24693465e5ae", - "sha256:753485bb2b18cbd11340e227e4aaf9bde3bb64f83406dfa011e92ad0231a42c9", - "sha256:7b690bc30f58931b0abd47635d93a43a82d67972e83a6511cc8adbcd7da25310", - "sha256:7efdf68e97e8fea8683bfc17f25747fefbda729b9018bc2e3221658ac41ee0bd", - "sha256:7ff89011b5ee30209b3106641e3b7b4959f10aa6e9d6f3030205123c178f605d", - "sha256:8260c002e4845a5af65908d5ee2099bcc25a16c7646c5c417fa27f1e4b844bc1", - "sha256:8e4983486d419daa45cade40874bb869976e27ba11f77fb4b9ae32417284ade7", - "sha256:8f00175f8e12e6f7d3a01309de6d7008fac94a2cdce6837ad066f0961472c9e5", - "sha256:9f869286fcefa7f8e89e38448309891ff110ad74f58a7317ec204f3d4b8ad5f5", - "sha256:a0330322c6c8123745ac7667fcc6ae3e0de3b73c15bdfaa28c788a9eaa0f50da", - "sha256:a043cff1781ddb2a0ba0e8e760a79fc5be2430957c4f2a1f51bd4528cc53178f", - "sha256:a477f652e6c417ad40387a8498d9ad827421006f156aab16f67adc9b81699a72", - "sha256:a4dbc28fede7f504b9ac65ce9cbea35585e999d63f9fa68bc73f5a75b4929302", - "sha256:afb418409e0d323c6cb5e3be7ea4d14dfbf8a07eb03ab0b0062904cacf819878", - "sha256:b0d91663d450c11404ec189ebc5a4abdf20f7c4eca5954a920427cdbf5601525", - "sha256:ba6f533b262f4ee1636b754992bb2fb349df0500d765ac9be014a375c047f4db", - "sha256:bbdbba1ec3498b17eefca14d424ee90bb95b53e1423ecb22f1c17733c3406559", - "sha256:ca8eb7a6607f8a664187a330bab9f8d11c9f81ed885e063dfbb29a130944a72a", - "sha256:cca2d4c783f985b91b98e80d09ac79b6ed3f317a729cba5ba86edfe5eb9a2d9c", - "sha256:d59e62faa03003be81aa14d37ac34ea110e5ac59c8678fd4c0daa7d8b8f42096", - "sha256:d95fc8bc177a009053548c6d851a513b2147c465a5e8fea82287ea22d6825c4e", - "sha256:dbddadca6f08212732e83a60e30a42cfc7d2695892cedea208b3c3e7131c9993", - "sha256:e13a4e81761636591f5b60104f6e1eec70832ffd9aa781db68d7ebb576970d4b", - "sha256:e28a8d9cf462e2b4c9e71abd0630f9ec245d88b976b283b0dbb4602c9ddb3938", - "sha256:e5949f3b4e207fa7901c0cc3b49470b2a3372617a47dfbc892db31c2b56af296", - "sha256:e98965c6e60d76ff63989d9400ae8e65efd67c665d785b377f438f166a57c053", - "sha256:f1993a68c0edc45895d3df350d01b0456efe79aaf309cef777762742be501f2a", - "sha256:faeebde62f0f6ad86985bec5685411260393d2eb7ba907972da56af586b644e8", - "sha256:ff09a0a1e5cef05309ac09dfc5185e8151d927bcf45470d2f540c96260f8a355" - ], - "markers": "python_version >= '3.7' and python_version < '4'", - "version": "==3.6.13" - }, "requests": { "hashes": [ "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", @@ -929,30 +459,6 @@ "markers": "python_version >= '3.7'", "version": "==2.31.0" }, - "requests-oauthlib": { - "hashes": [ - "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5", - "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.3.1" - }, - "rsa": { - "hashes": [ - "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", - "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21" - ], - "markers": "python_version >= '3.6' and python_version < '4'", - "version": "==4.9" - }, - "setuptools": { - "hashes": [ - "sha256:26ead7d1f93efc0f8c804d9fafafbe4a44b179580a7105754b245155f9af05a8", - "sha256:47c7b0c0f8fc10eec4cf1e71c6fdadf8decaa74ffa087e68cd1c20db7ad6a592" - ], - "markers": "python_version >= '3.7'", - "version": "==62.1.0" - }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", @@ -961,13 +467,6 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, - "sortedcontainers": { - "hashes": [ - "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", - "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" - ], - "version": "==2.4.0" - }, "sqlparse": { "hashes": [ "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3", @@ -976,21 +475,6 @@ "markers": "python_version >= '3.5'", "version": "==0.4.4" }, - "tablib": { - "extras": [ - "html", - "ods", - "xls", - "xlsx", - "yaml" - ], - "hashes": [ - "sha256:9821caa9eca6062ff7299fa645e737aecff982e6b2b42046928a6413c8dabfd9", - "sha256:f6661dfc45e1d4f51fa8a6239f9c8349380859a5bfaa73280645f046d6c96e33" - ], - "markers": "python_version >= '3.8'", - "version": "==3.5.0" - }, "typing-extensions": { "hashes": [ "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0", @@ -1015,14 +499,6 @@ "markers": "python_version >= '3.7'", "version": "==2.0.4" }, - "websocket-client": { - "hashes": [ - "sha256:3aad25d31284266bcfcfd1fd8a743f63282305a364b8d0948a43bd606acc652f", - "sha256:6cfc30d051ebabb73a5fa246efdcc14c8fbebbd0330f8984ac3bb6d9edd2ad03" - ], - "markers": "python_version >= '3.8'", - "version": "==1.6.3" - }, "werkzeug": { "hashes": [ "sha256:2b8c0e447b4b9dbcc85dd97b6eeb4dcbaf6c8b6c3be0bd654e25553e0a2157d8", @@ -1031,20 +507,6 @@ "markers": "python_version >= '3.8'", "version": "==2.3.7" }, - "xlrd": { - "hashes": [ - "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd", - "sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88" - ], - "version": "==2.0.1" - }, - "xlwt": { - "hashes": [ - "sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e", - "sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88" - ], - "version": "==1.3.0" - }, "zipp": { "hashes": [ "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31", diff --git a/backend/api/views.py b/backend/api/views.py index a8b6cde..ec055bc 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -1,12 +1,13 @@ import logging from codeforlife.mixins import CronMixin +from codeforlife.request import HttpRequest from common.models import UserSession from django.contrib.auth import login from django.contrib.auth.views import LoginView as _LoginView from django.contrib.sessions.models import Session, SessionManager from django.core.management import call_command -from django.http import HttpResponse, JsonResponse +from django.http import JsonResponse from rest_framework import status from rest_framework.response import Response from rest_framework.views import APIView @@ -22,6 +23,8 @@ # TODO: add 2FA logic class LoginView(_LoginView): + request: HttpRequest + def get_form_class(self): form = self.kwargs["form"] if form == "email": @@ -30,7 +33,7 @@ def get_form_class(self): return UsernameAuthForm elif form == "user-id": return UserIdAuthForm - elif form == "otp": # TODO: add 2fa logic. + elif form == "otp": return OtpAuthForm def form_valid(self, form: BaseAuthForm): @@ -45,7 +48,13 @@ def form_valid(self, form: BaseAuthForm): ) UserSession.objects.create(**user_session) - return HttpResponse() + return JsonResponse( + { + "session_auth_factors": list( + self.request.user.session.session_auth_factors + ) + } + ) def form_invalid(self, form: BaseAuthForm): return JsonResponse(form.errors, status=status.HTTP_400_BAD_REQUEST) From c1d308145f36f9dbe9109433cb07f948ff1c0853 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Wed, 27 Sep 2023 16:13:43 +0100 Subject: [PATCH 47/58] codeforlife.user --- backend/service/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/service/settings.py b/backend/service/settings.py index fcab0ea..4283518 100644 --- a/backend/service/settings.py +++ b/backend/service/settings.py @@ -27,7 +27,7 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", - "codeforlife", + "codeforlife.user", "api", "aimmo", # TODO: remove this "game", # TODO: remove this From abf35f23b2d2977a5e82e2620e27128fe9641628 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Wed, 27 Sep 2023 18:34:47 +0100 Subject: [PATCH 48/58] save session data before response --- backend/api/views.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/backend/api/views.py b/backend/api/views.py index ec055bc..4c69af7 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -37,6 +37,7 @@ def get_form_class(self): return OtpAuthForm def form_valid(self, form: BaseAuthForm): + # Create session (without data). login(self.request, form.user) # TODO: use google analytics @@ -48,10 +49,15 @@ def form_valid(self, form: BaseAuthForm): ) UserSession.objects.create(**user_session) + # Save session (with data). + self.request.session.save() + return JsonResponse( { - "session_auth_factors": list( - self.request.user.session.session_auth_factors + "session_auth_factors": ( + self.request.user.session.session_auth_factors.values_list( + "auth_factor__type", flat=True + ) ) } ) From 5e30b733615fd6038bcf69d571de8e1698fa8c2a Mon Sep 17 00:00:00 2001 From: SKairinos Date: Thu, 28 Sep 2023 09:37:34 +0100 Subject: [PATCH 49/58] return auth factors --- backend/Pipfile | 7 + backend/Pipfile.lock | 566 ++++++++++++++++++++++++++++++++++++++++++- backend/api/views.py | 2 +- 3 files changed, 564 insertions(+), 11 deletions(-) diff --git a/backend/Pipfile b/backend/Pipfile index b01817c..c8cd69d 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -4,10 +4,17 @@ verify_ssl = true name = "pypi" [packages] +# TODO: use actual version codeforlife = {ref = "otp", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} django = "==3.2.20" djangorestframework = "==3.13.1" django-cors-headers = "==4.1.0" +# https://pypi.org/user/codeforlife/ +cfl-common = "==6.37.1" # TODO: remove +codeforlife-portal = "==6.37.1" # TODO: remove +aimmo = "==2.10.6" # TODO: remove +rapid-router = "==5.11.3" # TODO: remove +phonenumbers = "==8.12.12" # TODO: remove [dev-packages] black = "*" diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 4b7a332..b358fa3 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "82b242655141e9c20e3d45f61628ec01233301b6f18912d81516956d7b9f0fad" + "sha256": "b7fc0960ca7dd5d6aff21fc72ba1b8e0457483585cffb043c77ad2df96f0d182" }, "pipfile-spec": 6, "requires": { @@ -16,6 +16,14 @@ ] }, "default": { + "aimmo": { + "hashes": [ + "sha256:b89f83586412320b147ea61b4277599732c10e7668fba5b2d0a383db6a173145", + "sha256:bd2841b24d7830096b7cc81bdf7548377d30602f1a1b3d9e9084a58b11557413" + ], + "index": "pypi", + "version": "==2.10.6" + }, "asgiref": { "hashes": [ "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e", @@ -24,6 +32,22 @@ "markers": "python_version >= '3.7'", "version": "==3.7.2" }, + "attrs": { + "hashes": [ + "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", + "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015" + ], + "markers": "python_version >= '3.7'", + "version": "==23.1.0" + }, + "cachetools": { + "hashes": [ + "sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590", + "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b" + ], + "markers": "python_version >= '3.7'", + "version": "==5.3.1" + }, "certifi": { "hashes": [ "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", @@ -34,10 +58,11 @@ }, "cfl-common": { "hashes": [ - "sha256:171e5607e7704e7f979d947226c681c1672c1f038116c0ed69b1fe57c2eb1eac", - "sha256:8fd9b61f1f6b70a1f8957ed65c5ff42fa6c6b6fedbf1b34cdeae0df563844d9f" + "sha256:24045d5550c741249a1d466cfb90149883454833accb48bbdb1aceec51e885c1", + "sha256:e28553af70dc4388fc73a6dfb4ece08e8518f21531d63213b448d24a5c0abef3" ], - "version": "==6.36.2" + "index": "pypi", + "version": "==6.37.1" }, "charset-normalizer": { "hashes": [ @@ -130,7 +155,31 @@ }, "codeforlife": { "git": "https://github.com/ocadotechnology/codeforlife-package-python.git", - "ref": "ef8b4951699afb5bd58d5fbf0aaf061cda566152" + "ref": "f9e2256e2a6e354907a5c29e225e3944ed45a32e" + }, + "codeforlife-portal": { + "hashes": [ + "sha256:3c31ac0135af0cd78ec39e1b3e32ba98e514c05d944447e504002c000ce6b334", + "sha256:63a234390da9728139de7fbe8a4da9f4ca4b4b24d37c4bed248e5d49af563e53" + ], + "index": "pypi", + "version": "==6.37.1" + }, + "defusedxml": { + "hashes": [ + "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", + "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==0.7.1" + }, + "diff-match-patch": { + "hashes": [ + "sha256:953019cdb9c9d2c9e47b5b12bcff3cf4746fc4598eb406076fa1fc27e6a1f15c", + "sha256:dce43505fb7b1b317de7195579388df0746d90db07015ed47a85e5e44930ef93" + ], + "markers": "python_version >= '3.7'", + "version": "==20230430" }, "django": { "hashes": [ @@ -140,6 +189,13 @@ "index": "pypi", "version": "==3.2.20" }, + "django-classy-tags": { + "hashes": [ + "sha256:25eb4f95afee396148683bfb4811b83b3f5729218d73ad0a3399271a6f9fcc49", + "sha256:d59d98bdf96a764dcf7a2929a86439d023b283a9152492811c7e44fc47555bc9" + ], + "version": "==2.0.0" + }, "django-cors-headers": { "hashes": [ "sha256:36a8d7a6dee6a85f872fe5916cc878a36d0812043866355438dfeda0b20b6b78", @@ -155,6 +211,13 @@ ], "version": "==7.3.1" }, + "django-csp": { + "hashes": [ + "sha256:01443a07723f9a479d498bd7bb63571aaa771e690f64bde515db6cdb76e8041a", + "sha256:01eda02ad3f10261c74131cdc0b5a6a62b7c7ad4fd017fbefb7a14776e0a9727" + ], + "version": "==3.7" + }, "django-formtools": { "hashes": [ "sha256:304fa777b8ef9e0693ce7833f885cb89ba46b0e46fc23b01176900a93f46742f", @@ -162,6 +225,21 @@ ], "version": "==2.2" }, + "django-import-export": { + "hashes": [ + "sha256:88ecaf06be06bd95d97cf34f3c911c56c012a7a81712a8956740e5bfc2465162", + "sha256:d02e31908c965d512cc6f7ef6e72935177647b15d3846050d0f094177fca0d86" + ], + "markers": "python_version >= '3.8'", + "version": "==3.3.1" + }, + "django-js-reverse": { + "hashes": [ + "sha256:2a392d169f44e30b883c30dfcfd917a14167ce8fe196c99d2385b31c90d77aa0", + "sha256:8134c2ab6307c945edfa90671ca65e85d6c1754d48566bdd6464be259cc80c30" + ], + "version": "==0.9.1" + }, "django-otp": { "hashes": [ "sha256:8ba5ab9bd2738c7321376c349d7cce49cf4404e79f6804e0a3cc462a91728e18", @@ -177,6 +255,46 @@ "markers": "python_version >= '3.7'", "version": "==6.4.0" }, + "django-pipeline": { + "hashes": [ + "sha256:26f1d344a7bf39bc92c9dc520093471d912de53abd7d22ac715e77d779a831c8", + "sha256:56c299cec0e644e77d5f928f4cebfff804b919cc10ff5c0bfaa070ff57e8da44" + ], + "version": "==2.0.8" + }, + "django-preventconcurrentlogins": { + "hashes": [ + "sha256:9cb45fcd63edeec55e5ac29bbd2ee96974dc2a72d74ab88088dbf6a1f52978e9" + ], + "version": "==0.8.2" + }, + "django-ratelimit": { + "hashes": [ + "sha256:73223d860abd5c5d7b9a807fabb39a6220068129b514be8d78044b52607ab154", + "sha256:857e797f23de948b204a31dba9d88aea3ce731b7a5d926d0240c772e19b5486f" + ], + "markers": "python_version >= '3.4'", + "version": "==3.0.1" + }, + "django-recaptcha": { + "hashes": [ + "sha256:567784963fd5400feaf92e8951d8dbbbdb4b4c48a76e225d4baa63a2c9d2cd8c" + ], + "version": "==2.0.6" + }, + "django-sekizai": { + "hashes": [ + "sha256:5c5e16845d37ce822fc655ce79ec02715191b3d03330b550997bcb842cf24fdf", + "sha256:e829f09b0d6bf01ee5cde05de1fb3faf2fbc5df66dc4dc280fbaac224ca4336f" + ], + "version": "==2.0.0" + }, + "django-treebeard": { + "hashes": [ + "sha256:83aebc34a9f06de7daaec330d858d1c47887e81be3da77e3541fe7368196dd8a" + ], + "version": "==4.3.1" + }, "django-two-factor-auth": { "hashes": [ "sha256:3fac266d12472ac66475dd737bb18f2992484313bf56acf5a2eea5e824291ee6", @@ -192,6 +310,29 @@ "index": "pypi", "version": "==3.13.1" }, + "dnspython": { + "hashes": [ + "sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01", + "sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "et-xmlfile": { + "hashes": [ + "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c", + "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada" + ], + "markers": "python_version >= '3.6'", + "version": "==1.1.0" + }, + "eventlet": { + "hashes": [ + "sha256:27ae41fad9deed9bbf4166f3e3b65acc15d524d42210a518e5877da85a6b8c5d", + "sha256:b36ec2ecc003de87fc87b93197d77fea528aa0f9204a34fdf3b2f8d0f01e017b" + ], + "version": "==0.31.0" + }, "flask": { "hashes": [ "sha256:7eb373984bf1c770023fce9db164ed0c3353cd0b53f130f4693da0ca756a2e6d", @@ -200,6 +341,92 @@ "markers": "python_version >= '3.7'", "version": "==2.2.3" }, + "google-auth": { + "hashes": [ + "sha256:9800802266366a2a87890fb2d04923fc0c0d4368af0b86db18edd94a62386ea1", + "sha256:d38bdf4fa1e7c5a35e574861bce55784fd08afadb4e48f99f284f1e487ce702d" + ], + "markers": "python_version >= '3.7'", + "version": "==2.23.1" + }, + "greenlet": { + "hashes": [ + "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a", + "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a", + "sha256:1087300cf9700bbf455b1b97e24db18f2f77b55302a68272c56209d5587c12d1", + "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43", + "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33", + "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8", + "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088", + "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca", + "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343", + "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645", + "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db", + "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df", + "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3", + "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86", + "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2", + "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a", + "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf", + "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7", + "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394", + "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40", + "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3", + "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6", + "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74", + "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0", + "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3", + "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91", + "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5", + "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9", + "sha256:8512a0c38cfd4e66a858ddd1b17705587900dd760c6003998e9472b77b56d417", + "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8", + "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b", + "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6", + "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb", + "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73", + "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b", + "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df", + "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9", + "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f", + "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0", + "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857", + "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a", + "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249", + "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30", + "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292", + "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b", + "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d", + "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b", + "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c", + "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca", + "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7", + "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75", + "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae", + "sha256:d4606a527e30548153be1a9f155f4e283d109ffba663a15856089fb55f933e47", + "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b", + "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470", + "sha256:d967650d3f56af314b72df7089d96cda1083a7fc2da05b375d2bc48c82ab3f3c", + "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564", + "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9", + "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099", + "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0", + "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5", + "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19", + "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1", + "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==2.0.2" + }, + "hypothesis": { + "hashes": [ + "sha256:6a3471ff74864ab04a0650c75500ef15f2f4a901d49ccbb7cbec668365736688", + "sha256:989162a9e0715c624b99ad9b2b4206765879b40eb51eef17b1e37de3e898370a" + ], + "markers": "python_version >= '3.6'", + "version": "==5.41.3" + }, "idna": { "hashes": [ "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", @@ -232,6 +459,31 @@ "markers": "python_version >= '3.7'", "version": "==3.1.2" }, + "kubernetes": { + "hashes": [ + "sha256:5854b0c508e8d217ca205591384ab58389abdae608576f9c9afc35a3c76a366c", + "sha256:e3db6800abf7e36c38d2629b5cb6b74d10988ee0cba6fba45595a7cbe60c0042" + ], + "markers": "python_version >= '3.6'", + "version": "==26.1.0" + }, + "libsass": { + "hashes": [ + "sha256:081e256ab3c5f3f09c7b8dea3bf3bf5e64a97c6995fd9eea880639b3f93a9f9a", + "sha256:3ab5ad18e47db560f4f0c09e3d28cf3bb1a44711257488ac2adad69f4f7f8425", + "sha256:65455a2728b696b62100eb5932604aa13a29f4ac9a305d95773c14aaa7200aaf", + "sha256:89c5ce497fcf3aba1dd1b19aae93b99f68257e5f2026b731b00a872f13324c7f", + "sha256:f1efc1b612299c88aec9e39d6ca0c266d360daa5b19d9430bdeaffffa86993f9" + ], + "markers": "python_version >= '3.6'", + "version": "==0.22.0" + }, + "markuppy": { + "hashes": [ + "sha256:1adee2c0a542af378fe84548ff6f6b0168f3cb7f426b46961038a2bcfaad0d5f" + ], + "version": "==1.14" + }, "markupsafe": { "hashes": [ "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e", @@ -298,6 +550,14 @@ "markers": "python_version >= '3.7'", "version": "==2.1.3" }, + "more-itertools": { + "hashes": [ + "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced", + "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713" + ], + "markers": "python_version >= '3.5'", + "version": "==8.7.0" + }, "numpy": { "hashes": [ "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f", @@ -332,6 +592,29 @@ "markers": "python_version >= '3.8'", "version": "==1.24.4" }, + "oauthlib": { + "hashes": [ + "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", + "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918" + ], + "markers": "python_version >= '3.6'", + "version": "==3.2.2" + }, + "odfpy": { + "hashes": [ + "sha256:db766a6e59c5103212f3cc92ec8dd50a0f3a02790233ed0b52148b70d3c438ec", + "sha256:fc3b8d1bc098eba4a0fda865a76d9d1e577c4ceec771426bcb169a82c5e9dfe0" + ], + "version": "==1.4.1" + }, + "openpyxl": { + "hashes": [ + "sha256:a6f5977418eff3b2d5500d54d9db50c8277a368436f4e4f8ddb1be3422870184", + "sha256:f91456ead12ab3c6c2e9491cf33ba6d08357d802192379bb482f1033ade496f5" + ], + "markers": "python_version >= '3.6'", + "version": "==3.1.2" + }, "pandas": { "hashes": [ "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682", @@ -371,6 +654,90 @@ "markers": "python_version >= '3.8'", "version": "==0.4.0" }, + "phonenumbers": { + "hashes": [ + "sha256:23944f9e628f32a975d3b221b6d76e6ba8ae618d53cb3d82fc23d9e100a59b29", + "sha256:70aa98a50ba7bc7f6bf17851f806c927107e7c44e7d21eb46bdbec07b99d23ae" + ], + "index": "pypi", + "version": "==8.12.12" + }, + "pillow": { + "hashes": [ + "sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff", + "sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f", + "sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21", + "sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635", + "sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a", + "sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f", + "sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1", + "sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d", + "sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db", + "sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849", + "sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7", + "sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876", + "sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3", + "sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317", + "sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91", + "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d", + "sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b", + "sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd", + "sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed", + "sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500", + "sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7", + "sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a", + "sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a", + "sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0", + "sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf", + "sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f", + "sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1", + "sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088", + "sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971", + "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a", + "sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205", + "sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54", + "sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08", + "sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21", + "sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d", + "sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08", + "sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e", + "sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf", + "sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b", + "sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145", + "sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2", + "sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d", + "sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d", + "sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf", + "sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad", + "sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d", + "sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1", + "sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4", + "sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2", + "sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19", + "sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37", + "sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4", + "sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68", + "sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1" + ], + "markers": "python_version >= '3.8'", + "version": "==10.0.1" + }, + "pyasn1": { + "hashes": [ + "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57", + "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==0.5.0" + }, + "pyasn1-modules": { + "hashes": [ + "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c", + "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==0.3.0" + }, "pydantic": { "hashes": [ "sha256:01aea3a42c13f2602b7ecbbea484a98169fb568ebd9e247593ea05f01b884b2e", @@ -413,6 +780,14 @@ "markers": "python_version >= '3.7'", "version": "==1.10.7" }, + "pyhamcrest": { + "hashes": [ + "sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316", + "sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29" + ], + "markers": "python_version >= '3.5'", + "version": "==2.0.2" + }, "pyjwt": { "hashes": [ "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd", @@ -421,6 +796,14 @@ "markers": "python_version >= '3.7'", "version": "==2.6.0" }, + "pyotp": { + "hashes": [ + "sha256:346b6642e0dbdde3b4ff5a930b664ca82abfa116356ed48cc42c7d6590d36f63", + "sha256:81c2e5865b8ac55e825b0358e496e1d9387c811e85bb40e71a3b29b288963612" + ], + "markers": "python_version >= '3.7'", + "version": "==2.9.0" + }, "pypng": { "hashes": [ "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c", @@ -433,7 +816,7 @@ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.8.2" }, "pytz": { @@ -443,6 +826,41 @@ ], "version": "==2023.3.post1" }, + "pyyaml": { + "hashes": [ + "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", + "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696", + "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393", + "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77", + "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922", + "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5", + "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8", + "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10", + "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc", + "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", + "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", + "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", + "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347", + "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", + "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541", + "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", + "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", + "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc", + "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", + "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa", + "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", + "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122", + "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", + "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", + "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", + "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc", + "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247", + "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6", + "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==5.4.1" + }, "qrcode": { "hashes": [ "sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a", @@ -451,6 +869,65 @@ "markers": "python_version >= '3.7'", "version": "==7.4.2" }, + "rapid-router": { + "hashes": [ + "sha256:b3b0b9dd449775aaac8b6dcc05601f8e0d3d1805100ba80a086cc8a3c2661526", + "sha256:bb88bb75e0f743ebedcfd44fbbfba5e1f63e5464c2d7d7bd02d3c838486f78ac" + ], + "index": "pypi", + "version": "==5.11.3" + }, + "reportlab": { + "hashes": [ + "sha256:0b94e4f65a5f77a631cc010c9a7892d69e33f3251b760639dcc76420e138ce95", + "sha256:11a71c314183532d889ad4b3941f61c3fe4bfdda769c768a7f02d93cb69dd1bb", + "sha256:149718c3eaee937f28094325f0dd9ae1add3172c2dacbb93ff5403f37c9d3c57", + "sha256:21d6b6bcdecee9c7ce047156d0553a30d736b8172629e4c0fcacab35ba261f3b", + "sha256:269c59e508df08be498ab9e5278addb2cc16989677a03f800b17f8a31f8c5cc7", + "sha256:36568d3cb4101a210c4d821d9101635c2ef6e06bd649335938c01eb197f50c5d", + "sha256:3cb0da4975dbade6cc2ea6b0b0b17578af266dc3f669e959648f3306af993369", + "sha256:48eadd93237c7e2739525c74cf6615dd6c1a767c839f4b0d7c12167dc0b09911", + "sha256:57add04824bca89a130f9d428ace1b003cce4061386e0ec2a1b45b554ffe7aa3", + "sha256:58ea3471b9b4b8e7952bd357e8487789da11213470be328ffb3e5b7d7690c2c7", + "sha256:5a460f4c0c30bdf9d7bef46a816671a4386a9253670a53d35c694c666544261f", + "sha256:6172481e8acffcf72042653e977281fbd807a41705a39456d92d2606d8b8c5e2", + "sha256:65b441e22d8fe93154567a30662d8539e639b78142815afcaf92b388846eb3c1", + "sha256:6ea46fef07c588fef84d1164d4788fef322b39feb2bfb2df0a0706181dff79b8", + "sha256:6f75d33f7a3720cf47371ab63ced0f0ebd1aeb6db19386ae92f8977a09be9611", + "sha256:6fdac930dfdc6227720545ec44fdb396e92d53ec227a6f5ae58cc8cb9a6cbe89", + "sha256:701290747662d2b3be49fc0de33898ecc9ce3fafe0e2887d406e24693465e5ae", + "sha256:753485bb2b18cbd11340e227e4aaf9bde3bb64f83406dfa011e92ad0231a42c9", + "sha256:7b690bc30f58931b0abd47635d93a43a82d67972e83a6511cc8adbcd7da25310", + "sha256:7efdf68e97e8fea8683bfc17f25747fefbda729b9018bc2e3221658ac41ee0bd", + "sha256:7ff89011b5ee30209b3106641e3b7b4959f10aa6e9d6f3030205123c178f605d", + "sha256:8260c002e4845a5af65908d5ee2099bcc25a16c7646c5c417fa27f1e4b844bc1", + "sha256:8e4983486d419daa45cade40874bb869976e27ba11f77fb4b9ae32417284ade7", + "sha256:8f00175f8e12e6f7d3a01309de6d7008fac94a2cdce6837ad066f0961472c9e5", + "sha256:9f869286fcefa7f8e89e38448309891ff110ad74f58a7317ec204f3d4b8ad5f5", + "sha256:a0330322c6c8123745ac7667fcc6ae3e0de3b73c15bdfaa28c788a9eaa0f50da", + "sha256:a043cff1781ddb2a0ba0e8e760a79fc5be2430957c4f2a1f51bd4528cc53178f", + "sha256:a477f652e6c417ad40387a8498d9ad827421006f156aab16f67adc9b81699a72", + "sha256:a4dbc28fede7f504b9ac65ce9cbea35585e999d63f9fa68bc73f5a75b4929302", + "sha256:afb418409e0d323c6cb5e3be7ea4d14dfbf8a07eb03ab0b0062904cacf819878", + "sha256:b0d91663d450c11404ec189ebc5a4abdf20f7c4eca5954a920427cdbf5601525", + "sha256:ba6f533b262f4ee1636b754992bb2fb349df0500d765ac9be014a375c047f4db", + "sha256:bbdbba1ec3498b17eefca14d424ee90bb95b53e1423ecb22f1c17733c3406559", + "sha256:ca8eb7a6607f8a664187a330bab9f8d11c9f81ed885e063dfbb29a130944a72a", + "sha256:cca2d4c783f985b91b98e80d09ac79b6ed3f317a729cba5ba86edfe5eb9a2d9c", + "sha256:d59e62faa03003be81aa14d37ac34ea110e5ac59c8678fd4c0daa7d8b8f42096", + "sha256:d95fc8bc177a009053548c6d851a513b2147c465a5e8fea82287ea22d6825c4e", + "sha256:dbddadca6f08212732e83a60e30a42cfc7d2695892cedea208b3c3e7131c9993", + "sha256:e13a4e81761636591f5b60104f6e1eec70832ffd9aa781db68d7ebb576970d4b", + "sha256:e28a8d9cf462e2b4c9e71abd0630f9ec245d88b976b283b0dbb4602c9ddb3938", + "sha256:e5949f3b4e207fa7901c0cc3b49470b2a3372617a47dfbc892db31c2b56af296", + "sha256:e98965c6e60d76ff63989d9400ae8e65efd67c665d785b377f438f166a57c053", + "sha256:f1993a68c0edc45895d3df350d01b0456efe79aaf309cef777762742be501f2a", + "sha256:faeebde62f0f6ad86985bec5685411260393d2eb7ba907972da56af586b644e8", + "sha256:ff09a0a1e5cef05309ac09dfc5185e8151d927bcf45470d2f540c96260f8a355" + ], + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==3.6.13" + }, "requests": { "hashes": [ "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", @@ -459,14 +936,45 @@ "markers": "python_version >= '3.7'", "version": "==2.31.0" }, + "requests-oauthlib": { + "hashes": [ + "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5", + "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.3.1" + }, + "rsa": { + "hashes": [ + "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", + "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21" + ], + "markers": "python_version >= '3.6' and python_version < '4'", + "version": "==4.9" + }, + "setuptools": { + "hashes": [ + "sha256:26ead7d1f93efc0f8c804d9fafafbe4a44b179580a7105754b245155f9af05a8", + "sha256:47c7b0c0f8fc10eec4cf1e71c6fdadf8decaa74ffa087e68cd1c20db7ad6a592" + ], + "markers": "python_version >= '3.7'", + "version": "==62.1.0" + }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.16.0" }, + "sortedcontainers": { + "hashes": [ + "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", + "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" + ], + "version": "==2.4.0" + }, "sqlparse": { "hashes": [ "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3", @@ -475,6 +983,21 @@ "markers": "python_version >= '3.5'", "version": "==0.4.4" }, + "tablib": { + "extras": [ + "html", + "ods", + "xls", + "xlsx", + "yaml" + ], + "hashes": [ + "sha256:9821caa9eca6062ff7299fa645e737aecff982e6b2b42046928a6413c8dabfd9", + "sha256:f6661dfc45e1d4f51fa8a6239f9c8349380859a5bfaa73280645f046d6c96e33" + ], + "markers": "python_version >= '3.8'", + "version": "==3.5.0" + }, "typing-extensions": { "hashes": [ "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0", @@ -493,11 +1016,19 @@ }, "urllib3": { "hashes": [ - "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11", - "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4" + "sha256:13abf37382ea2ce6fb744d4dad67838eec857c9f4f57009891805e0b5e123594", + "sha256:ef16afa8ba34a1f989db38e1dbbe0c302e4289a47856990d0682e374563ce35e" ], "markers": "python_version >= '3.7'", - "version": "==2.0.4" + "version": "==2.0.5" + }, + "websocket-client": { + "hashes": [ + "sha256:3aad25d31284266bcfcfd1fd8a743f63282305a364b8d0948a43bd606acc652f", + "sha256:6cfc30d051ebabb73a5fa246efdcc14c8fbebbd0330f8984ac3bb6d9edd2ad03" + ], + "markers": "python_version >= '3.8'", + "version": "==1.6.3" }, "werkzeug": { "hashes": [ @@ -507,6 +1038,21 @@ "markers": "python_version >= '3.8'", "version": "==2.3.7" }, + "xlrd": { + "hashes": [ + "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd", + "sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==2.0.1" + }, + "xlwt": { + "hashes": [ + "sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e", + "sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88" + ], + "version": "==1.3.0" + }, "zipp": { "hashes": [ "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31", diff --git a/backend/api/views.py b/backend/api/views.py index 4c69af7..0dd6a2c 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -54,7 +54,7 @@ def form_valid(self, form: BaseAuthForm): return JsonResponse( { - "session_auth_factors": ( + "auth_factors": list( self.request.user.session.session_auth_factors.values_list( "auth_factor__type", flat=True ) From ced37591ada5f3b740dd1bff6be77cb228bb2280 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Thu, 28 Sep 2023 09:43:43 +0100 Subject: [PATCH 50/58] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 511480b..4985df9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# codeforlife-service-template +# codeforlife-sso -[Frontend Docs](docs/frontend) +This repo contains CFL's Single Sign-On (SSO) service. This will be responsible for authenticating users. From db1bbf48c2504db98505de1a2f1e83b6f92a8065 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Thu, 28 Sep 2023 17:54:58 +0100 Subject: [PATCH 51/58] update lock --- backend/Pipfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index dd1bd06..78d2591 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -816,7 +816,7 @@ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.8.2" }, "pytz": { @@ -965,7 +965,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.16.0" }, "sortedcontainers": { From a539aa43444aa629ab320c4f4e01933fb0c9c1bb Mon Sep 17 00:00:00 2001 From: SKairinos Date: Fri, 29 Sep 2023 14:11:36 +0100 Subject: [PATCH 52/58] support backup token authentication --- backend/api/forms.py | 7 +++++++ backend/api/urls.py | 2 +- backend/api/views.py | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/backend/api/forms.py b/backend/api/forms.py index e6cb0b1..85a7db3 100644 --- a/backend/api/forms.py +++ b/backend/api/forms.py @@ -63,6 +63,13 @@ def get_invalid_login_error_message(self): ) +class TokenAuthForm(BaseAuthForm): + token = forms.CharField(min_length=8, max_length=8) + + def get_invalid_login_error_message(self): + return "Must be exactly 8 characters. A token can only be used once." + + class UsernameAuthForm(BaseAuthForm): username = UsernameField() password = forms.CharField(strip=False) diff --git a/backend/api/urls.py b/backend/api/urls.py index 8113e66..5619bd5 100644 --- a/backend/api/urls.py +++ b/backend/api/urls.py @@ -8,7 +8,7 @@ include( [ re_path( - r"^login/(?Pemail|username|user-id|otp)/$", + r"^login/(?Pemail|username|user-id|otp|token)/$", LoginView.as_view(), name="login", ), diff --git a/backend/api/views.py b/backend/api/views.py index 0dd6a2c..6cd9e13 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -16,6 +16,7 @@ BaseAuthForm, EmailAuthForm, OtpAuthForm, + TokenAuthForm, UserIdAuthForm, UsernameAuthForm, ) @@ -35,6 +36,8 @@ def get_form_class(self): return UserIdAuthForm elif form == "otp": return OtpAuthForm + elif form == "token": + return TokenAuthForm def form_valid(self, form: BaseAuthForm): # Create session (without data). From db83876c7c118f9d8663305479b274f367e0c4d1 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Fri, 29 Sep 2023 16:31:50 +0100 Subject: [PATCH 53/58] test otp --- backend/Pipfile.lock | 2 +- backend/api/tests/test_views.py | 43 +++++++++++++++++++++++++++++++++ backend/app.yaml | 2 +- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 78d2591..297c54a 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -155,7 +155,7 @@ }, "codeforlife": { "git": "https://github.com/ocadotechnology/codeforlife-package-python.git", - "ref": "85cd22c040104bc58172275f1c0a71b842aae78d" + "ref": "89f39c158a391571d6c928932c7c6391d22da030" }, "codeforlife-portal": { "hashes": [ diff --git a/backend/api/tests/test_views.py b/backend/api/tests/test_views.py index ceee7bf..ff9cc13 100644 --- a/backend/api/tests/test_views.py +++ b/backend/api/tests/test_views.py @@ -1,7 +1,50 @@ from unittest.mock import Mock, patch +import pyotp from codeforlife.tests import CronTestCase +from codeforlife.user.models import AuthFactor, User +from django.test import TestCase from django.urls import reverse +from django.utils import timezone + + +class TestLoginView(TestCase): + def setUp(self): + self.user = User.objects.get(id=2) + + def test_post__otp(self): + AuthFactor.objects.create( + user=self.user, + type=AuthFactor.Type.OTP, + ) + + response = self.client.post( + reverse("login", kwargs={"form": "email"}), + data={ + "email": self.user.email, + "password": "Password1", + }, + ) + + assert response.status_code == 200 + self.assertDictEqual( + response.json(), {"auth_factors": [AuthFactor.Type.OTP]} + ) + + self.user.userprofile.otp_secret = pyotp.random_base32() + self.user.userprofile.save() + + totp = pyotp.TOTP(self.user.userprofile.otp_secret) + + now = timezone.now() + with patch.object(timezone, "now", return_value=now): + response = self.client.post( + reverse("login", kwargs={"form": "otp"}), + data={"otp": totp.at(now)}, + ) + + assert response.status_code == 200 + self.assertDictEqual(response.json(), {"auth_factors": []}) class TestClearExpiredView(CronTestCase): diff --git a/backend/app.yaml b/backend/app.yaml index 9381239..4e9114f 100644 --- a/backend/app.yaml +++ b/backend/app.yaml @@ -11,5 +11,5 @@ env_variables: SECRET_KEY: "REPLACE_ME" SERVICE_NAME: "REPLACE_ME" SERVICE_PROTOCOL: "https" - SERVICE_DOMAIN: "codeforlife.education" + SERVICE_DOMAIN: "www.codeforlife.education" SERVICE_PORT: "443" From c97dd4f7aea002b1e662a3794507f3ade158a91d Mon Sep 17 00:00:00 2001 From: SKairinos Date: Fri, 29 Sep 2023 17:03:00 +0100 Subject: [PATCH 54/58] remove users expired sessions --- backend/api/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/api/views.py b/backend/api/views.py index 6cd9e13..6842c49 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -40,6 +40,9 @@ def get_form_class(self): return TokenAuthForm def form_valid(self, form: BaseAuthForm): + # Clear expired sessions. + self.request.session.clear_expired(form.user.id) + # Create session (without data). login(self.request, form.user) From 425410ff75585aa9804aec52438a3125f8159a2f Mon Sep 17 00:00:00 2001 From: SKairinos Date: Fri, 29 Sep 2023 17:08:06 +0100 Subject: [PATCH 55/58] update lock --- backend/Pipfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 297c54a..6a68e35 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -155,7 +155,7 @@ }, "codeforlife": { "git": "https://github.com/ocadotechnology/codeforlife-package-python.git", - "ref": "89f39c158a391571d6c928932c7c6391d22da030" + "ref": "e98ef041ee93534a54b8e14b44fe44655146a2fe" }, "codeforlife-portal": { "hashes": [ From 9fba8a840388890c4834c1cc67dd6746767969cd Mon Sep 17 00:00:00 2001 From: SKairinos Date: Fri, 29 Sep 2023 17:17:39 +0100 Subject: [PATCH 56/58] fix mock --- backend/api/tests/test_views.py | 12 ++++++------ backend/api/views.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/api/tests/test_views.py b/backend/api/tests/test_views.py index ff9cc13..e58dbca 100644 --- a/backend/api/tests/test_views.py +++ b/backend/api/tests/test_views.py @@ -1,8 +1,9 @@ -from unittest.mock import Mock, patch +from unittest.mock import patch import pyotp from codeforlife.tests import CronTestCase from codeforlife.user.models import AuthFactor, User +from django.core import management from django.test import TestCase from django.urls import reverse from django.utils import timezone @@ -48,8 +49,7 @@ def test_post__otp(self): class TestClearExpiredView(CronTestCase): - @patch("django.core.management.call_command") - def test_clear_expired_view(self, call_command: Mock): - self.client.get(reverse("clear-expired-sessions")) - - call_command.assert_called_once_with("clearsessions") + def test_clear_expired_view(self): + with patch.object(management, "call_command") as call_command: + self.client.get(reverse("clear-expired-sessions")) + call_command.assert_called_once_with("clearsessions") diff --git a/backend/api/views.py b/backend/api/views.py index 6842c49..7b9d290 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -6,7 +6,7 @@ from django.contrib.auth import login from django.contrib.auth.views import LoginView as _LoginView from django.contrib.sessions.models import Session, SessionManager -from django.core.management import call_command +from django.core import management from django.http import JsonResponse from rest_framework import status from rest_framework.response import Response @@ -82,7 +82,7 @@ def get(self, request): # Clears expired sessions. # https://docs.djangoproject.com/en/3.2/ref/django-admin/#clearsessions - call_command("clearsessions") + management.call_command("clearsessions") after_session_count = session_objects.count() logging.info(f"Session count after clearance: {after_session_count}") From ca6f6a2c090077ed870c0b3efe25b896b9798773 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Mon, 2 Oct 2023 10:47:23 +0100 Subject: [PATCH 57/58] use new cfl package --- backend/Pipfile | 2 +- backend/Pipfile.lock | 14 +++++++------- backend/api/forms.py | 14 +++++++------- backend/api/urls.py | 2 +- backend/api/views.py | 6 +++--- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/backend/Pipfile b/backend/Pipfile index c8cd69d..fdf1c45 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -5,7 +5,7 @@ name = "pypi" [packages] # TODO: use actual version -codeforlife = {ref = "otp", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} +codeforlife = {ref = "v0.8.0", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} django = "==3.2.20" djangorestframework = "==3.13.1" django-cors-headers = "==4.1.0" diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 6a68e35..520d772 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "b7fc0960ca7dd5d6aff21fc72ba1b8e0457483585cffb043c77ad2df96f0d182" + "sha256": "9b5dd7292210fab043685afe18e5fc517103bc85902a80699ebf09b26c760ee3" }, "pipfile-spec": 6, "requires": { @@ -155,7 +155,7 @@ }, "codeforlife": { "git": "https://github.com/ocadotechnology/codeforlife-package-python.git", - "ref": "e98ef041ee93534a54b8e14b44fe44655146a2fe" + "ref": "5fb23069bb2ca1ccd9a1e9e5d3db1841d2758fd1" }, "codeforlife-portal": { "hashes": [ @@ -816,7 +816,7 @@ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.8.2" }, "pytz": { @@ -965,7 +965,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, "sortedcontainers": { @@ -1125,11 +1125,11 @@ }, "packaging": { "hashes": [ - "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", - "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" ], "markers": "python_version >= '3.7'", - "version": "==23.1" + "version": "==23.2" }, "pathspec": { "hashes": [ diff --git a/backend/api/forms.py b/backend/api/forms.py index 85a7db3..3f74a64 100644 --- a/backend/api/forms.py +++ b/backend/api/forms.py @@ -52,6 +52,13 @@ def get_invalid_login_error_message(self): return "Please enter the correct one-time password." +class OtpBypassTokenAuthForm(BaseAuthForm): + token = forms.CharField(min_length=8, max_length=8) + + def get_invalid_login_error_message(self): + return "Must be exactly 8 characters. A token can only be used once." + + class EmailAuthForm(BaseAuthForm): email = forms.EmailField() password = forms.CharField(strip=False) @@ -63,13 +70,6 @@ def get_invalid_login_error_message(self): ) -class TokenAuthForm(BaseAuthForm): - token = forms.CharField(min_length=8, max_length=8) - - def get_invalid_login_error_message(self): - return "Must be exactly 8 characters. A token can only be used once." - - class UsernameAuthForm(BaseAuthForm): username = UsernameField() password = forms.CharField(strip=False) diff --git a/backend/api/urls.py b/backend/api/urls.py index 5619bd5..6df0a75 100644 --- a/backend/api/urls.py +++ b/backend/api/urls.py @@ -8,7 +8,7 @@ include( [ re_path( - r"^login/(?Pemail|username|user-id|otp|token)/$", + r"^login/(?Pemail|username|user-id|otp|otp-bypass-token)/$", LoginView.as_view(), name="login", ), diff --git a/backend/api/views.py b/backend/api/views.py index 7b9d290..709e324 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -16,7 +16,7 @@ BaseAuthForm, EmailAuthForm, OtpAuthForm, - TokenAuthForm, + OtpBypassTokenAuthForm, UserIdAuthForm, UsernameAuthForm, ) @@ -36,8 +36,8 @@ def get_form_class(self): return UserIdAuthForm elif form == "otp": return OtpAuthForm - elif form == "token": - return TokenAuthForm + elif form == "otp-bypass-token": + return OtpBypassTokenAuthForm def form_valid(self, form: BaseAuthForm): # Clear expired sessions. From c4007b3b89df774ce4c71680f338c15c30bde6be Mon Sep 17 00:00:00 2001 From: SKairinos Date: Mon, 2 Oct 2023 10:52:04 +0100 Subject: [PATCH 58/58] remove todo --- backend/Pipfile | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/Pipfile b/backend/Pipfile index fdf1c45..162707b 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -4,7 +4,6 @@ verify_ssl = true name = "pypi" [packages] -# TODO: use actual version codeforlife = {ref = "v0.8.0", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"} django = "==3.2.20" djangorestframework = "==3.13.1"