From a52d765bd876ad746073c2e480dada9468766568 Mon Sep 17 00:00:00 2001 From: faucomte97 Date: Fri, 1 Nov 2024 22:51:03 +0000 Subject: [PATCH 01/14] feat: Upgrade to Django 4.2 --- Pipfile | 7 +- Pipfile.lock | 486 ++++++++++++++++++++------------------------ cfl_common/setup.py | 13 +- setup.py | 19 +- 4 files changed, 236 insertions(+), 289 deletions(-) diff --git a/Pipfile b/Pipfile index 58e9c901e..0d7fc8c9a 100644 --- a/Pipfile +++ b/Pipfile @@ -9,14 +9,13 @@ codeforlife-portal = {path = ".", editable = true} [dev-packages] black = "*" -django-import-export = "*" -django-selenium-clean = "==1.0.0" -django-test-migrations = "==1.2.0" +django-selenium-clean = "==1.0.1" +django-test-migrations = "==1.4.0" isort = "*" PyPDF2 = "==2.10.6" pytest = "==8.*" pytest-cov = "*" -pytest-django = "==4.5.2" +pytest-django = "==4.8.0" pytest-mock = "*" pytest-order = "*" pytest-xdist = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 060009d7f..907e43b2f 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "164e3bef474a2fa70671ac95377be239fd0098883de8d05b2991a0ce18c424fd" + "sha256": "0274e773964a9e94171cb04ba5c7e996c301fa10fd4808c4c9dba2a11964e642" }, "pipfile-spec": 6, "requires": { @@ -153,62 +153,65 @@ }, "diff-match-patch": { "hashes": [ - "sha256:953019cdb9c9d2c9e47b5b12bcff3cf4746fc4598eb406076fa1fc27e6a1f15c", - "sha256:dce43505fb7b1b317de7195579388df0746d90db07015ed47a85e5e44930ef93" + "sha256:93cea333fb8b2bc0d181b0de5e16df50dd344ce64828226bda07728818936782", + "sha256:beae57a99fa48084532935ee2968b8661db861862ec82c6f21f4acdd6d835073" ], "markers": "python_version >= '3.7'", - "version": "==20230430" + "version": "==20241021" }, "django": { "hashes": [ - "sha256:7ca38a78654aee72378594d63e51636c04b8e28574f5505dff630895b5472777", - "sha256:a52ea7fcf280b16f7b739cec38fa6d3f8953a5456986944c3ca97e79882b4e38" + "sha256:1ddc333a16fc139fd253035a1606bb24261951bbc3a6ca256717fa06cc41a898", + "sha256:6f1616c2786c408ce86ab7e10f792b8f15742f7b7b7460243929cb371e7f1dad" ], - "markers": "python_version >= '3.6'", - "version": "==3.2.25" + "markers": "python_version >= '3.8'", + "version": "==4.2.16" }, "django-classy-tags": { "hashes": [ - "sha256:25eb4f95afee396148683bfb4811b83b3f5729218d73ad0a3399271a6f9fcc49", - "sha256:d59d98bdf96a764dcf7a2929a86439d023b283a9152492811c7e44fc47555bc9" + "sha256:1c784cf1bac49c20a77b8f7d1541867c64076642a160a847ff449588d4e01e55", + "sha256:c8d9d1aa2fa6e71c4d866df4dd11d23a69b8d25bbb750b2490a17b161774ee59" ], - "version": "==2.0.0" + "markers": "python_version >= '3.8'", + "version": "==4.1.0" }, "django-countries": { "hashes": [ - "sha256:5a4ee958f77810bcc38ae96605e47d76a707e81f53cf2938743ef45faafd2fce", - "sha256:b6b439cc5c7e766ec2335615160fdcebb9f2774ccc17aaa5e173306832d77594" + "sha256:1ed20842fe0f6194f91faca21076649513846a8787c9eb5aeec3cbe1656b8acc", + "sha256:c772d4e3e54afcc5f97a018544e96f246c6d9f1db51898ab0c15cd57e19437cf" ], - "version": "==7.3.1" + "version": "==7.6.1" }, "django-csp": { "hashes": [ - "sha256:01443a07723f9a479d498bd7bb63571aaa771e690f64bde515db6cdb76e8041a", - "sha256:01eda02ad3f10261c74131cdc0b5a6a62b7c7ad4fd017fbefb7a14776e0a9727" + "sha256:19b2978b03fcd73517d7d67acbc04fbbcaec0facc3e83baa502965892d1e0719", + "sha256:ef0f1a9f7d8da68ae6e169c02e9ac661c0ecf04db70e0d1d85640512a68471c0" ], - "version": "==3.7" + "version": "==3.8" }, "django-formtools": { "hashes": [ - "sha256:304fa777b8ef9e0693ce7833f885cb89ba46b0e46fc23b01176900a93f46742f", - "sha256:c5272c03c1cd51b2375abf7397a199a3148a9fbbf2f100e186467a84025d13b2" + "sha256:47cb34552c6efca088863d693284d04fc36eaaf350eb21e1a1d935e0df523c93", + "sha256:bce9b64eda52cc1eef6961cc649cf75aacd1a707c2fff08d6c3efcbc8e7e761a" ], - "version": "==2.2" + "markers": "python_version >= '3.8'", + "version": "==2.5.1" }, "django-import-export": { "hashes": [ - "sha256:16ecc5a9f0df46bde6eb278a3e65ebda0ee1db55656f36440e9fb83f40ab85a3", - "sha256:730ae2443a02b1ba27d8dba078a27ae9123adfcabb78161b4f130843607b3df9" + "sha256:6a616046498b44bf4291610609615b00101bb2b9c4701b59b78edfaa5552aa7b", + "sha256:bb8482bd8b124f1f47e58a877e34358820d09293c65fe36c21e9dbcdee170d4d" ], - "markers": "python_version >= '3.8'", - "version": "==4.1.1" + "markers": "python_version >= '3.9'", + "version": "==4.2.0" }, "django-otp": { "hashes": [ - "sha256:8ba5ab9bd2738c7321376c349d7cce49cf4404e79f6804e0a3cc462a91728e18", - "sha256:f523fb9dec420f28a29d3e2ad72ac06f64588956ed4f2b5b430d8e957ebb8287" + "sha256:0d9497ea4fb13fc04d50b49aa53dd1c740fe4bc5dde0ca27fb394f84e5da7bac", + "sha256:783dea669ac0eaf5cd336f73839443584ee665af427a84175cca5a9d620366db" ], - "version": "==1.0.2" + "markers": "python_version >= '3.7'", + "version": "==1.5.4" }, "django-phonenumber-field": { "hashes": [ @@ -220,10 +223,10 @@ }, "django-pipeline": { "hashes": [ - "sha256:26f1d344a7bf39bc92c9dc520093471d912de53abd7d22ac715e77d779a831c8", - "sha256:56c299cec0e644e77d5f928f4cebfff804b919cc10ff5c0bfaa070ff57e8da44" + "sha256:36470aeced4889694b899a995bcd2eda512f45b61ac68d50adc5863c8bad5ee4", + "sha256:aa973adeb480762f9d795818140da32e0348b7cd85a90b316205b8e966f986b7" ], - "version": "==2.0.8" + "version": "==3.1.0" }, "django-preventconcurrentlogins": { "hashes": [ @@ -241,45 +244,41 @@ }, "django-recaptcha": { "hashes": [ - "sha256:567784963fd5400feaf92e8951d8dbbbdb4b4c48a76e225d4baa63a2c9d2cd8c" + "sha256:0d912d5c7c009df4e47accd25029133d47a74342dbd2a8edc2877b6bffa971a3", + "sha256:5316438f97700c431d65351470d1255047e3f2cd9af0f2f13592b637dad9213e" ], - "version": "==2.0.6" - }, - "django-reverse-js": { - "hashes": [ - "sha256:7d626f4d660604e4c2623a494a08ddb70587375110cf4e7bb6f56eeb4471630f", - "sha256:89c15e3f1bd656a6f7c7f3641144c0a79d42ec692182c8edd6a91061674f6b62" - ], - "markers": "python_version >= '3.10'", - "version": "==0.1.7" + "version": "==4.0.0" }, "django-sekizai": { "hashes": [ - "sha256:5c5e16845d37ce822fc655ce79ec02715191b3d03330b550997bcb842cf24fdf", - "sha256:e829f09b0d6bf01ee5cde05de1fb3faf2fbc5df66dc4dc280fbaac224ca4336f" + "sha256:2aca36cbae0b5c0cefed9565416ec442335767fb3145bff11e58622fc653cdad", + "sha256:aa12e66ba0335fbe726b7d74cf4e8716b89a0be99a1304a9b9e8b191229e2e4a" ], - "version": "==2.0.0" + "markers": "python_version >= '3.8'", + "version": "==4.1.0" }, "django-treebeard": { "hashes": [ - "sha256:83aebc34a9f06de7daaec330d858d1c47887e81be3da77e3541fe7368196dd8a" + "sha256:846e462904b437155f76e04907ba4e48480716855f88b898df4122bdcfbd6e98", + "sha256:995c7120153ab999898fe3043bbdcd8a0fc77cc106eb94de7350e9d02c885135" ], - "version": "==4.3.1" + "markers": "python_version >= '3.8'", + "version": "==4.7.1" }, "django-two-factor-auth": { "hashes": [ - "sha256:3fac266d12472ac66475dd737bb18f2992484313bf56acf5a2eea5e824291ee6", - "sha256:44fb9f6a52dbf83229de52c6f642eb2e0feafdb919854f3dc0e7716877c340ba" + "sha256:1bfebce0d627027520a2442901dde07fac2ecfd073da92c78ce0c1171121a7cd", + "sha256:36aefcd20bb13c9813a33f8cbdc54542da95758d81adf7f9ceb6636f164bf056" ], - "version": "==1.13.2" + "version": "==1.15.1" }, "djangorestframework": { "hashes": [ - "sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee", - "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa" + "sha256:3ccc0475bce968608cf30d07fb17d8e52d1d7fc8bfe779c905463200750cbca6", + "sha256:f88fad74183dfc7144b2756d0d2ac716ea5b4c7c9840995ac3bfd8ec034333c1" ], "markers": "python_version >= '3.6'", - "version": "==3.13.1" + "version": "==3.15.1" }, "idna": { "hashes": [ @@ -441,97 +440,84 @@ }, "pillow": { "hashes": [ - "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885", - "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", - "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df", - "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", - "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c", - "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d", - "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd", - "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", - "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908", - "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", - "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", - "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", - "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b", - "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", - "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a", - "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e", - "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", - "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696", - "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b", - "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", - "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e", - "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab", - "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d", - "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", - "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d", - "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", - "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4", - "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3", - "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6", - "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", - "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", - "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b", - "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496", - "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0", - "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319", - "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", - "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856", - "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef", - "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680", - "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b", - "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", - "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e", - "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", - "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", - "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8", - "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", - "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736", - "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da", - "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126", - "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd", - "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5", - "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b", - "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026", - "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b", - "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc", - "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46", - "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2", - "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c", - "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe", - "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984", - "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", - "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", - "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", - "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b", - "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91", - "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3", - "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84", - "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1", - "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5", - "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be", - "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f", - "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", - "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", - "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e", - "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141", - "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", - "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22", - "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27", - "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e", - "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1" + "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7", + "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5", + "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903", + "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2", + "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38", + "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2", + "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9", + "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f", + "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc", + "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8", + "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d", + "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2", + "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316", + "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a", + "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25", + "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd", + "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba", + "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc", + "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273", + "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa", + "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a", + "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b", + "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a", + "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae", + "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291", + "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97", + "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06", + "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904", + "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b", + "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b", + "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8", + "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527", + "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947", + "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb", + "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003", + "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5", + "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f", + "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739", + "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944", + "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830", + "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f", + "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3", + "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4", + "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84", + "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7", + "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6", + "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6", + "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9", + "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de", + "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4", + "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47", + "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd", + "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50", + "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c", + "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086", + "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba", + "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306", + "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699", + "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e", + "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488", + "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa", + "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2", + "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3", + "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9", + "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923", + "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2", + "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790", + "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734", + "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916", + "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1", + "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f", + "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798", + "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb", + "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2", + "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9" ], - "markers": "python_version >= '3.8'", - "version": "==10.4.0" - }, - "pyhamcrest": { - "hashes": [ - "sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316", - "sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29" - ], - "markers": "python_version >= '3.5'", - "version": "==2.0.2" + "markers": "python_version >= '3.9'", + "version": "==11.0.0" }, "pyjwt": { "hashes": [ @@ -553,7 +539,7 @@ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "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.9.0.post0" }, "pytz": { @@ -630,13 +616,6 @@ "markers": "python_version >= '3.7'", "version": "==7.4.2" }, - "rapid-router": { - "hashes": [ - "sha256:44da4e1a61331693150d4e50507905c558bec086b32c11fb10db51f4fd9eaf14", - "sha256:950a0e544a910b915aa568d3585b87624356261a520b5dea29894140c4e3785e" - ], - "version": "==6.6.0" - }, "reportlab": { "hashes": [ "sha256:0b94e4f65a5f77a631cc010c9a7892d69e33f3251b760639dcc76420e138ce95", @@ -709,7 +688,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" }, "sqlparse": { @@ -722,11 +701,11 @@ }, "tablib": { "hashes": [ - "sha256:9821caa9eca6062ff7299fa645e737aecff982e6b2b42046928a6413c8dabfd9", - "sha256:f6661dfc45e1d4f51fa8a6239f9c8349380859a5bfaa73280645f046d6c96e33" + "sha256:9a6930037cfe0f782377963ca3f2b1dae3fd4cdbf0883848f22f1447e7bb718b", + "sha256:f9db84ed398df5109bd69c11d46613d16cc572fb9ad3213f10d95e2b5f12c18e" ], - "markers": "python_version >= '3.8'", - "version": "==3.5.0" + "markers": "python_version >= '3.9'", + "version": "==3.7.0" }, "typing-extensions": { "hashes": [ @@ -939,112 +918,96 @@ "toml" ], "hashes": [ - "sha256:078a87519057dacb5d77e333f740708ec2a8f768655f1db07f8dfd28d7a005f0", - "sha256:087932079c065d7b8ebadd3a0160656c55954144af6439886c8bcf78bbbcde7f", - "sha256:0bbae11c138585c89fb4e991faefb174a80112e1a7557d507aaa07675c62e66b", - "sha256:0ff2ef83d6d0b527b5c9dad73819b24a2f76fdddcfd6c4e7a4d7e73ecb0656b4", - "sha256:12179eb0575b8900912711688e45474f04ab3934aaa7b624dea7b3c511ecc90f", - "sha256:1e5e92e3e84a8718d2de36cd8387459cba9a4508337b8c5f450ce42b87a9e760", - "sha256:2186369a654a15628e9c1c9921409a6b3eda833e4b91f3ca2a7d9f77abb4987c", - "sha256:21c0ea0d4db8a36b275cb6fb2437a3715697a4ba3cb7b918d3525cc75f726304", - "sha256:24500f4b0e03aab60ce575c85365beab64b44d4db837021e08339f61d1fbfe52", - "sha256:2b636a301e53964550e2f3094484fa5a96e699db318d65398cfba438c5c92171", - "sha256:343056c5e0737487a5291f5691f4dfeb25b3e3c8699b4d36b92bb0e586219d14", - "sha256:35a51598f29b2a19e26d0908bd196f771a9b1c5d9a07bf20be0adf28f1ad4f77", - "sha256:39d3b964abfe1519b9d313ab28abf1d02faea26cd14b27f5283849bf59479ff5", - "sha256:3ec528ae69f0a139690fad6deac8a7d33629fa61ccce693fdd07ddf7e9931fba", - "sha256:47ccb6e99a3031ffbbd6e7cc041e70770b4fe405370c66a54dbf26a500ded80b", - "sha256:4eea60c79d36a8f39475b1af887663bc3ae4f31289cd216f514ce18d5938df40", - "sha256:536f77f2bf5797983652d1d55f1a7272a29afcc89e3ae51caa99b2db4e89d658", - "sha256:5ed69befa9a9fc796fe015a7040c9398722d6b97df73a6b608e9e275fa0932b0", - "sha256:62ab4231c01e156ece1b3a187c87173f31cbeee83a5e1f6dff17f288dca93345", - "sha256:667952739daafe9616db19fbedbdb87917eee253ac4f31d70c7587f7ab531b4e", - "sha256:69f251804e052fc46d29d0e7348cdc5fcbfc4861dc4a1ebedef7e78d241ad39e", - "sha256:6c2ba1e0c24d8fae8f2cf0aeb2fc0a2a7f69b6d20bd8d3749fd6b36ecef5edf0", - "sha256:6e85830eed5b5263ffa0c62428e43cb844296f3b4461f09e4bdb0d44ec190bc2", - "sha256:7571e8bbecc6ac066256f9de40365ff833553e2e0c0c004f4482facb131820ef", - "sha256:7781f4f70c9b0b39e1b129b10c7d43a4e0c91f90c60435e6da8288efc2b73438", - "sha256:7926d8d034e06b479797c199747dd774d5e86179f2ce44294423327a88d66ca7", - "sha256:7b80fbb0da3aebde102a37ef0138aeedff45997e22f8962e5f16ae1742852676", - "sha256:7fca4a92c8a7a73dee6946471bce6d1443d94155694b893b79e19ca2a540d86e", - "sha256:84c4315577f7cd511d6250ffd0f695c825efe729f4205c0340f7004eda51191f", - "sha256:8d9c5d13927d77af4fbe453953810db766f75401e764727e73a6ee4f82527b3e", - "sha256:9681516288e3dcf0aa7c26231178cc0be6cac9705cac06709f2353c5b406cfea", - "sha256:97df87e1a20deb75ac7d920c812e9326096aa00a9a4b6d07679b4f1f14b06c90", - "sha256:9bcd51eeca35a80e76dc5794a9dd7cb04b97f0e8af620d54711793bfc1fbba4b", - "sha256:9c6b0c1cafd96213a0327cf680acb39f70e452caf8e9a25aeb05316db9c07f89", - "sha256:a5f81e68aa62bc0cfca04f7b19eaa8f9c826b53fc82ab9e2121976dc74f131f3", - "sha256:a663b180b6669c400b4630a24cc776f23a992d38ce7ae72ede2a397ce6b0f170", - "sha256:a7b2e437fbd8fae5bc7716b9c7ff97aecc95f0b4d56e4ca08b3c8d8adcaadb84", - "sha256:a867d26f06bcd047ef716175b2696b315cb7571ccb951006d61ca80bbc356e9e", - "sha256:aa68a6cdbe1bc6793a9dbfc38302c11599bbe1837392ae9b1d238b9ef3dafcf1", - "sha256:ab31fdd643f162c467cfe6a86e9cb5f1965b632e5e65c072d90854ff486d02cf", - "sha256:ad4ef1c56b47b6b9024b939d503ab487231df1f722065a48f4fc61832130b90e", - "sha256:b92f9ca04b3e719d69b02dc4a69debb795af84cb7afd09c5eb5d54b4a1ae2191", - "sha256:bb21bac7783c1bf6f4bbe68b1e0ff0d20e7e7732cfb7995bc8d96e23aa90fc7b", - "sha256:bf4eeecc9e10f5403ec06138978235af79c9a79af494eb6b1d60a50b49ed2869", - "sha256:bfde025e2793a22efe8c21f807d276bd1d6a4bcc5ba6f19dbdfc4e7a12160909", - "sha256:c37faddc8acd826cfc5e2392531aba734b229741d3daec7f4c777a8f0d4993e5", - "sha256:c71965d1ced48bf97aab79fad56df82c566b4c498ffc09c2094605727c4b7e36", - "sha256:c9192925acc33e146864b8cf037e2ed32a91fdf7644ae875f5d46cd2ef086a5f", - "sha256:c9df1950fb92d49970cce38100d7e7293c84ed3606eaa16ea0b6bc27175bb667", - "sha256:cdfcf2e914e2ba653101157458afd0ad92a16731eeba9a611b5cbb3e7124e74b", - "sha256:d03a060ac1a08e10589c27d509bbdb35b65f2d7f3f8d81cf2fa199877c7bc58a", - "sha256:d20c3d1f31f14d6962a4e2f549c21d31e670b90f777ef4171be540fb7fb70f02", - "sha256:e4ee15b267d2dad3e8759ca441ad450c334f3733304c55210c2a44516e8d5530", - "sha256:e8ea055b3ea046c0f66217af65bc193bbbeca1c8661dc5fd42698db5795d2627", - "sha256:ebabdf1c76593a09ee18c1a06cd3022919861365219ea3aca0247ededf6facd6", - "sha256:ebc94fadbd4a3f4215993326a6a00e47d79889391f5659bf310f55fe5d9f581c", - "sha256:ed5ac02126f74d190fa2cc14a9eb2a5d9837d5863920fa472b02eb1595cdc925", - "sha256:f01e53575f27097d75d42de33b1b289c74b16891ce576d767ad8c48d17aeb5e0", - "sha256:f361296ca7054f0936b02525646b2731b32c8074ba6defab524b79b2b7eeac72", - "sha256:f9035695dadfb397bee9eeaf1dc7fbeda483bf7664a7397a629846800ce6e276", - "sha256:fcad7d5d2bbfeae1026b395036a8aa5abf67e8038ae7e6a25c7d0f88b10a8e6a", - "sha256:ff797320dcbff57caa6b2301c3913784a010e13b1f6cf4ab3f563f3c5e7919db" + "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376", + "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9", + "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111", + "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172", + "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491", + "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546", + "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2", + "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11", + "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08", + "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c", + "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2", + "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963", + "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613", + "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0", + "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db", + "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf", + "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73", + "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117", + "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1", + "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e", + "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522", + "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25", + "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc", + "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea", + "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52", + "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a", + "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07", + "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06", + "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa", + "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901", + "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b", + "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17", + "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0", + "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21", + "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19", + "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5", + "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51", + "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3", + "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3", + "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f", + "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076", + "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a", + "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718", + "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba", + "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e", + "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27", + "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e", + "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09", + "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e", + "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70", + "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f", + "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72", + "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a", + "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef", + "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b", + "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b", + "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f", + "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806", + "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b", + "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1", + "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c", + "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858" ], "markers": "python_version >= '3.9'", - "version": "==7.6.2" - }, - "diff-match-patch": { - "hashes": [ - "sha256:953019cdb9c9d2c9e47b5b12bcff3cf4746fc4598eb406076fa1fc27e6a1f15c", - "sha256:dce43505fb7b1b317de7195579388df0746d90db07015ed47a85e5e44930ef93" - ], - "markers": "python_version >= '3.7'", - "version": "==20230430" + "version": "==7.6.4" }, "django": { "hashes": [ - "sha256:7ca38a78654aee72378594d63e51636c04b8e28574f5505dff630895b5472777", - "sha256:a52ea7fcf280b16f7b739cec38fa6d3f8953a5456986944c3ca97e79882b4e38" - ], - "markers": "python_version >= '3.6'", - "version": "==3.2.25" - }, - "django-import-export": { - "hashes": [ - "sha256:16ecc5a9f0df46bde6eb278a3e65ebda0ee1db55656f36440e9fb83f40ab85a3", - "sha256:730ae2443a02b1ba27d8dba078a27ae9123adfcabb78161b4f130843607b3df9" + "sha256:1ddc333a16fc139fd253035a1606bb24261951bbc3a6ca256717fa06cc41a898", + "sha256:6f1616c2786c408ce86ab7e10f792b8f15742f7b7b7460243929cb371e7f1dad" ], "markers": "python_version >= '3.8'", - "version": "==4.1.1" + "version": "==4.2.16" }, "django-selenium-clean": { "hashes": [ - "sha256:35982f7af664f578b6db48053ade5e4260f96c13d778308b992007a548d1fc26", - "sha256:a31b71a92934399597c1fa84401892987524d95168f48ea4212812f6f05d1eaa" + "sha256:74c002026047f493d2de4802414ccfa860de15f9c21fd564e676b5fc12268a8d", + "sha256:a80548eab73c9c2ecdd4c995b73aea6dc78a1ee83e83d880de01603483de2b0f" ], "index": "pypi", - "version": "==1.0.0" + "version": "==1.0.1" }, "django-test-migrations": { "hashes": [ - "sha256:874884ff4e980583cd9f1c986bb9fbfe72b436e759be23004e4f52c26a15f363", - "sha256:9e8b9b4364fef70dde10a5f85c5a75d447ca2189ec648325610fab1268daec97" + "sha256:294dff98f6d43d020d4046b971bac5339e7c71458a35e9ad6450c388fe16ed6b", + "sha256:f0c9c92864ed27d0c9a582e92056637e91227f54bd868a50cb9a1726668c563e" ], "index": "pypi", - "markers": "python_version >= '3.6' and python_version < '4.0'", - "version": "==1.2.0" + "markers": "python_version >= '3.9' and python_version < '4.0'", + "version": "==1.4.0" }, "execnet": { "hashes": [ @@ -1170,21 +1133,21 @@ }, "pytest-cov": { "hashes": [ - "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652", - "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857" + "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", + "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==5.0.0" + "markers": "python_version >= '3.9'", + "version": "==6.0.0" }, "pytest-django": { "hashes": [ - "sha256:c60834861933773109334fe5a53e83d1ef4828f2203a1d6a0fa9972f4f75ab3e", - "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2" + "sha256:5d054fe011c56f3b10f978f41a8efb2e5adfc7e680ef36fb571ada1f24779d90", + "sha256:ca1ddd1e0e4c227cf9e3e40a6afc6d106b3e70868fd2ac5798a22501271cd0c7" ], "index": "pypi", - "markers": "python_version >= '3.5'", - "version": "==4.5.2" + "markers": "python_version >= '3.8'", + "version": "==4.8.0" }, "pytest-mock": { "hashes": [ @@ -1213,13 +1176,6 @@ "markers": "python_version >= '3.8'", "version": "==3.6.1" }, - "pytz": { - "hashes": [ - "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", - "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725" - ], - "version": "==2024.2" - }, "pyvirtualdisplay": { "hashes": [ "sha256:09755bc3ceb6eb725fb07eca5425f43f2358d3bf08e00d2a9b792a1aedd16159", @@ -1285,14 +1241,6 @@ "markers": "python_version >= '3.8'", "version": "==0.5.1" }, - "tablib": { - "hashes": [ - "sha256:9821caa9eca6062ff7299fa645e737aecff982e6b2b42046928a6413c8dabfd9", - "sha256:f6661dfc45e1d4f51fa8a6239f9c8349380859a5bfaa73280645f046d6c96e33" - ], - "markers": "python_version >= '3.8'", - "version": "==3.5.0" - }, "termcolor": { "hashes": [ "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8", @@ -1303,11 +1251,11 @@ }, "trio": { "hashes": [ - "sha256:0346c3852c15e5c7d40ea15972c4805689ef2cb8b5206f794c9c19450119f3a4", - "sha256:c5237e8133eb0a1d72f09a971a55c28ebe69e351c783fc64bc37db8db8bbe1d0" + "sha256:1dcc95ab1726b2da054afea8fd761af74bad79bd52381b84eae408e983c76831", + "sha256:68eabbcf8f457d925df62da780eff15ff5dc68fd6b367e2dde59f7aaf2a0b884" ], "markers": "python_version >= '3.8'", - "version": "==0.26.2" + "version": "==0.27.0" }, "trio-websocket": { "hashes": [ diff --git a/cfl_common/setup.py b/cfl_common/setup.py index 103647146..cd2cff55a 100644 --- a/cfl_common/setup.py +++ b/cfl_common/setup.py @@ -17,12 +17,17 @@ version=version, include_package_data=True, install_requires=[ - "django==3.2.25", - "djangorestframework==3.13.1", - "django-two-factor-auth==1.13.2", - "django-countries==7.3.1", + "django==4.2.16", + "djangorestframework==3.15.1", + "django-two-factor-auth==1.15.1", + "django-countries==7.6.1", "pyjwt==2.6.0", "pgeocode==0.4.0", + "django-pipeline==3.1.0", + "django-csp==3.8", + "more-itertools==8.7.0", + "libsass==0.23.0", + "django-import-export==4.2.0", ], tests_require=[], test_suite="tests", diff --git a/setup.py b/setup.py index ceb0a9452..89e56b09f 100644 --- a/setup.py +++ b/setup.py @@ -24,27 +24,22 @@ packages=find_packages(), include_package_data=True, install_requires=[ - "django-pipeline==2.0.8", - "django-recaptcha==2.0.6", + "django-recaptcha==4.0.0", "pyyaml==6.0.2", "importlib-metadata==4.13.0", - "rapid-router>=6.3.6", + # "rapid-router>=6.3.6", "reportlab==3.6.13", - "django-formtools==2.2", - "django-otp==1.0.2", # we needed to fix this due to a wide ranged dependency in django-two-factor-auth + "django-formtools==2.5.1", + "django-otp==1.5.4", "requests==2.32.2", - "django-treebeard==4.3.1", - "django-sekizai==2.0.0", - "django-classy-tags==2.0.0", - "libsass==0.23.0", + "django-treebeard==4.7.1", + "django-sekizai==4.1.0", + "django-classy-tags==4.1.0", "phonenumbers==8.12.12", - "more-itertools==8.7.0", f"cfl-common=={version}", "django-ratelimit==3.0.1", "django-preventconcurrentlogins==0.8.2", - "django-csp==3.7", "setuptools==74.0.0", - "django-import-export", ], classifiers=[ "Programming Language :: Python", From 213e1d8f371d5ddbb18fb08d30085bc8dc1d5331 Mon Sep 17 00:00:00 2001 From: faucomte97 Date: Fri, 1 Nov 2024 23:09:37 +0000 Subject: [PATCH 02/14] Comment out common --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 89e56b09f..cc88f4b3c 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ "django-sekizai==4.1.0", "django-classy-tags==4.1.0", "phonenumbers==8.12.12", - f"cfl-common=={version}", + # f"cfl-common=={version}", "django-ratelimit==3.0.1", "django-preventconcurrentlogins==0.8.2", "setuptools==74.0.0", From 7e2f2e42528b7410d9e9be8c9b15720dc3a93204 Mon Sep 17 00:00:00 2001 From: faucomte97 Date: Tue, 5 Nov 2024 19:03:09 +0000 Subject: [PATCH 03/14] Fix a whole bunch of stuff --- deploy/middleware/security.py | 9 +- example_project/portal_test_settings.py | 2 +- example_project/settings.py | 4 +- example_project/urls.py | 9 +- portal/admin.py | 50 ++--- portal/forms/play.py | 4 +- portal/forms/registration.py | 4 +- portal/forms/teach.py | 4 +- portal/helpers/decorators.py | 36 ++-- portal/templates/portal/register.html | 5 +- portal/templates/two_factor/core/login.html | 116 ++++++----- .../templates/two_factor/profile/profile.html | 50 +++-- portal/tests/test_captcha_forms.py | 4 +- portal/tests/test_middleware.py | 42 +++- portal/urls.py | 181 +++++++++--------- portal/views/api.py | 46 +++-- portal/views/home.py | 62 ++++-- portal/views/registration.py | 6 +- portal/views/two_factor/core.py | 3 - 19 files changed, 359 insertions(+), 278 deletions(-) diff --git a/deploy/middleware/security.py b/deploy/middleware/security.py index 3708f414f..93abd39b0 100644 --- a/deploy/middleware/security.py +++ b/deploy/middleware/security.py @@ -4,8 +4,8 @@ class CustomSecurityMiddleware(SecurityMiddleware): """ Extends Django's Security Middleware. - See https://docs.djangoproject.com/en/3.2/_modules/django/middleware/security/ for - the source code, as well as https://docs.djangoproject.com/en/3.2/ref/middleware/#module-django.middleware.security + See https://docs.djangoproject.com/en/4.2/_modules/django/middleware/security/ for + the source code, as well as https://docs.djangoproject.com/en/4.2/ref/middleware/#module-django.middleware.security for docs on security middleware. """ @@ -13,10 +13,9 @@ def process_response(self, request, response): """ Extends the original security middleware to ensure the X-XSS-Protection header is set to 1. + https://docs.djangoproject.com/en/5.1/releases/4.0/#securitymiddleware-no-longer-sets-the-x-xss-protection-header """ super().process_response(request, response) - - if self.xss_filter: - response["X-XSS-Protection"] = "1" + response.headers.setdefault("X-XSS-Protection", "1; mode=block") return response diff --git a/example_project/portal_test_settings.py b/example_project/portal_test_settings.py index 977b0a0c7..a195952a7 100644 --- a/example_project/portal_test_settings.py +++ b/example_project/portal_test_settings.py @@ -86,7 +86,7 @@ "game", "pipeline", "portal", - "captcha", + "django_recaptcha", "common", "django.contrib.admin", "django.contrib.admindocs", diff --git a/example_project/settings.py b/example_project/settings.py index 9dceef920..6e5d0d4f7 100644 --- a/example_project/settings.py +++ b/example_project/settings.py @@ -36,13 +36,13 @@ EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" -SILENCED_SYSTEM_CHECKS = ["captcha.recaptcha_test_key_error"] +SILENCED_SYSTEM_CHECKS = ["django_recaptcha.recaptcha_test_key_error"] INSTALLED_APPS = [ "game", "pipeline", "portal", - "captcha", + "django_recaptcha", "common", "django.contrib.admin", "django.contrib.admindocs", diff --git a/example_project/urls.py b/example_project/urls.py index 85365e297..73853cde5 100644 --- a/example_project/urls.py +++ b/example_project/urls.py @@ -1,6 +1,5 @@ -from django.conf.urls import include, url from django.contrib import admin -from django.urls import path +from django.urls import include, path, re_path from game import python_den_urls from game import urls as game_urls @@ -9,8 +8,8 @@ admin.autodiscover() urlpatterns = [ - url(r"^", include(portal_urls)), + re_path(r"^", include(portal_urls)), path("administration/", admin.site.urls), - url(r"^rapidrouter/", include(game_urls)), - url(r"^pythonden/", include(python_den_urls)), + re_path(r"^rapidrouter/", include(game_urls)), + re_path(r"^pythonden/", include(python_den_urls)), ] diff --git a/portal/admin.py b/portal/admin.py index 20149b9dc..cf66af757 100644 --- a/portal/admin.py +++ b/portal/admin.py @@ -12,7 +12,7 @@ UserProfile, ) from django.contrib import admin -from django.contrib.auth.admin import UserAdmin +from django.contrib.auth.admin import UserAdmin as _UserAdmin from django.contrib.auth.models import User from django.http import HttpResponse from import_export.admin import ExportActionMixin @@ -172,38 +172,40 @@ def has_delete_permission(self, request, obj=None): return False -def anonymise_user(user_admin, request, queryset): - for user in queryset: - anonymise(user) +_UserAdmin.list_display += ("date_joined", "id") +_UserAdmin.list_filter += ("date_joined",) -def export_as_csv(self, request, queryset): - meta = self.model._meta - field_names = [ - field.name for field in meta.fields if field.name != "password" - ] +class UserAdmin(_UserAdmin): + actions = ["anonymise_user", "export_as_csv"] + add_form = AdminUserCreationForm + change_password_form = AdminChangeUserPasswordForm - response = HttpResponse(content_type="text/csv") - response["Content-Disposition"] = "attachment; filename={}.csv".format(meta) - writer = csv.writer(response) + def anonymise_user(self, request, queryset): + for user in queryset: + anonymise(user) - writer.writerow(field_names) - for obj in queryset: - writer.writerow([getattr(obj, field) for field in field_names]) + anonymise_user.short_description = "Anonymise selected users" - return response + def export_as_csv(self, request, queryset): + meta = self.model._meta + field_names = [ + field.name for field in meta.fields if field.name != "password" + ] + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = "attachment; filename={}.csv".format( + meta + ) + writer = csv.writer(response) -anonymise_user.short_description = "Anonymise selected users" -export_as_csv.short_description = "Export selected users data as CSV" + writer.writerow(field_names) + for obj in queryset: + writer.writerow([getattr(obj, field) for field in field_names]) + return response -UserAdmin.list_display += ("date_joined", "id") -UserAdmin.list_filter += ("date_joined",) -UserAdmin.add_form = AdminUserCreationForm -UserAdmin.change_password_form = AdminChangeUserPasswordForm -UserAdmin.actions.append(anonymise_user) -UserAdmin.actions.append(export_as_csv) + export_as_csv.short_description = "Export selected users data as CSV" admin.site.register(Class, ClassAdmin) diff --git a/portal/forms/play.py b/portal/forms/play.py index 3ab2342b9..8a3a6893f 100644 --- a/portal/forms/play.py +++ b/portal/forms/play.py @@ -1,8 +1,6 @@ import re from datetime import timedelta, date -from captcha.fields import ReCaptchaField -from captcha.widgets import ReCaptchaV2Invisible from common.helpers.emails import send_verification_email from common.models import Class, Student, stripStudentName from common.permissions import logged_in_as_independent_student @@ -10,6 +8,8 @@ from django.contrib.auth import authenticate from django.contrib.auth.forms import AuthenticationForm from django.utils import timezone +from django_recaptcha.fields import ReCaptchaField +from django_recaptcha.widgets import ReCaptchaV2Invisible from portal.forms.error_messages import INVALID_LOGIN_MESSAGE from portal.helpers.password import PasswordStrength, form_clean_password diff --git a/portal/forms/registration.py b/portal/forms/registration.py index d066840cc..d0b664226 100644 --- a/portal/forms/registration.py +++ b/portal/forms/registration.py @@ -1,5 +1,3 @@ -from captcha.fields import ReCaptchaField -from captcha.widgets import ReCaptchaV2Invisible from common.mail import campaign_ids, send_dotdigital_email from common.models import Student, Teacher from django import forms @@ -10,6 +8,8 @@ from django.urls import reverse_lazy from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode +from django_recaptcha.fields import ReCaptchaField +from django_recaptcha.widgets import ReCaptchaV2Invisible from portal.helpers.password import PasswordStrength, form_clean_password diff --git a/portal/forms/teach.py b/portal/forms/teach.py index 553bfa8d4..9b30310d9 100644 --- a/portal/forms/teach.py +++ b/portal/forms/teach.py @@ -2,14 +2,14 @@ import re from builtins import map, range, str -from captcha.fields import ReCaptchaField -from captcha.widgets import ReCaptchaV2Invisible from common.helpers.emails import send_verification_email from common.models import Student, stripStudentName, UserSession, Teacher from django import forms from django.contrib.auth import authenticate from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.models import User +from django_recaptcha.fields import ReCaptchaField +from django_recaptcha.widgets import ReCaptchaV2Invisible from game.models import Episode from portal.forms.error_messages import INVALID_LOGIN_MESSAGE diff --git a/portal/helpers/decorators.py b/portal/helpers/decorators.py index 5cb00a061..326eb33b7 100644 --- a/portal/helpers/decorators.py +++ b/portal/helpers/decorators.py @@ -1,28 +1,24 @@ from __future__ import absolute_import import datetime -import pytz -import re - +from functools import wraps +import pytz from common.models import Teacher, Student from django.contrib.auth import logout from django.shortcuts import render -from functools import wraps from ratelimit import ALL, UNSAFE from portal.helpers.ratelimit import is_ratelimited -from portal.templatetags.app_tags import is_logged_in -from portal.views.registration import blocked_and_not_expired -from portal.views.login import has_user_lockout_expired -from portal.helpers.ratelimit import get_ratelimit_count_for_user - from portal.helpers.request_handlers import get_access_code_from_request +from portal.templatetags.app_tags import is_logged_in __all__ = ["ratelimit"] -def ratelimit(group=None, key=None, rate=None, method=ALL, block=False, is_teacher=True): +def ratelimit( + group=None, key=None, rate=None, method=ALL, block=False, is_teacher=True +): """ Ratelimit decorator, adding custom functionality to django-ratelimit's default decorator. On block, the user is logged out, redirected to the "locked out" page, @@ -63,25 +59,29 @@ def _wrapped(request, *args, **kw): access_code = get_access_code_from_request(request) model_finder = model.objects.get - # look for school student - # if access code not found (AttributeError) - # if student not found (IndexError) - # move on to another try block - # similar logic followed afterwards + # look for school student if access code not found + # (AttributeError) if student not found (IndexError) move + # on to another try block similar logic followed afterwards if access_code: user_to_lockout = model_finder( new_user__first_name=username, class_field__access_code=access_code, # extract the found text from regex ) - lockout_template = "portal/locked_out_school_student.html" + lockout_template = ( + "portal/locked_out_school_student.html" + ) # look for indy student or teacher else: - user_to_lockout = model_finder(new_user__username=username) + user_to_lockout = model_finder( + new_user__username=username + ) else: user_to_lockout = model.objects.get(new_user=request.user) if user_to_lockout: - user_to_lockout.blocked_time = datetime.datetime.now(tz=pytz.utc) + user_to_lockout.blocked_time = datetime.datetime.now( + tz=pytz.utc + ) user_to_lockout.save() if is_logged_in(request.user): diff --git a/portal/templates/portal/register.html b/portal/templates/portal/register.html index ce406f2d2..fdbeb9c87 100644 --- a/portal/templates/portal/register.html +++ b/portal/templates/portal/register.html @@ -76,7 +76,8 @@

Teacher/Tutor

@@ -178,7 +179,7 @@

Independent learner

diff --git a/portal/templates/two_factor/core/login.html b/portal/templates/two_factor/core/login.html index 70c434c39..54c20101c 100644 --- a/portal/templates/two_factor/core/login.html +++ b/portal/templates/two_factor/core/login.html @@ -1,63 +1,57 @@ -{# Overriden the original "two_factor" template to remove extending base template and slightly adjust style #} - -{% load i18n two_factor %} - -
- - - -
+ {% include "two_factor/_wizard_actions.html" %} + + +{% block 'backup_tokens' %} +{% if backup_tokens %} +
+
+
+ {% csrf_token %} +

{% trans "As a last resort, you can use a backup token:" %}

+

+ +

+
+
+{% endif %} +{% endblock %} +{% endblock %} diff --git a/portal/templates/two_factor/profile/profile.html b/portal/templates/two_factor/profile/profile.html index 1e4b2d175..e2c9d2ad3 100644 --- a/portal/templates/two_factor/profile/profile.html +++ b/portal/templates/two_factor/profile/profile.html @@ -1,41 +1,57 @@ {% extends "two_factor/_base.html" %} -{% load future i18n two_factor %} +{% load i18n %} +{% load two_factor_tags %} {% block content %} -

{% block title %}{% trans "Account Security" %}{% endblock %}

+

{% block title %}{% trans "Account Security" %}{% endblock %}

{% if default_device %} -{% if default_device_type == 'TOTPDevice' %} -

{% trans "Tokens will be generated by your token generator." %}

-{% elif default_device_type == 'PhoneDevice' %} -

{% blocktrans with primary=default_device|device_action %}Primary method: {{ primary }}{% endblocktrans %}

-{% elif default_device_type == 'RemoteYubikeyDevice' %} -

{% blocktrans %}Tokens will be generated by your YubiKey.{% endblocktrans %}

+

{% blocktrans with primary=default_device|as_action %}Primary method: {{ primary }}{% endblocktrans %}

+ +{% if available_phone_methods %} +

{% trans "Backup Phone Numbers" %}

+

{% blocktrans trimmed %}If your primary method is not available, we are able to + send backup tokens to the phone numbers listed below.{% endblocktrans %}

+
    + {% for phone in backup_phones %} +
  • + {{ phone|as_action }} +
    + {% csrf_token %} + +
    +
  • + {% endfor %} +
+

{% trans "Add Phone Number" %}

{% endif %} -

{% trans "Backup Tokens" %}

+

{% trans "Backup Tokens" %}

- {% blocktrans %}If you don't have any device with you, you can access + {% blocktrans trimmed %}If you don't have any device with you, you can access your account using backup tokens.{% endblocktrans %} - {% blocktrans count counter=backup_tokens %} + {% blocktrans trimmed count counter=backup_tokens %} You have only one backup token remaining. {% plural %} You have {{ counter }} backup tokens remaining. {% endblocktrans %}

{% trans "Show Codes" %}

+ class="btn btn-info">{% trans "Show Codes" %}

-

{% trans "Disable Two-Factor Authentication" %}

-

{% blocktrans %}However we strongly discourage you to do so, you can +

{% trans "Disable Two-Factor Authentication" %}

+

{% blocktrans trimmed %}However we strongly discourage you to do so, you can also disable two-factor authentication for your account.{% endblocktrans %}

-

+

{% trans "Disable Two-Factor Authentication" %}

{% else %} -

{% blocktrans %}Two-factor authentication is not enabled for your +

{% blocktrans trimmed %}Two-factor authentication is not enabled for your account. Enable two-factor authentication for enhanced account security.{% endblocktrans %}

-

+

{% trans "Enable Two-Factor Authentication" %}

{% endif %} diff --git a/portal/tests/test_captcha_forms.py b/portal/tests/test_captcha_forms.py index b7834dce8..ef39bc637 100644 --- a/portal/tests/test_captcha_forms.py +++ b/portal/tests/test_captcha_forms.py @@ -1,7 +1,7 @@ -from captcha.fields import ReCaptchaField -from captcha.widgets import ReCaptchaV2Invisible from django import forms from django.test import TestCase +from django_recaptcha.fields import ReCaptchaField +from django_recaptcha.widgets import ReCaptchaV2Invisible from portal.helpers.captcha import is_captcha_in_form, remove_captcha_from_forms diff --git a/portal/tests/test_middleware.py b/portal/tests/test_middleware.py index 171401937..c1438ba7b 100644 --- a/portal/tests/test_middleware.py +++ b/portal/tests/test_middleware.py @@ -32,7 +32,9 @@ def setUp(self) -> None: self.email, self.password = self._setup_user() self.monkeypatch = MonkeyPatch() - self.monkeypatch.setattr("deploy.middleware.admin_access.MODULE_NAME", "test") + self.monkeypatch.setattr( + "deploy.middleware.admin_access.MODULE_NAME", "test" + ) def _setup_user(self) -> Tuple[str, str]: email, password = signup_teacher_directly() @@ -79,7 +81,11 @@ def test_superuser_without_2FA_is_redirected(self): assert type(response) == HttpResponseRedirect assert response.url == "/teach/dashboard/" - @mock.patch("deploy.middleware.admin_access.using_two_factor", return_value=True, autospec=True) + @mock.patch( + "deploy.middleware.admin_access.using_two_factor", + return_value=True, + autospec=True, + ) def test_non_superuser_with_2FA_is_redirected(self, mock_using_two_factor): self.client.login(username=self.email, password=self.password) @@ -91,8 +97,14 @@ def test_non_superuser_with_2FA_is_redirected(self, mock_using_two_factor): assert type(response) == HttpResponseRedirect assert response.url == "/teach/dashboard/" - @mock.patch("deploy.middleware.admin_access.using_two_factor", return_value=True, autospec=True) - def test_superuser_with_2FA_can_access_admin_site(self, mock_using_two_factor): + @mock.patch( + "deploy.middleware.admin_access.using_two_factor", + return_value=True, + autospec=True, + ) + def test_superuser_with_2FA_can_access_admin_site( + self, mock_using_two_factor + ): self._make_user_superuser() self.client.login(username=self.email, password=self.password) @@ -118,7 +130,7 @@ def test_security_headers(self): assert response.headers["cache-control"] == "private" assert response.headers["x-content-type-options"] == "nosniff" assert response.headers["x-frame-options"] == "DENY" - assert response.headers["x-xss-protection"] == "1" + assert response.headers["x-xss-protection"] == "1; mode=block" class TestSessionTimeoutMiddleware(TestCase): @@ -132,7 +144,9 @@ def setUp(self) -> None: self.email, self.password = self._setup_user() self.monkeypatch = MonkeyPatch() - self.monkeypatch.setattr("deploy.middleware.session_timeout.SESSION_EXPIRY_TIME", 5) + self.monkeypatch.setattr( + "deploy.middleware.session_timeout.SESSION_EXPIRY_TIME", 5 + ) def _setup_user(self) -> Tuple[str, str]: email, password = signup_teacher_directly() @@ -211,14 +225,18 @@ def test_screentime_warning_timeout(self): self.client.get("/") session = self.client.session assert "screentime_warning_timeout" in session - previous_screentime_warning_timeout = session["screentime_warning_timeout"] + previous_screentime_warning_timeout = session[ + "screentime_warning_timeout" + ] self.client.get("/") session = self.client.session assert "screentime_warning_timeout" in session new_screentime_warning_timeout = session["screentime_warning_timeout"] - assert new_screentime_warning_timeout < previous_screentime_warning_timeout + assert ( + new_screentime_warning_timeout < previous_screentime_warning_timeout + ) # Check the reset_screentime_warning API resets the timeout url = reverse("reset_screentime_warning") @@ -226,5 +244,9 @@ def test_screentime_warning_timeout(self): self.client.get("/") session = self.client.session assert "screentime_warning_timeout" in session - renewed_screentime_warning_timeout = session["screentime_warning_timeout"] - assert renewed_screentime_warning_timeout > new_screentime_warning_timeout + renewed_screentime_warning_timeout = session[ + "screentime_warning_timeout" + ] + assert ( + renewed_screentime_warning_timeout > new_screentime_warning_timeout + ) diff --git a/portal/urls.py b/portal/urls.py index 0dd8127ce..e2cabb90d 100644 --- a/portal/urls.py +++ b/portal/urls.py @@ -1,7 +1,6 @@ from common.permissions import teacher_verified -from django.conf.urls import include, url from django.http import HttpResponse -from django.urls import path +from django.urls import include, path, re_path from django.views.generic import RedirectView from django.views.generic.base import TemplateView from django.views.i18n import JavaScriptCatalog @@ -114,26 +113,28 @@ js_info_dict = {"packages": ("conf.locale",)} two_factor_patterns = [ - url( + re_path( r"^account/two_factor/setup/$", CustomSetupView.as_view(), name="setup" ), - url(r"^account/two_factor/qrcode/$", QRGeneratorView.as_view(), name="qr"), - url( + re_path( + r"^account/two_factor/qrcode/$", QRGeneratorView.as_view(), name="qr" + ), + re_path( r"^account/two_factor/setup/complete/$", SetupCompleteView.as_view(), name="setup_complete", ), - url( + re_path( r"^account/two_factor/backup/tokens/$", teacher_verified(BackupTokensView.as_view()), name="backup_tokens", ), - url( + re_path( r"^account/two_factor/$", teacher_verified(ProfileView.as_view()), name="profile", ), - url( + re_path( r"^account/two_factor/disable/$", teacher_verified(CustomDisableView.as_view()), name="disable", @@ -186,45 +187,45 @@ ] ), ), - url( + re_path( r"^favicon\.ico$", RedirectView.as_view( url="/static/portal/img/favicon.ico", permanent=True ), ), - url( + re_path( r"^administration/password_change/$", AdminChangePasswordView.as_view(), name="administration_password_change", ), - url( + re_path( r"^administration/password_change_done/$", AdminChangePasswordDoneView.as_view(), name="administration_password_change_done", ), - url( + re_path( r"^users/inactive/", InactiveUsersView.as_view(), name="inactive_users" ), - url( + re_path( r"^locked_out/$", TemplateView.as_view(template_name="portal/locked_out.html"), name="locked_out", ), - url( + re_path( r"^", include((two_factor_patterns, "two_factor"), namespace="two_factor"), ), - url(r"^i18n/", include("django.conf.urls.i18n")), - url(r"^jsi18n/$", JavaScriptCatalog.as_view(), js_info_dict), - url( + re_path(r"^i18n/", include("django.conf.urls.i18n")), + re_path(r"^jsi18n/$", JavaScriptCatalog.as_view(), js_info_dict), + re_path( r"^(?P[A-Z0-9]+)/$", play_default_level, name="play_default_level", ), - url(r"^$", home, name="home"), - url(r"^home-learning", home_learning, name="home-learning"), - url(r"^register_form", register_view, name="register"), - url( + re_path(r"^$", home, name="home"), + re_path(r"^home-learning", home_learning, name="home-learning"), + re_path(r"^register_form", register_view, name="register"), + re_path( r"^login/teacher/$", # The ratelimit decorator checks how often a POST request is performed on that view. # It checks against the username value specifically. If the number of requests @@ -238,7 +239,7 @@ )(TeacherLoginView.as_view()), name="teacher_login", ), - url( + re_path( rf"^login/student/(?P{ACCESS_CODE_REGEX})/(?:(?Pclassform)/)?$", ratelimit( group=RATELIMIT_LOGIN_GROUP, @@ -250,17 +251,17 @@ )(StudentLoginView.as_view()), name="student_login", ), - url( + re_path( r"^login/student/$", StudentClassCodeView.as_view(), name="student_login_access_code", ), - url( + re_path( r"^u/(?P[0-9]+)/(?P[a-z0-9]+)/$", student_direct_login, name="student_direct_login", ), - url( + re_path( r"^login/independent/$", ratelimit( group=RATELIMIT_LOGIN_GROUP, @@ -272,106 +273,110 @@ )(IndependentStudentLoginView.as_view()), name="independent_student_login", ), - url(r"^login_form", old_login_form_redirect, name="old_login_form"), - url(r"^logout/$", logout_view, name="logout_view"), - url( + re_path(r"^login_form", old_login_form_redirect, name="old_login_form"), + re_path(r"^logout/$", logout_view, name="logout_view"), + re_path( r"^news_signup/$", process_newsletter_form, name="process_newsletter_form", ), - url(r"^donate_signup/$", process_donate_form, name="process_donate_form"), - url(r"^consent_form/$", dotmailer_consent_form, name="consent_form"), - url( + re_path( + r"^donate_signup/$", process_donate_form, name="process_donate_form" + ), + re_path(r"^consent_form/$", dotmailer_consent_form, name="consent_form"), + re_path( r"^verify_email/$", TemplateView.as_view( template_name="portal/email_verification_needed.html" ), name="email_verification", ), - url( + re_path( rf"^verify_email/(?P{JWT_REGEX})/$", verify_email, name="verify_email", ), - url( + re_path( r"^user/password/reset/student/$", student_password_reset, name="student_password_reset", ), - url( + re_path( r"^user/password/reset/teacher/$", teacher_password_reset, name="teacher_password_reset", ), - url( + re_path( r"^user/password/reset/done/$", password_reset_done, name="reset_password_email_sent", ), - url( + re_path( r"^user/password/reset/(?P[0-9A-Za-z]+)-(?P.+)/$", password_reset_check_and_confirm, name="password_reset_check_and_confirm", ), - url( + re_path( r"^user/reset_screentime_warning/$", reset_screentime_warning, name="reset_screentime_warning", ), - url( + re_path( r"^user/reset_session_time/$", lambda _: HttpResponse(status=204), name="reset_session_time", ), - url( + re_path( r"^teacher/password/reset/complete/$", TemplateView.as_view(template_name="portal/reset_password_done.html"), name="password_reset_complete", ), - url(r"^teach/$", teach, name="teach"), - url( + re_path(r"^teach/$", teach, name="teach"), + re_path( r"^teach/onboarding-organisation/$", organisation_manage, name="onboarding-organisation", ), - url( + re_path( r"^teach/onboarding-classes", teacher_onboarding_create_class, name="onboarding-classes", ), - url( + re_path( rf"^teach/onboarding-class/(?P{ACCESS_CODE_REGEX})$", teacher_onboarding_edit_class, name="onboarding-class", ), - url( + re_path( rf"^teach/onboarding-class/(?P{ACCESS_CODE_REGEX})/print_reminder_cards/$", teacher_print_reminder_cards, name="teacher_print_reminder_cards", ), - url( + re_path( rf"^teach/onboarding-class/(?P{ACCESS_CODE_REGEX})/download_csv/$", teacher_download_csv, name="teacher_download_csv", ), - url( + re_path( r"^invited_teacher/(?P[0-9a-f]+)/$", invited_teacher, name="invited_teacher", ), - url(r"^play/$", play_landing_page, name="play"), - url( + re_path(r"^play/$", play_landing_page, name="play"), + re_path( r"^play/details/$", SchoolStudentDashboard.as_view(), name="student_details", ), - url( + re_path( r"^play/details/independent$", IndependentStudentDashboard.as_view(), name="independent_student_details", ), - url(r"^play/account/$", student_edit_account, name="student_edit_account"), - url( + re_path( + r"^play/account/$", student_edit_account, name="student_edit_account" + ), + re_path( r"^play/account/independent/$", ratelimit( group=RATELIMIT_LOGIN_GROUP, @@ -383,136 +388,136 @@ )(independentStudentEditAccountView), name="independent_edit_account", ), - url( + re_path( r"^play/account/school_student/$", SchoolStudentEditAccountView.as_view(), name="school_student_edit_account", ), - url( + re_path( r"^play/join/$", student_join_organisation, name="student_join_organisation", ), - url(r"^about", about, name="about"), - url(r"^getinvolved", getinvolved, name="getinvolved"), - url(r"^contribute", contribute, name="contribute"), - url(r"^terms", terms, name="terms"), - url(r"^privacy-notice/$", privacy_notice, name="privacy_notice"), - url( + re_path(r"^about", about, name="about"), + re_path(r"^getinvolved", getinvolved, name="getinvolved"), + re_path(r"^contribute", contribute, name="contribute"), + re_path(r"^terms", terms, name="terms"), + re_path(r"^privacy-notice/$", privacy_notice, name="privacy_notice"), + re_path( r"^privacy-policy/$", privacy_notice, name="privacy_policy" ), # Keeping this to route from old URL - url(r"^teach/dashboard/$", dashboard_manage, name="dashboard"), - url( + re_path(r"^teach/dashboard/$", dashboard_manage, name="dashboard"), + re_path( r"^teach/dashboard/kick/(?P[0-9]+)/$", organisation_kick, name="organisation_kick", ), - url( + re_path( r"^teach/dashboard/toggle_admin/(?P[0-9]+)/$", organisation_toggle_admin, name="organisation_toggle_admin", ), - url( + re_path( r"^teach/dashboard/disable_2FA/(?P[0-9]+)/$", teacher_disable_2FA, name="teacher_disable_2FA", ), - url( + re_path( r"^teach/dashboard/school/leave/$", organisation_leave, name="organisation_leave", ), - url( + re_path( r"^teach/dashboard/student/accept/(?P[0-9]+)/$", teacher_accept_student_request, name="teacher_accept_student_request", ), - url( + re_path( r"^teach/dashboard/student/reject/(?P[0-9]+)/$", teacher_reject_student_request, name="teacher_reject_student_request", ), - url( + re_path( rf"^teach/class/(?P{ACCESS_CODE_REGEX})$", teacher_view_class, name="view_class", ), - url( + re_path( rf"^teach/class/delete/(?P{ACCESS_CODE_REGEX})$", teacher_delete_class, name="teacher_delete_class", ), - url( + re_path( rf"^teach/class/(?P{ACCESS_CODE_REGEX})/students/delete/$", teacher_delete_students, name="teacher_delete_students", ), - url( + re_path( rf"^teach/class/edit/(?P{ACCESS_CODE_REGEX})$", teacher_edit_class, name="teacher_edit_class", ), - url( + re_path( r"^teach/class/student/edit/(?P[0-9]+)/$", teacher_edit_student, name="teacher_edit_student", ), - url( + re_path( rf"^teach/class/(?P{ACCESS_CODE_REGEX})/password_reset/$", teacher_class_password_reset, name="teacher_class_password_reset", ), - url( + re_path( rf"^teach/class/(?P{ACCESS_CODE_REGEX})/students/dismiss/$", teacher_dismiss_students, name="teacher_dismiss_students", ), - url( + re_path( rf"^teach/class/(?P{ACCESS_CODE_REGEX})/students/move/$", teacher_move_students, name="teacher_move_students", ), - url( + re_path( r"^teach/dashboard/resend_invite/(?P[0-9a-f]+)/$", resend_invite_teacher, name="resend_invite_teacher", ), - url( + re_path( r"^teach/dashboard/toggle_admin_invite/(?P[0-9]+)/$", invite_toggle_admin, name="invite_toggle_admin", ), - url( + re_path( r"^teach/dashboard/delete_teacher_invite/(?P[0-9a-f]+)$", delete_teacher_invite, name="delete_teacher_invite", ), - url( + re_path( rf"^teach/class/(?P{ACCESS_CODE_REGEX})/students/move/disambiguate/$", teacher_move_students_to_class, name="teacher_move_students_to_class", ), - url(r"^delete/account/$", delete_account, name="delete_account"), - url( + re_path(r"^delete/account/$", delete_account, name="delete_account"), + re_path( r"^schools/anonymise/(?P\d+)/", AnonymiseOrphanSchoolsView.as_view(), name="anonymise_orphan_schools", ), - url( + re_path( r"^api/", include( [ - url( + re_path( r"^registered/(?P\d{4})/(?P\d{2})/(?P\d{2})/$", registered_users, name="registered-users", ), - url( + re_path( r"^lastconnectedsince/(?P\d{4})/(?P\d{2})/(?P\d{2})/$", last_connected_since, name="last-connected-since", ), - url( + re_path( r"^userspercountry/(?P(AF|AX|AL|DZ|AS|AD|AO|AI|AQ|AG|AR|AM|AW|AU|AT|AZ|BS|BH|BD|BB|BY|BE|BZ|BJ|BM|BT|BO|BQ|BA|BW|BV|BR|IO|BN|BG|BF|BI|KH|CM|CA|CV|KY|CF|TD|CL|CN|CX|CC|CO|KM|CG|CD|CK|CR|CI|HR|CU|CW|CY|CZ|DK|DJ|DM|DO|EC|EG|SV|GQ|ER|EE|ET|FK|FO|FJ|FI|FR|GF|PF|TF|GA|GM|GE|DE|GH|GI|GR|GL|GD|GP|GU|GT|GG|GN|GW|GY|HT|HM|VA|HN|HK|HU|IS|IN|ID|IR|IQ|IE|IM|IL|IT|JM|JP|JE|JO|KZ|KE|KI|KP|KR|KW|KG|LA|LV|LB|LS|LR|LY|LI|LT|LU|MO|MK|MG|MW|MY|MV|ML|MT|MH|MQ|MR|MU|YT|MX|FM|MD|MC|MN|ME|MS|MA|MZ|MM|NA|NR|NP|NL|NC|NZ|NI|NE|NG|NU|NF|MP|NO|OM|PK|PW|PS|PA|PG|PY|PE|PH|PN|PL|PT|PR|QA|RE|RO|RU|RW|BL|SH|KN|LC|MF|PM|VC|WS|SM|ST|SA|SN|RS|SC|SL|SG|SX|SK|SI|SB|SO|ZA|GS|SS|ES|LK|SD|SR|SJ|SZ|SE|CH|SY|TW|TJ|TZ|TH|TL|TG|TK|TO|TT|TN|TR|TM|TC|TV|UG|UA|AE|GB|US|UM|UY|UZ|VU|VE|VN|VG|VI|WF|EH|YE|ZM|ZW))/$", number_users_per_country, name="number_users_per_country", @@ -520,16 +525,16 @@ ] ), ), - url(r"^codingClub/$", coding_club, name="codingClub"), - url( + re_path(r"^codingClub/$", coding_club, name="codingClub"), + re_path( r"^codingClub/(?P[3-4])/", download_student_pack, name="download_student_pack", ), - url( + re_path( r"^removeFakeAccounts/", RemoveFakeAccounts.as_view(), name="remove_fake_accounts", ), - url(r"^celebrate/", ten_year_map_page, name="celebrate"), + re_path(r"^celebrate/", ten_year_map_page, name="celebrate"), ] diff --git a/portal/views/api.py b/portal/views/api.py index 8bfea2ad7..3de94790a 100644 --- a/portal/views/api.py +++ b/portal/views/api.py @@ -5,16 +5,16 @@ from common.models import Class, School, Student, Teacher, UserProfile from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User -from django.db.models import Exists, OuterRef from django.http import HttpRequest, HttpResponse from django.utils import timezone -from portal.app_settings import IS_CLOUD_SCHEDULER_FUNCTION from rest_framework import generics, permissions, serializers, status from rest_framework.authentication import SessionAuthentication from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework.reverse import reverse_lazy +from portal.app_settings import IS_CLOUD_SCHEDULER_FUNCTION + LOGGER = logging.getLogger(__name__) THREE_YEARS_IN_DAYS = 1095 @@ -23,7 +23,11 @@ @login_required(login_url=reverse_lazy("administration_login")) def registered_users(request, year, month, day): try: - nbr_reg = User.objects.filter(date_joined__startswith=datetime.date(int(year), int(month), int(day))).count() + nbr_reg = User.objects.filter( + date_joined__startswith=datetime.date( + int(year), int(month), int(day) + ) + ).count() return Response(nbr_reg) except ValueError: return HttpResponse(status=404) @@ -33,7 +37,9 @@ def registered_users(request, year, month, day): @login_required(login_url=reverse_lazy("administration_login")) def last_connected_since(request, year, month, day): try: - nbr_active_users = User.objects.filter(last_login__gte=datetime.date(int(year), int(month), int(day))).count() + nbr_active_users = User.objects.filter( + last_login__gte=datetime.date(int(year), int(month), int(day)) + ).count() return Response(nbr_active_users) except ValueError: return HttpResponse(status=404) @@ -45,7 +51,9 @@ def number_users_per_country(request, country): try: nbr_reg = ( Teacher.objects.filter(school__country__exact=country).count() - + Student.objects.filter(class_field__teacher__school__country__exact=country).count() + + Student.objects.filter( + class_field__teacher__school__country__exact=country + ).count() ) return Response(nbr_reg) except ValueError: @@ -67,7 +75,9 @@ class IsAdminOrGoogleAppEngine(permissions.IsAdminUser): """Checks whether the request is from a Google App Engine cron job.""" def has_permission(self, request: HttpRequest, view): - is_admin = super(IsAdminOrGoogleAppEngine, self).has_permission(request, view) + is_admin = super(IsAdminOrGoogleAppEngine, self).has_permission( + request, view + ) return IS_CLOUD_SCHEDULER_FUNCTION(request) or is_admin @@ -107,7 +117,9 @@ def anonymise(user): # if user is admin and the school does not have another admin, appoint another teacher as admin if is_admin: - teachers = Teacher.objects.filter(school=school).order_by("new_user__last_name", "new_user__first_name") + teachers = Teacher.objects.filter(school=school).order_by( + "new_user__last_name", "new_user__first_name" + ) if not teachers: # no other teacher, anonymise the school school.anonymise() @@ -134,10 +146,14 @@ class InactiveUsersView(generics.ListAPIView): """ queryset = User.objects.filter(is_active=True) & ( - User.objects.filter(last_login__lte=timezone.now() - timezone.timedelta(days=THREE_YEARS_IN_DAYS)) + User.objects.filter( + last_login__lte=timezone.now() + - timezone.timedelta(days=THREE_YEARS_IN_DAYS) + ) | User.objects.filter( last_login__isnull=True, - date_joined__lte=timezone.now() - timezone.timedelta(days=THREE_YEARS_IN_DAYS), + date_joined__lte=timezone.now() + - timezone.timedelta(days=THREE_YEARS_IN_DAYS), ) ) authentication_classes = (SessionAuthentication,) @@ -156,6 +172,7 @@ class RemoveFakeAccounts(generics.ListAPIView): """ This API endpoint will delete suspicious accounts that have the same first and last name and who are not verified """ + authentication_classes = (SessionAuthentication,) serializer_class = InactiveUserSerializer permission_classes = (IsAdminOrGoogleAppEngine,) @@ -178,17 +195,14 @@ class AnonymiseOrphanSchoolsView(generics.ListAPIView): def get(self, request: HttpRequest, start_id): # Re-anonymise all inactive teachers so their schools (if necessary) and classes/students are anonymised - for teacher in Teacher._base_manager.filter(pk__gte=start_id, new_user__is_active=False): + for teacher in Teacher._base_manager.filter( + pk__gte=start_id, new_user__is_active=False + ): anonymise(teacher.new_user) LOGGER.info(f"Anonymised teacher ID {teacher.pk}") # Anonymise schools without any teachers - for school in School.objects.filter( - Exists( - Teacher._base_manager.filter(school=OuterRef("pk")), - negated=True, - ) - ): + for school in School.objects.filter(teacher_school__isnull=True): school.anonymise() return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/portal/views/home.py b/portal/views/home.py index 47baa59e4..a6c101f11 100644 --- a/portal/views/home.py +++ b/portal/views/home.py @@ -33,9 +33,12 @@ ) from portal.strings.coding_club import CODING_CLUB_BANNER from portal.strings.home_learning import HOME_LEARNING_BANNER -from portal.strings.ten_year_map import TEN_YEAR_MAP_BANNER, TEN_YEAR_MAP_HEADLINE +from portal.strings.ten_year_map import ( + TEN_YEAR_MAP_BANNER, + TEN_YEAR_MAP_HEADLINE, +) from portal.templatetags.app_tags import cloud_storage -from portal.views.teacher.teach import DownloadType, count_student_pack_downloads_click +from portal.views.teacher.teach import count_student_pack_downloads_click LOGGER = logging.getLogger(__name__) @@ -65,11 +68,17 @@ def render_signup_form(request): invalid_form = False teacher_signup_form = TeacherSignupForm(prefix="teacher_signup") - independent_student_signup_form = IndependentStudentSignupForm(prefix="independent_student_signup") + independent_student_signup_form = IndependentStudentSignupForm( + prefix="independent_student_signup" + ) if request.method == "POST": + print("IN POST") + print(request.POST) if "teacher_signup-teacher_email" in request.POST: - teacher_signup_form = TeacherSignupForm(request.POST, prefix="teacher_signup") + teacher_signup_form = TeacherSignupForm( + request.POST, prefix="teacher_signup" + ) if not captcha.CAPTCHA_ENABLED: remove_captcha_from_forms(teacher_signup_form) @@ -123,11 +132,15 @@ def process_signup_form(request, data): [email], personalization_values={ "EMAIL": email, - "LOGIN_URL": request.build_absolute_uri(reverse("teacher_login")), + "LOGIN_URL": request.build_absolute_uri( + reverse("teacher_login") + ), }, ) else: - LOGGER.warn(f"Ratelimit teacher {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}") + LOGGER.warn( + f"Ratelimit teacher {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}" + ) else: teacher = Teacher.objects.factory( first_name=data["teacher_first_name"], @@ -138,7 +151,9 @@ def process_signup_form(request, data): send_verification_email(request, teacher.user.user, data) - TotalActivity.objects.update(teacher_registrations=F("teacher_registrations") + 1) + TotalActivity.objects.update( + teacher_registrations=F("teacher_registrations") + 1 + ) return render( request, @@ -166,11 +181,15 @@ def process_independent_student_signup_form(request, data): [email], personalization_values={ "EMAIL": email, - "LOGIN_URL": request.build_absolute_uri(reverse("independent_student_login")), + "LOGIN_URL": request.build_absolute_uri( + reverse("independent_student_login") + ), }, ) else: - LOGGER.warning(f"Ratelimit independent {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}") + LOGGER.warning( + f"Ratelimit independent {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}" + ) return render( request, "portal/email_verification_needed.html", @@ -188,7 +207,9 @@ def process_independent_student_signup_form(request, data): send_verification_email(request, student.new_user, data, age=age) - TotalActivity.objects.update(independent_registrations=F("independent_registrations") + 1) + TotalActivity.objects.update( + independent_registrations=F("independent_registrations") + 1 + ) return render( request, @@ -199,7 +220,10 @@ def process_independent_student_signup_form(request, data): def is_developer(request): - return hasattr(request.user, "userprofile") and request.user.userprofile.developer + return ( + hasattr(request.user, "userprofile") + and request.user.userprofile.developer + ) def redirect_teacher_to_correct_page(request, teacher): @@ -229,10 +253,14 @@ def home(request): # tests where the first Selenium test passes, but any following test # fails because it cannot find the Maintenance banner instance. try: - maintenance_banner = DynamicElement.objects.get(name="Maintenance banner") + maintenance_banner = DynamicElement.objects.get( + name="Maintenance banner" + ) if maintenance_banner.active: - messages.info(request, format_html(maintenance_banner.text), extra_tags="safe") + messages.info( + request, format_html(maintenance_banner.text), extra_tags="safe" + ) except ObjectDoesNotExist: pass @@ -250,7 +278,9 @@ def home(request): def coding_club(request): - return render(request, "portal/coding_club.html", {"BANNER": CODING_CLUB_BANNER}) + return render( + request, "portal/coding_club.html", {"BANNER": CODING_CLUB_BANNER} + ) def download_student_pack(request, student_pack_type): @@ -269,7 +299,9 @@ def home_learning(request): def ten_year_map_page(request): - messages.info(request, "This page is currently under construction.", extra_tags="safe") + messages.info( + request, "This page is currently under construction.", extra_tags="safe" + ) return render( request, "portal/ten_year_map.html", diff --git a/portal/views/registration.py b/portal/views/registration.py index 5e38e52c7..6d138d7b3 100644 --- a/portal/views/registration.py +++ b/portal/views/registration.py @@ -21,9 +21,9 @@ from django.shortcuts import render from django.template.response import TemplateResponse from django.urls import reverse_lazy -from django.utils.encoding import force_text +from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_decode -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from django.views.decorators.cache import never_cache from django.views.decorators.csrf import csrf_protect from django.views.decorators.debug import sensitive_post_parameters @@ -202,7 +202,7 @@ def password_reset_confirm( check_uidb64(uidb64, token) try: - uid = force_text(urlsafe_base64_decode(uidb64)) + uid = force_bytes(urlsafe_base64_decode(uidb64)) user = UserModel._default_manager.get(pk=uid) except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist): user = None diff --git a/portal/views/two_factor/core.py b/portal/views/two_factor/core.py index 09eb4e83d..9eab65033 100644 --- a/portal/views/two_factor/core.py +++ b/portal/views/two_factor/core.py @@ -2,7 +2,6 @@ TOTPDeviceForm, MethodForm, DeviceValidationForm, - YubiKeyDeviceForm, ) from two_factor.views.core import SetupView @@ -14,12 +13,10 @@ class CustomSetupView(SetupView): ("generator", TOTPDeviceForm), ("method", MethodForm), ("validation", DeviceValidationForm), - ("yubikey", YubiKeyDeviceForm), ) condition_dict = { "call": lambda self: self.get_method() == "call", "sms": lambda self: self.get_method() == "sms", "validation": lambda self: self.get_method() in ("sms", "call"), - "yubikey": lambda self: self.get_method() == "yubikey", } From a3f4a5038b044e167432d527dcac4446bc132a9e Mon Sep 17 00:00:00 2001 From: faucomte97 Date: Tue, 5 Nov 2024 19:21:18 +0000 Subject: [PATCH 04/14] Temporarily disable captcha fields --- portal/forms/play.py | 2 +- portal/forms/teach.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/portal/forms/play.py b/portal/forms/play.py index 8a3a6893f..9f9d3ad4d 100644 --- a/portal/forms/play.py +++ b/portal/forms/play.py @@ -222,7 +222,7 @@ class IndependentStudentSignupForm(forms.Form): widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Repeat password"}), ) - captcha = ReCaptchaField(widget=ReCaptchaV2Invisible) + # captcha = ReCaptchaField(widget=ReCaptchaV2Invisible) def clean_name(self): name = self.cleaned_data.get("name", None) diff --git a/portal/forms/teach.py b/portal/forms/teach.py index 9b30310d9..d19fd97bb 100644 --- a/portal/forms/teach.py +++ b/portal/forms/teach.py @@ -86,7 +86,7 @@ class TeacherSignupForm(InvitedTeacherForm): ), ) - captcha = ReCaptchaField(widget=ReCaptchaV2Invisible) + # captcha = ReCaptchaField(widget=ReCaptchaV2Invisible) class TeacherEditAccountForm(forms.Form): From 3477f8c7823d0bcac287fdfc83a996b19fcf2e68 Mon Sep 17 00:00:00 2001 From: faucomte97 Date: Tue, 5 Nov 2024 19:48:58 +0000 Subject: [PATCH 05/14] Install RR branch --- Pipfile | 1 + Pipfile.lock | 136 ++++++++++++++++++++++++++++++--------------------- setup.py | 3 +- 3 files changed, 81 insertions(+), 59 deletions(-) diff --git a/Pipfile b/Pipfile index 0d7fc8c9a..60e89b468 100644 --- a/Pipfile +++ b/Pipfile @@ -20,6 +20,7 @@ pytest-mock = "*" pytest-order = "*" pytest-xdist = "*" pyvirtualdisplay = "*" +rapid-router = {ref = "django4", git = "https://github.com/ocadotechnology/rapid-router.git"} responses = "==0.18.0" selenium = "==4.9.0" snapshottest = "==1.0.0a1" diff --git a/Pipfile.lock b/Pipfile.lock index 907e43b2f..11da9c373 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "0274e773964a9e94171cb04ba5c7e996c301fa10fd4808c4c9dba2a11964e642" + "sha256": "c9dacf5b37e3d488543c2f75ea8bfc9327591883b3d12fb427d9fcef3982329e" }, "pipfile-spec": 6, "requires": { @@ -318,62 +318,64 @@ }, "numpy": { "hashes": [ - "sha256:05b2d4e667895cc55e3ff2b56077e4c8a5604361fc21a042845ea3ad67465aa8", - "sha256:12edb90831ff481f7ef5f6bc6431a9d74dc0e5ff401559a71e5e4611d4f2d466", - "sha256:13311c2db4c5f7609b462bc0f43d3c465424d25c626d95040f073e30f7570e35", - "sha256:13532a088217fa624c99b843eeb54640de23b3414b14aa66d023805eb731066c", - "sha256:13602b3174432a35b16c4cfb5de9a12d229727c3dd47a6ce35111f2ebdf66ff4", - "sha256:1600068c262af1ca9580a527d43dc9d959b0b1d8e56f8a05d830eea39b7c8af6", - "sha256:1b8cde4f11f0a975d1fd59373b32e2f5a562ade7cde4f85b7137f3de8fbb29a0", - "sha256:1c193d0b0238638e6fc5f10f1b074a6993cb13b0b431f64079a509d63d3aa8b7", - "sha256:1ebec5fd716c5a5b3d8dfcc439be82a8407b7b24b230d0ad28a81b61c2f4659a", - "sha256:242b39d00e4944431a3cd2db2f5377e15b5785920421993770cddb89992c3f3a", - "sha256:259ec80d54999cc34cd1eb8ded513cb053c3bf4829152a2e00de2371bd406f5e", - "sha256:2abbf905a0b568706391ec6fa15161fad0fb5d8b68d73c461b3c1bab6064dd62", - "sha256:2cbba4b30bf31ddbe97f1c7205ef976909a93a66bb1583e983adbd155ba72ac2", - "sha256:2ffef621c14ebb0188a8633348504a35c13680d6da93ab5cb86f4e54b7e922b5", - "sha256:30d53720b726ec36a7f88dc873f0eec8447fbc93d93a8f079dfac2629598d6ee", - "sha256:32e16a03138cabe0cb28e1007ee82264296ac0983714094380b408097a418cfe", - "sha256:43cca367bf94a14aca50b89e9bc2061683116cfe864e56740e083392f533ce7a", - "sha256:456e3b11cb79ac9946c822a56346ec80275eaf2950314b249b512896c0d2505e", - "sha256:4d6ec0d4222e8ffdab1744da2560f07856421b367928026fb540e1945f2eeeaf", - "sha256:5006b13a06e0b38d561fab5ccc37581f23c9511879be7693bd33c7cd15ca227c", - "sha256:675c741d4739af2dc20cd6c6a5c4b7355c728167845e3c6b0e824e4e5d36a6c3", - "sha256:6cdb606a7478f9ad91c6283e238544451e3a95f30fb5467fbf715964341a8a86", - "sha256:6d95f286b8244b3649b477ac066c6906fbb2905f8ac19b170e2175d3d799f4df", - "sha256:76322dcdb16fccf2ac56f99048af32259dcc488d9b7e25b51e5eca5147a3fb98", - "sha256:7c1c60328bd964b53f8b835df69ae8198659e2b9302ff9ebb7de4e5a5994db3d", - "sha256:860ec6e63e2c5c2ee5e9121808145c7bf86c96cca9ad396c0bd3e0f2798ccbe2", - "sha256:8e00ea6fc82e8a804433d3e9cedaa1051a1422cb6e443011590c14d2dea59146", - "sha256:9c6c754df29ce6a89ed23afb25550d1c2d5fdb9901d9c67a16e0b16eaf7e2550", - "sha256:a26ae94658d3ba3781d5e103ac07a876b3e9b29db53f68ed7df432fd033358a8", - "sha256:a65acfdb9c6ebb8368490dbafe83c03c7e277b37e6857f0caeadbbc56e12f4fb", - "sha256:a7d80b2e904faa63068ead63107189164ca443b42dd1930299e0d1cb041cec2e", - "sha256:a84498e0d0a1174f2b3ed769b67b656aa5460c92c9554039e11f20a05650f00d", - "sha256:ab4754d432e3ac42d33a269c8567413bdb541689b02d93788af4131018cbf366", - "sha256:ad369ed238b1959dfbade9018a740fb9392c5ac4f9b5173f420bd4f37ba1f7a0", - "sha256:b1d0fcae4f0949f215d4632be684a539859b295e2d0cb14f78ec231915d644db", - "sha256:b42a1a511c81cc78cbc4539675713bbcf9d9c3913386243ceff0e9429ca892fe", - "sha256:bd33f82e95ba7ad632bc57837ee99dba3d7e006536200c4e9124089e1bf42426", - "sha256:bdd407c40483463898b84490770199d5714dcc9dd9b792f6c6caccc523c00952", - "sha256:c6eef7a2dbd0abfb0d9eaf78b73017dbfd0b54051102ff4e6a7b2980d5ac1a03", - "sha256:c82af4b2ddd2ee72d1fc0c6695048d457e00b3582ccde72d8a1c991b808bb20f", - "sha256:d666cb72687559689e9906197e3bec7b736764df6a2e58ee265e360663e9baf7", - "sha256:d7bf0a4f9f15b32b5ba53147369e94296f5fffb783db5aacc1be15b4bf72f43b", - "sha256:d82075752f40c0ddf57e6e02673a17f6cb0f8eb3f587f63ca1eaab5594da5b17", - "sha256:da65fb46d4cbb75cb417cddf6ba5e7582eb7bb0b47db4b99c9fe5787ce5d91f5", - "sha256:e2b49c3c0804e8ecb05d59af8386ec2f74877f7ca8fd9c1e00be2672e4d399b1", - "sha256:e585c8ae871fd38ac50598f4763d73ec5497b0de9a0ab4ef5b69f01c6a046142", - "sha256:e8d3ca0a72dd8846eb6f7dfe8f19088060fcb76931ed592d29128e0219652884", - "sha256:ef444c57d664d35cac4e18c298c47d7b504c66b17c2ea91312e979fcfbdfb08a", - "sha256:f1eb068ead09f4994dec71c24b2844f1e4e4e013b9629f812f292f04bd1510d9", - "sha256:f2ded8d9b6f68cc26f8425eda5d3877b47343e68ca23d0d0846f4d312ecaa445", - "sha256:f751ed0a2f250541e19dfca9f1eafa31a392c71c832b6bb9e113b10d050cb0f1", - "sha256:faa88bc527d0f097abdc2c663cddf37c05a1c2f113716601555249805cf573f1", - "sha256:fc44e3c68ff00fd991b59092a54350e6e4911152682b4782f68070985aa9e648" + "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe", + "sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0", + "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48", + "sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a", + "sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564", + "sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958", + "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17", + "sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0", + "sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee", + "sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b", + "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4", + "sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4", + "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6", + "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4", + "sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d", + "sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f", + "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f", + "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f", + "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56", + "sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9", + "sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd", + "sha256:72dcc4a35a8515d83e76b58fdf8113a5c969ccd505c8a946759b24e3182d1f23", + "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed", + "sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a", + "sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098", + "sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1", + "sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512", + "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f", + "sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09", + "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f", + "sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc", + "sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8", + "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0", + "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761", + "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef", + "sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5", + "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e", + "sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b", + "sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d", + "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43", + "sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c", + "sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41", + "sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff", + "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408", + "sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2", + "sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9", + "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57", + "sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb", + "sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9", + "sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3", + "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a", + "sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0", + "sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e", + "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598", + "sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4" ], "markers": "python_version >= '3.10'", - "version": "==2.1.2" + "version": "==2.1.3" }, "pandas": { "hashes": [ @@ -539,7 +541,7 @@ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "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.9.0.post0" }, "pytz": { @@ -688,7 +690,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" }, "sqlparse": { @@ -992,6 +994,14 @@ "markers": "python_version >= '3.8'", "version": "==4.2.16" }, + "django-reverse-js": { + "hashes": [ + "sha256:7d626f4d660604e4c2623a494a08ddb70587375110cf4e7bb6f56eeb4471630f", + "sha256:89c15e3f1bd656a6f7c7f3641144c0a79d42ec692182c8edd6a91061674f6b62" + ], + "markers": "python_version >= '3.10'", + "version": "==0.1.7" + }, "django-selenium-clean": { "hashes": [ "sha256:74c002026047f493d2de4802414ccfa860de15f9c21fd564e676b5fc12268a8d", @@ -1105,6 +1115,14 @@ "markers": "python_version >= '3.8'", "version": "==1.5.0" }, + "pyhamcrest": { + "hashes": [ + "sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316", + "sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29" + ], + "markers": "python_version >= '3.5'", + "version": "==2.0.2" + }, "pypdf2": { "hashes": [ "sha256:6315e7a6bd49afd695925c243d72876e2dbfb774ecd551b3115e87d18df29599", @@ -1184,6 +1202,10 @@ "index": "pypi", "version": "==3.0" }, + "rapid-router": { + "git": "https://github.com/ocadotechnology/rapid-router.git", + "ref": "87c34a8bb7651b7213d69e6ae93dfd5f96550dee" + }, "requests": { "hashes": [ "sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289", diff --git a/setup.py b/setup.py index cc88f4b3c..3cd18e7ce 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,6 @@ "django-recaptcha==4.0.0", "pyyaml==6.0.2", "importlib-metadata==4.13.0", - # "rapid-router>=6.3.6", "reportlab==3.6.13", "django-formtools==2.5.1", "django-otp==1.5.4", @@ -36,7 +35,7 @@ "django-sekizai==4.1.0", "django-classy-tags==4.1.0", "phonenumbers==8.12.12", - # f"cfl-common=={version}", + f"cfl-common=={version}", "django-ratelimit==3.0.1", "django-preventconcurrentlogins==0.8.2", "setuptools==74.0.0", From 4a33afdac23718f8dcf6d734b1b1c2e864aa4a3e Mon Sep 17 00:00:00 2001 From: faucomte97 Date: Tue, 5 Nov 2024 20:05:12 +0000 Subject: [PATCH 06/14] Clean dependencies --- Pipfile.lock | 19 ++++++++++--------- cfl_common/setup.py | 2 +- setup.py | 1 - 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 11da9c373..912089312 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -215,11 +215,11 @@ }, "django-phonenumber-field": { "hashes": [ - "sha256:72a3e7a3e7493bf2a12c07a3bc77ce89813acc16592bf04d0eee3b5a452097ed", - "sha256:a31b4f05ac0ff898661516c84940f83adb5cdcf0ae4b9b1d31bb8ad3ff345b58" + "sha256:196c917b70c01a98e327f482eb8a4a4a55a29891db551f99078585397370b3ba", + "sha256:8a560fe1b01b94c9de8cde22bc373b695f023cc6df4baba00264cb079da9f631" ], - "markers": "python_version >= '3.7'", - "version": "==6.4.0" + "markers": "python_version >= '3.8'", + "version": "==8.0.0" }, "django-pipeline": { "hashes": [ @@ -267,10 +267,11 @@ }, "django-two-factor-auth": { "hashes": [ - "sha256:1bfebce0d627027520a2442901dde07fac2ecfd073da92c78ce0c1171121a7cd", - "sha256:36aefcd20bb13c9813a33f8cbdc54542da95758d81adf7f9ceb6636f164bf056" + "sha256:622e78b0d6cf12eeafa239665d99c1221c399228f2f902fe478aea7759995e0e", + "sha256:a2dcc3efedd0ce4b4c14d389766c9fd8e13cabdff5e4e1b645adeb650c550cf7" ], - "version": "==1.15.1" + "markers": "python_version >= '3.8'", + "version": "==1.17.0" }, "djangorestframework": { "hashes": [ @@ -541,7 +542,7 @@ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "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.9.0.post0" }, "pytz": { @@ -690,7 +691,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" }, "sqlparse": { diff --git a/cfl_common/setup.py b/cfl_common/setup.py index cd2cff55a..17c5d94c4 100644 --- a/cfl_common/setup.py +++ b/cfl_common/setup.py @@ -19,7 +19,7 @@ install_requires=[ "django==4.2.16", "djangorestframework==3.15.1", - "django-two-factor-auth==1.15.1", + "django-two-factor-auth==1.17.0", "django-countries==7.6.1", "pyjwt==2.6.0", "pgeocode==0.4.0", diff --git a/setup.py b/setup.py index 3cd18e7ce..d97ed6968 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,6 @@ "django-sekizai==4.1.0", "django-classy-tags==4.1.0", "phonenumbers==8.12.12", - f"cfl-common=={version}", "django-ratelimit==3.0.1", "django-preventconcurrentlogins==0.8.2", "setuptools==74.0.0", From d637a13a045bc5e408f83b40707122d51b3f1909 Mon Sep 17 00:00:00 2001 From: faucomte97 Date: Tue, 5 Nov 2024 20:09:23 +0000 Subject: [PATCH 07/14] Fix some 2fa stuff --- portal/templates/two_factor/profile/profile.html | 2 ++ portal/views/two_factor/profile.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/portal/templates/two_factor/profile/profile.html b/portal/templates/two_factor/profile/profile.html index e2c9d2ad3..ff864edf2 100644 --- a/portal/templates/two_factor/profile/profile.html +++ b/portal/templates/two_factor/profile/profile.html @@ -12,6 +12,7 @@

{% block title %}{% trans "Account Security" %}{% endblock %}

{% trans "Backup Phone Numbers" %}

{% blocktrans trimmed %}If your primary method is not available, we are able to send backup tokens to the phone numbers listed below.{% endblocktrans %}

+{% if backup_phones %}
    {% for phone in backup_phones %}
  • @@ -25,6 +26,7 @@

    {% trans "Backup Phone Numbers" %}

  • {% endfor %}
+{% endif %}

{% trans "Add Phone Number" %}

{% endif %} diff --git a/portal/views/two_factor/profile.py b/portal/views/two_factor/profile.py index 32c85dbee..2640570f9 100644 --- a/portal/views/two_factor/profile.py +++ b/portal/views/two_factor/profile.py @@ -1,11 +1,11 @@ +from django.utils.decorators import method_decorator from django.views.decorators.cache import never_cache from two_factor.views.profile import DisableView -from two_factor.views.utils import class_view_decorator from .form import DisableForm # This is not changed but imports the from form.py so it overwrites the disable 2FA form -@class_view_decorator(never_cache) +@method_decorator(never_cache, name='dispatch') class CustomDisableView(DisableView): form_class = DisableForm From 15234465e6e15cda1d76e66f6a43f331cab73591 Mon Sep 17 00:00:00 2001 From: faucomte97 Date: Tue, 5 Nov 2024 20:26:20 +0000 Subject: [PATCH 08/14] Fix cypress test --- portal/tests/cypress/integration/admin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal/tests/cypress/integration/admin.js b/portal/tests/cypress/integration/admin.js index 2fd4a7391..77d158cd0 100644 --- a/portal/tests/cypress/integration/admin.js +++ b/portal/tests/cypress/integration/admin.js @@ -52,5 +52,5 @@ one special character. cy.deleteUser('testadmin') // Logout - cy.get('[href="/administration/logout/"]').click() + cy.get('[action="/administration/logout/"]').click() } From a74c7991e6f2ef8719d9341d6a9f68206f13a801 Mon Sep 17 00:00:00 2001 From: faucomte97 Date: Tue, 5 Nov 2024 20:48:00 +0000 Subject: [PATCH 09/14] Update two factor elements --- portal/templates/two_factor/core/login.html | 21 +++- portal/templates/two_factor/core/setup.html | 107 +++++++++++--------- portal/urls.py | 45 +------- portal/views/two_factor/__init__.py | 0 portal/views/two_factor/core.py | 22 ---- portal/views/two_factor/form.py | 7 -- portal/views/two_factor/profile.py | 11 -- 7 files changed, 77 insertions(+), 136 deletions(-) delete mode 100644 portal/views/two_factor/__init__.py delete mode 100644 portal/views/two_factor/core.py delete mode 100644 portal/views/two_factor/form.py delete mode 100644 portal/views/two_factor/profile.py diff --git a/portal/templates/two_factor/core/login.html b/portal/templates/two_factor/core/login.html index 54c20101c..9b13f1fc0 100644 --- a/portal/templates/two_factor/core/login.html +++ b/portal/templates/two_factor/core/login.html @@ -1,4 +1,5 @@ -{% extends "two_factor/_base_focus.html" %} +{# Overriden the original "two_factor" template to remove extending base template and slightly adjust style #} + {% load i18n %} {% load two_factor_tags %} @@ -7,7 +8,13 @@ {% endblock %} {% block content %} -

{% block title %}{% trans "Login" %}{% endblock %}

+ +
+ + + + +
{% endif %} {% endblock %} {% endblock %} diff --git a/portal/templates/two_factor/core/setup.html b/portal/templates/two_factor/core/setup.html index 673a5ca60..192e46727 100644 --- a/portal/templates/two_factor/core/setup.html +++ b/portal/templates/two_factor/core/setup.html @@ -3,55 +3,64 @@ {% extends "two_factor/_base_focus.html" %} {% load i18n %} +{% block extra_media %} +{{ form.media }} +{% endblock %} + {% block content %} -

{% block title %}{% trans "Two-factor authentication" %}{% endblock %}

- {% if wizard.steps.current == 'welcome' %} -

{% blocktrans trimmed %}You are about to take your account security to the - next level. Follow the steps in this wizard to enable two-factor - authentication.{% endblocktrans %}

- {% elif wizard.steps.current == 'method' %} -

{% blocktrans trimmed %}Please select which authentication method you would - like to use.{% endblocktrans %}

- {% elif wizard.steps.current == 'generator' %} -

{% blocktrans trimmed %}Two-factor authentication is not currently set up on your account. - Enable two-factor authentication (2FA) for enhanced account security - {% endblocktrans %}

-

{% blocktrans trimmed %}To start using a token generator, please use your - smartphone to scan the QR code below. For example, use Google - Authenticator. - {% endblocktrans %}

-

QR Code

- {% elif wizard.steps.current == 'sms' %} -

{% blocktrans trimmed %}Please enter the phone number you wish to receive the - text messages on. This number will be validated in the next step. - {% endblocktrans %}

- {% elif wizard.steps.current == 'call' %} -

{% blocktrans trimmed %}Please enter the phone number you wish to be called on. - This number will be validated in the next step. {% endblocktrans %}

- {% elif wizard.steps.current == 'validation' %} - {% if challenge_succeeded %} - {% if device.method == 'call' %} -

{% blocktrans trimmed %}We are calling your phone right now, please enter the - digits you hear.{% endblocktrans %}

- {% elif device.method == 'sms' %} -

{% blocktrans trimmed %}We sent you a text message, please enter the tokens we - sent.{% endblocktrans %}

- {% endif %} - {% else %} - - {% endif %} - {% elif wizard.steps.current == 'yubikey' %} -

{% blocktrans trimmed %}To identify and verify your YubiKey, please insert a - token in the field below. Your YubiKey will be linked to your - account.{% endblocktrans %}

- {% endif %} +

{% block title %}{% trans "Enable Two-Factor Authentication" %}{% endblock %}

+{% if wizard.steps.current == 'welcome' %} +

{% blocktrans trimmed %}You are about to take your account security to the + next level. Follow the steps in this wizard to enable two-factor + authentication.{% endblocktrans %}

+{% elif wizard.steps.current == 'method' %} +

{% blocktrans trimmed %}Please select which authentication method you would + like to use.{% endblocktrans %}

+{% elif wizard.steps.current == 'generator' %} +

{% blocktrans trimmed %}To start using a token generator, please use your + smartphone to scan the QR code below. For example, use Google + Authenticator.{% endblocktrans %}

+

QR Code

+

{% blocktrans trimmed %}Alternatively you can use the following secret to + set up TOTP in your authenticator or password manager manually.{% endblocktrans %}

+

{% translate "TOTP Secret:" %} {{ secret_key }}

+

{% blocktrans %}Then, enter the token generated by the app.{% endblocktrans %}

+ +{% elif wizard.steps.current == 'sms' %} +

{% blocktrans trimmed %}Please enter the phone number you wish to receive the + text messages on. This number will be validated in the next step. + {% endblocktrans %}

+{% elif wizard.steps.current == 'call' %} +

{% blocktrans trimmed %}Please enter the phone number you wish to be called on. + This number will be validated in the next step. {% endblocktrans %}

+{% elif wizard.steps.current == 'validation' %} +{% if challenge_succeeded %} +{% if device.method == 'call' %} +

{% blocktrans trimmed %}We are calling your phone right now, please enter the + digits you hear.{% endblocktrans %}

+{% elif device.method == 'sms' %} +

{% blocktrans trimmed %}We sent you a text message, please enter the tokens we + sent.{% endblocktrans %}

+{% endif %} +{% else %} + +{% endif %} +{% elif wizard.steps.current == 'yubikey' %} +

{% blocktrans trimmed %}To identify and verify your YubiKey, please insert a + token in the field below. Your YubiKey will be linked to your + account.{% endblocktrans %}

+{% endif %} + +
{% csrf_token %} + {% include "two_factor/_wizard_forms.html" %} + + {# hidden submit button to enable [enter] key #} + - {% csrf_token %} - {% include "two_factor/setup_wizard_token.html" %} - {% include "two_factor/_wizard_actions_enable_2fa.html" %} -
+ {% include "two_factor/_wizard_actions.html" %} + {% endblock %} diff --git a/portal/urls.py b/portal/urls.py index e2cabb90d..393fc6fbe 100644 --- a/portal/urls.py +++ b/portal/urls.py @@ -1,16 +1,10 @@ -from common.permissions import teacher_verified from django.http import HttpResponse from django.urls import include, path, re_path from django.views.generic import RedirectView from django.views.generic.base import TemplateView from django.views.i18n import JavaScriptCatalog from game.views.level import play_default_level -from two_factor.views import ( - BackupTokensView, - ProfileView, - QRGeneratorView, - SetupCompleteView, -) +from two_factor.urls import urlpatterns as tf_urls from portal.helpers.decorators import ratelimit from portal.helpers.ratelimit import ( @@ -107,41 +101,9 @@ teacher_print_reminder_cards, teacher_view_class, ) -from portal.views.two_factor.core import CustomSetupView -from portal.views.two_factor.profile import CustomDisableView js_info_dict = {"packages": ("conf.locale",)} -two_factor_patterns = [ - re_path( - r"^account/two_factor/setup/$", CustomSetupView.as_view(), name="setup" - ), - re_path( - r"^account/two_factor/qrcode/$", QRGeneratorView.as_view(), name="qr" - ), - re_path( - r"^account/two_factor/setup/complete/$", - SetupCompleteView.as_view(), - name="setup_complete", - ), - re_path( - r"^account/two_factor/backup/tokens/$", - teacher_verified(BackupTokensView.as_view()), - name="backup_tokens", - ), - re_path( - r"^account/two_factor/$", - teacher_verified(ProfileView.as_view()), - name="profile", - ), - re_path( - r"^account/two_factor/disable/$", - teacher_verified(CustomDisableView.as_view()), - name="disable", - ), -] - - urlpatterns = [ path( "cron/", @@ -211,10 +173,7 @@ TemplateView.as_view(template_name="portal/locked_out.html"), name="locked_out", ), - re_path( - r"^", - include((two_factor_patterns, "two_factor"), namespace="two_factor"), - ), + path("", include(tf_urls)), re_path(r"^i18n/", include("django.conf.urls.i18n")), re_path(r"^jsi18n/$", JavaScriptCatalog.as_view(), js_info_dict), re_path( diff --git a/portal/views/two_factor/__init__.py b/portal/views/two_factor/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/portal/views/two_factor/core.py b/portal/views/two_factor/core.py deleted file mode 100644 index 9eab65033..000000000 --- a/portal/views/two_factor/core.py +++ /dev/null @@ -1,22 +0,0 @@ -from two_factor.forms import ( - TOTPDeviceForm, - MethodForm, - DeviceValidationForm, -) -from two_factor.views.core import SetupView - - -# This custom class gets rid of the 'welcome' step of 2FA -# which the new design not needs any more -class CustomSetupView(SetupView): - form_list = ( - ("generator", TOTPDeviceForm), - ("method", MethodForm), - ("validation", DeviceValidationForm), - ) - - condition_dict = { - "call": lambda self: self.get_method() == "call", - "sms": lambda self: self.get_method() == "sms", - "validation": lambda self: self.get_method() in ("sms", "call"), - } diff --git a/portal/views/two_factor/form.py b/portal/views/two_factor/form.py deleted file mode 100644 index 9fe0af725..000000000 --- a/portal/views/two_factor/form.py +++ /dev/null @@ -1,7 +0,0 @@ -from django import forms - - -# This is the form that asks if the user wants to disable 2FA after clicking disable -# 2FA, setting it to always checked and hidden in CSS -class DisableForm(forms.Form): - understand = forms.BooleanField(label="", initial=True) diff --git a/portal/views/two_factor/profile.py b/portal/views/two_factor/profile.py deleted file mode 100644 index 2640570f9..000000000 --- a/portal/views/two_factor/profile.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.utils.decorators import method_decorator -from django.views.decorators.cache import never_cache -from two_factor.views.profile import DisableView - -from .form import DisableForm - - -# This is not changed but imports the from form.py so it overwrites the disable 2FA form -@method_decorator(never_cache, name='dispatch') -class CustomDisableView(DisableView): - form_class = DisableForm From 03b54e24a7ebf28e57f8c8046b7146ee9cf52549 Mon Sep 17 00:00:00 2001 From: faucomte97 Date: Tue, 5 Nov 2024 21:09:25 +0000 Subject: [PATCH 10/14] Bring back custom views --- portal/urls.py | 44 ++++++++++++++++++++++++++++- portal/views/two_factor/__init__.py | 0 portal/views/two_factor/core.py | 10 +++++++ portal/views/two_factor/form.py | 7 +++++ portal/views/two_factor/profile.py | 11 ++++++++ 5 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 portal/views/two_factor/__init__.py create mode 100644 portal/views/two_factor/core.py create mode 100644 portal/views/two_factor/form.py create mode 100644 portal/views/two_factor/profile.py diff --git a/portal/urls.py b/portal/urls.py index 393fc6fbe..5964f0c98 100644 --- a/portal/urls.py +++ b/portal/urls.py @@ -1,3 +1,4 @@ +from common.permissions import teacher_verified from django.http import HttpResponse from django.urls import include, path, re_path from django.views.generic import RedirectView @@ -5,6 +6,12 @@ from django.views.i18n import JavaScriptCatalog from game.views.level import play_default_level from two_factor.urls import urlpatterns as tf_urls +from two_factor.views import ( + BackupTokensView, + ProfileView, + QRGeneratorView, + SetupCompleteView, +) from portal.helpers.decorators import ratelimit from portal.helpers.ratelimit import ( @@ -101,9 +108,41 @@ teacher_print_reminder_cards, teacher_view_class, ) +from portal.views.two_factor.core import CustomSetupView +from portal.views.two_factor.profile import CustomDisableView js_info_dict = {"packages": ("conf.locale",)} +two_factor_patterns = [ + re_path( + r"^account/two_factor/setup/$", CustomSetupView.as_view(), name="setup" + ), + re_path( + r"^account/two_factor/qrcode/$", QRGeneratorView.as_view(), name="qr" + ), + re_path( + r"^account/two_factor/setup/complete/$", + SetupCompleteView.as_view(), + name="setup_complete", + ), + re_path( + r"^account/two_factor/backup/tokens/$", + teacher_verified(BackupTokensView.as_view()), + name="backup_tokens", + ), + re_path( + r"^account/two_factor/$", + teacher_verified(ProfileView.as_view()), + name="profile", + ), + re_path( + r"^account/two_factor/disable/$", + teacher_verified(CustomDisableView.as_view()), + name="disable", + ), +] + + urlpatterns = [ path( "cron/", @@ -173,7 +212,10 @@ TemplateView.as_view(template_name="portal/locked_out.html"), name="locked_out", ), - path("", include(tf_urls)), + re_path( + r"^", + include((two_factor_patterns, "two_factor"), namespace="two_factor"), + ), re_path(r"^i18n/", include("django.conf.urls.i18n")), re_path(r"^jsi18n/$", JavaScriptCatalog.as_view(), js_info_dict), re_path( diff --git a/portal/views/two_factor/__init__.py b/portal/views/two_factor/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/portal/views/two_factor/core.py b/portal/views/two_factor/core.py new file mode 100644 index 000000000..1b0b6ca3a --- /dev/null +++ b/portal/views/two_factor/core.py @@ -0,0 +1,10 @@ +from two_factor.forms import MethodForm +from two_factor.views.core import SetupView + + +# This custom class gets rid of the 'welcome' step of 2FA +# which the new design not needs any more +class CustomSetupView(SetupView): + form_list = ( + ("method", MethodForm), + ) diff --git a/portal/views/two_factor/form.py b/portal/views/two_factor/form.py new file mode 100644 index 000000000..9fe0af725 --- /dev/null +++ b/portal/views/two_factor/form.py @@ -0,0 +1,7 @@ +from django import forms + + +# This is the form that asks if the user wants to disable 2FA after clicking disable +# 2FA, setting it to always checked and hidden in CSS +class DisableForm(forms.Form): + understand = forms.BooleanField(label="", initial=True) diff --git a/portal/views/two_factor/profile.py b/portal/views/two_factor/profile.py new file mode 100644 index 000000000..2640570f9 --- /dev/null +++ b/portal/views/two_factor/profile.py @@ -0,0 +1,11 @@ +from django.utils.decorators import method_decorator +from django.views.decorators.cache import never_cache +from two_factor.views.profile import DisableView + +from .form import DisableForm + + +# This is not changed but imports the from form.py so it overwrites the disable 2FA form +@method_decorator(never_cache, name='dispatch') +class CustomDisableView(DisableView): + form_class = DisableForm From 94f7f3ba5782fdb6d60dd82502a06d3b5a266d6a Mon Sep 17 00:00:00 2001 From: faucomte97 Date: Wed, 6 Nov 2024 15:49:37 +0000 Subject: [PATCH 11/14] Upgrade recaptcha extended templates --- portal/forms/play.py | 2 +- portal/forms/teach.py | 2 +- .../includes/js_v2_invisible.html | 6 +++--- .../{captcha => django_recaptcha}/widget_v2_invisible.html | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) rename portal/templates/{captcha => django_recaptcha}/includes/js_v2_invisible.html (89%) rename portal/templates/{captcha => django_recaptcha}/widget_v2_invisible.html (66%) diff --git a/portal/forms/play.py b/portal/forms/play.py index 9f9d3ad4d..8a3a6893f 100644 --- a/portal/forms/play.py +++ b/portal/forms/play.py @@ -222,7 +222,7 @@ class IndependentStudentSignupForm(forms.Form): widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Repeat password"}), ) - # captcha = ReCaptchaField(widget=ReCaptchaV2Invisible) + captcha = ReCaptchaField(widget=ReCaptchaV2Invisible) def clean_name(self): name = self.cleaned_data.get("name", None) diff --git a/portal/forms/teach.py b/portal/forms/teach.py index d19fd97bb..9b30310d9 100644 --- a/portal/forms/teach.py +++ b/portal/forms/teach.py @@ -86,7 +86,7 @@ class TeacherSignupForm(InvitedTeacherForm): ), ) - # captcha = ReCaptchaField(widget=ReCaptchaV2Invisible) + captcha = ReCaptchaField(widget=ReCaptchaV2Invisible) class TeacherEditAccountForm(forms.Form): diff --git a/portal/templates/captcha/includes/js_v2_invisible.html b/portal/templates/django_recaptcha/includes/js_v2_invisible.html similarity index 89% rename from portal/templates/captcha/includes/js_v2_invisible.html rename to portal/templates/django_recaptcha/includes/js_v2_invisible.html index 14f9bfc3d..caa32ce6a 100644 --- a/portal/templates/captcha/includes/js_v2_invisible.html +++ b/portal/templates/django_recaptcha/includes/js_v2_invisible.html @@ -1,8 +1,8 @@ -{# This is adapted from django-recaptcha 2.0.5 to work on multiple reCAPTCHA's on the same page #} -{# See verifyCaptcha_{{ widget_uuid}} for the edit #} +