diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..0b049c6 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,28 @@ +[run] +# Measure coverage for the 'team_production_system' directory only +source = team_production_system + +# Omit test files and certain configuration files from coverage +omit = + team_production_system/tests/* + team_production_system/apps.py + team_production_system/manage.py + team_production_system/__init__.py + team_production_system/asgi.py + team_production_system/wsgi.py + team_production_system/admin.py + team_production_system/urls.py + team_production_system/migrations/* + +[report] +# Exclude the same files from the coverage report +omit = + team_production_system/tests/* + team_production_system/apps.py + team_production_system/manage.py + team_production_system/__init__.py + team_production_system/asgi.py + team_production_system/wsgi.py + team_production_system/admin.py + team_production_system/urls.py + team_production_system/migrations/* \ No newline at end of file diff --git a/Pipfile b/Pipfile index 8ab3b0f..9472905 100644 --- a/Pipfile +++ b/Pipfile @@ -23,6 +23,7 @@ boto3 = "*" sentry-sdk = {extras = ["django"], version = "*"} pytz = "*" flake8 = "*" +coverage = "*" [dev-packages] autopep8 = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 62dfa62..ea80272 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d0a4269f0606e6f7871a0c9f1021600182962de7a84085ae7586b4b0f6e78352" + "sha256": "b2381ceabbdd1912ddad1ecf532009d54716f926ddea4744a2e7150e955117e6" }, "pipfile-spec": 6, "requires": { @@ -21,32 +21,32 @@ "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e", "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed" ], - "markers": "python_version >= '3.7'", + "markers": "python_full_version >= '3.7.0'", "version": "==3.7.2" }, "boto3": { "hashes": [ - "sha256:662731e464d14af1035f44fc6a46b0e3112ee011ac0a5ed416d205daa3e15f25", - "sha256:f66e5c9dbe7f34383bcf64fa6070771355c11a44dd75c7f1279f2f37e1c89183" + "sha256:67001b3f512cbe2e00e352c65fb443b504e5e388fee39d73bcc42da1ae87d9e3", + "sha256:cb8af03f553f1c7db7137bc897785baeeaa97b8fde483eb1cdb1f1ef3cec9cb7" ], "index": "pypi", - "version": "==1.26.161" + "version": "==1.28.10" }, "botocore": { "hashes": [ - "sha256:6f35d59e230095aed7cd747604fe248fa384bebb7d09549077892f936a8ca3df", - "sha256:988b948be685006b43c4bbd8f5c0cb93e77c66deb70561994e0c5b31b5a67210" + "sha256:736a9412f405d6985570c4a87b533c2396dd8d4042d8c7a0ca14e73d4f1bcf9d", + "sha256:a3bfd3627a490faedf37d79373d6957936d7720888ca85466e0471cb921e4557" ], - "markers": "python_version >= '3.7'", - "version": "==1.29.165" + "markers": "python_full_version >= '3.7.0'", + "version": "==1.31.10" }, "certifi": { "hashes": [ - "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7", - "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716" + "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", + "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" ], "markers": "python_version >= '3.6'", - "version": "==2023.5.7" + "version": "==2023.7.22" }, "cffi": { "hashes": [ @@ -195,9 +195,75 @@ "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac", "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa" ], - "markers": "python_version >= '3.7'", + "markers": "python_full_version >= '3.7.0'", "version": "==3.2.0" }, + "coverage": { + "hashes": [ + "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f", + "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2", + "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a", + "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a", + "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01", + "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6", + "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7", + "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f", + "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02", + "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c", + "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063", + "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a", + "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5", + "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959", + "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97", + "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6", + "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f", + "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9", + "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5", + "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f", + "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562", + "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe", + "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9", + "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f", + "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb", + "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb", + "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1", + "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb", + "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250", + "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e", + "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511", + "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5", + "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59", + "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2", + "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d", + "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3", + "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4", + "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de", + "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9", + "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833", + "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0", + "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9", + "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d", + "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050", + "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d", + "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6", + "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353", + "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb", + "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e", + "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8", + "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495", + "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2", + "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd", + "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27", + "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1", + "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818", + "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4", + "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e", + "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850", + "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3" + ], + "index": "pypi", + "version": "==7.2.7" + }, "cryptography": { "hashes": [ "sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711", @@ -224,7 +290,7 @@ "sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f", "sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14" ], - "index": "pypi", + "markers": "python_full_version >= '3.7.0'", "version": "==41.0.2" }, "defusedxml": { @@ -253,11 +319,11 @@ }, "django-cors-headers": { "hashes": [ - "sha256:36a8d7a6dee6a85f872fe5916cc878a36d0812043866355438dfeda0b20b6b78", - "sha256:88a4bfae24b6404dd0e0640203cb27704a2a57fd546a429e5d821dfa53dd1acf" + "sha256:9ada212b0e2efd4a5e339360ffc869cb21ac5605e810afe69f7308e577ea5bde", + "sha256:f9749c6410fe738278bc2b6ef17f05195bc7b251693c035752d8257026af024f" ], "index": "pypi", - "version": "==4.1.0" + "version": "==4.2.0" }, "django-environ": { "hashes": [ @@ -327,7 +393,7 @@ "sha256:4c0d2e2513e12587d93501ac091781684a216c3ee614eb3b5a10586aef5ca845", "sha256:d27d4bcac2c6394f678dea8b4d0d511c6e18a7f2eb8aaeeb8a7de601aeb77c42" ], - "markers": "python_version >= '3.7'", + "markers": "python_full_version >= '3.7.0'", "version": "==5.2.2" }, "djoser": { @@ -348,11 +414,11 @@ }, "gunicorn": { "hashes": [ - "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", - "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" + "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0", + "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033" ], "index": "pypi", - "version": "==20.1.0" + "version": "==21.2.0" }, "idna": { "hashes": [ @@ -367,7 +433,7 @@ "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe" ], - "markers": "python_version >= '3.7'", + "markers": "python_full_version >= '3.7.0'", "version": "==1.0.1" }, "mccabe": { @@ -386,13 +452,21 @@ "markers": "python_version >= '3.6'", "version": "==3.2.2" }, + "packaging": { + "hashes": [ + "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", + "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==23.1" + }, "phonenumbers": { "hashes": [ - "sha256:634b277dc53933962961697211746f2fb7bf945a6725a853a1556cdb4a7531e4", - "sha256:adf80fc0027ce8eac990f5e1d3fa00022b481133d65fa40c1c40ae9020fc5a66" + "sha256:89671217c706cbaa3ced101deefafa779836feac3e059434d886ac31f09f32c0", + "sha256:e8ffd86b2e0b844fd6189fdb0927dbe8707cb03b59102cba5532b3ea305cc1bd" ], "index": "pypi", - "version": "==8.13.15" + "version": "==8.13.17" }, "pilkit": { "hashes": [ @@ -402,75 +476,65 @@ }, "pillow": { "hashes": [ - "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1", - "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba", - "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a", - "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799", - "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51", - "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb", - "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5", - "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270", - "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6", - "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47", - "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf", - "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e", - "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b", - "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66", - "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865", - "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec", - "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c", - "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1", - "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38", - "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906", - "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705", - "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef", - "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc", - "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f", - "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf", - "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392", - "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d", - "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe", - "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32", - "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5", - "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7", - "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44", - "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d", - "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3", - "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625", - "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e", - "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829", - "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089", - "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3", - "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78", - "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96", - "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964", - "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597", - "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99", - "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a", - "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140", - "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7", - "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16", - "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903", - "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1", - "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296", - "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572", - "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115", - "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a", - "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd", - "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4", - "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1", - "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb", - "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa", - "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a", - "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569", - "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c", - "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf", - "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082", - "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062", - "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579" + "sha256:00e65f5e822decd501e374b0650146063fbb30a7264b4d2744bdd7b913e0cab5", + "sha256:040586f7d37b34547153fa383f7f9aed68b738992380ac911447bb78f2abe530", + "sha256:0b6eb5502f45a60a3f411c63187db83a3d3107887ad0d036c13ce836f8a36f1d", + "sha256:1ce91b6ec08d866b14413d3f0bbdea7e24dfdc8e59f562bb77bc3fe60b6144ca", + "sha256:1f62406a884ae75fb2f818694469519fb685cc7eaff05d3451a9ebe55c646891", + "sha256:22c10cc517668d44b211717fd9775799ccec4124b9a7f7b3635fc5386e584992", + "sha256:3400aae60685b06bb96f99a21e1ada7bc7a413d5f49bce739828ecd9391bb8f7", + "sha256:349930d6e9c685c089284b013478d6f76e3a534e36ddfa912cde493f235372f3", + "sha256:368ab3dfb5f49e312231b6f27b8820c823652b7cd29cfbd34090565a015e99ba", + "sha256:38250a349b6b390ee6047a62c086d3817ac69022c127f8a5dc058c31ccef17f3", + "sha256:3a684105f7c32488f7153905a4e3015a3b6c7182e106fe3c37fbb5ef3e6994c3", + "sha256:3a82c40d706d9aa9734289740ce26460a11aeec2d9c79b7af87bb35f0073c12f", + "sha256:3b08d4cc24f471b2c8ca24ec060abf4bebc6b144cb89cba638c720546b1cf538", + "sha256:3ed64f9ca2f0a95411e88a4efbd7a29e5ce2cea36072c53dd9d26d9c76f753b3", + "sha256:3f07ea8d2f827d7d2a49ecf1639ec02d75ffd1b88dcc5b3a61bbb37a8759ad8d", + "sha256:520f2a520dc040512699f20fa1c363eed506e94248d71f85412b625026f6142c", + "sha256:5c6e3df6bdd396749bafd45314871b3d0af81ff935b2d188385e970052091017", + "sha256:608bfdee0d57cf297d32bcbb3c728dc1da0907519d1784962c5f0c68bb93e5a3", + "sha256:685ac03cc4ed5ebc15ad5c23bc555d68a87777586d970c2c3e216619a5476223", + "sha256:76de421f9c326da8f43d690110f0e79fe3ad1e54be811545d7d91898b4c8493e", + "sha256:76edb0a1fa2b4745fb0c99fb9fb98f8b180a1bbceb8be49b087e0b21867e77d3", + "sha256:7be600823e4c8631b74e4a0d38384c73f680e6105a7d3c6824fcf226c178c7e6", + "sha256:81ff539a12457809666fef6624684c008e00ff6bf455b4b89fd00a140eecd640", + "sha256:88af2003543cc40c80f6fca01411892ec52b11021b3dc22ec3bc9d5afd1c5334", + "sha256:8c11160913e3dd06c8ffdb5f233a4f254cb449f4dfc0f8f4549eda9e542c93d1", + "sha256:8f8182b523b2289f7c415f589118228d30ac8c355baa2f3194ced084dac2dbba", + "sha256:9211e7ad69d7c9401cfc0e23d49b69ca65ddd898976d660a2fa5904e3d7a9baa", + "sha256:92be919bbc9f7d09f7ae343c38f5bb21c973d2576c1d45600fce4b74bafa7ac0", + "sha256:9c82b5b3e043c7af0d95792d0d20ccf68f61a1fec6b3530e718b688422727396", + "sha256:9f7c16705f44e0504a3a2a14197c1f0b32a95731d251777dcb060aa83022cb2d", + "sha256:9fb218c8a12e51d7ead2a7c9e101a04982237d4855716af2e9499306728fb485", + "sha256:a74ba0c356aaa3bb8e3eb79606a87669e7ec6444be352870623025d75a14a2bf", + "sha256:b4f69b3700201b80bb82c3a97d5e9254084f6dd5fb5b16fc1a7b974260f89f43", + "sha256:bc2ec7c7b5d66b8ec9ce9f720dbb5fa4bace0f545acd34870eff4a369b44bf37", + "sha256:c189af0545965fa8d3b9613cfdb0cd37f9d71349e0f7750e1fd704648d475ed2", + "sha256:c1fbe7621c167ecaa38ad29643d77a9ce7311583761abf7836e1510c580bf3dd", + "sha256:c7cf14a27b0d6adfaebb3ae4153f1e516df54e47e42dcc073d7b3d76111a8d86", + "sha256:c9f72a021fbb792ce98306ffb0c348b3c9cb967dce0f12a49aa4c3d3fdefa967", + "sha256:cd25d2a9d2b36fcb318882481367956d2cf91329f6892fe5d385c346c0649629", + "sha256:ce543ed15570eedbb85df19b0a1a7314a9c8141a36ce089c0a894adbfccb4568", + "sha256:ce7b031a6fc11365970e6a5686d7ba8c63e4c1cf1ea143811acbb524295eabed", + "sha256:d35e3c8d9b1268cbf5d3670285feb3528f6680420eafe35cccc686b73c1e330f", + "sha256:d50b6aec14bc737742ca96e85d6d0a5f9bfbded018264b3b70ff9d8c33485551", + "sha256:d5d0dae4cfd56969d23d94dc8e89fb6a217be461c69090768227beb8ed28c0a3", + "sha256:d5db32e2a6ccbb3d34d87c87b432959e0db29755727afb37290e10f6e8e62614", + "sha256:d72e2ecc68a942e8cf9739619b7f408cc7b272b279b56b2c83c6123fcfa5cdff", + "sha256:d737a602fbd82afd892ca746392401b634e278cb65d55c4b7a8f48e9ef8d008d", + "sha256:d80cf684b541685fccdd84c485b31ce73fc5c9b5d7523bf1394ce134a60c6883", + "sha256:db24668940f82321e746773a4bc617bfac06ec831e5c88b643f91f122a785684", + "sha256:dbc02381779d412145331789b40cc7b11fdf449e5d94f6bc0b080db0a56ea3f0", + "sha256:dffe31a7f47b603318c609f378ebcd57f1554a3a6a8effbc59c3c69f804296de", + "sha256:edf4392b77bdc81f36e92d3a07a5cd072f90253197f4a52a55a8cec48a12483b", + "sha256:efe8c0681042536e0d06c11f48cebe759707c9e9abf880ee213541c5b46c5bf3", + "sha256:f31f9fdbfecb042d046f9d91270a0ba28368a723302786c0009ee9b9f1f60199", + "sha256:f88a0b92277de8e3ca715a0d79d68dc82807457dae3ab8699c758f07c20b3c51", + "sha256:faaf07ea35355b01a35cb442dd950d8f1bb5b040a7787791a535de13db15ed90" ], "index": "pypi", - "version": "==9.5.0" + "version": "==10.0.0" }, "psycopg2-binary": { "hashes": [ @@ -565,11 +629,11 @@ }, "pyjwt": { "hashes": [ - "sha256:ba2b425b15ad5ef12f200dc67dd56af4e26de2331f965c5439994dad075876e1", - "sha256:bd6ca4a3c4285c1a2d4349e5a035fdf8fb94e04ccd0fcbe6ba289dae9cc3e074" + "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de", + "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320" ], - "markers": "python_version >= '3.7'", - "version": "==2.7.0" + "markers": "python_full_version >= '3.7.0'", + "version": "==2.8.0" }, "python-dateutil": { "hashes": [ @@ -599,7 +663,7 @@ "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" ], - "markers": "python_version >= '3.7'", + "markers": "python_full_version >= '3.7.0'", "version": "==2.31.0" }, "requests-oauthlib": { @@ -615,7 +679,7 @@ "sha256:3c0da2d074bf35d6870ef157158641178a4204a6e689e82546083e31e0311346", "sha256:640bb492711f4c0c0905e1f62b6aaeb771881935ad27884852411f8e9cacbca9" ], - "markers": "python_version >= '3.7'", + "markers": "python_full_version >= '3.7.0'", "version": "==0.6.1" }, "sentry-sdk": { @@ -623,19 +687,11 @@ "django" ], "hashes": [ - "sha256:0c9f858337ec3781cf4851972ef42bba8c9828aea116b0dbed8f38c5f9a1896c", - "sha256:760e4fb6d01c994110507133e08ecd4bdf4d75ee4be77f296a3579796cf73134" + "sha256:6bdb25bd9092478d3a817cb0d01fa99e296aea34d404eac3ca0037faa5c2aa0a", + "sha256:dcd88c68aa64dae715311b5ede6502fd684f70d00a7cd4858118f0ba3153a3ae" ], "index": "pypi", - "version": "==1.26.0" - }, - "setuptools": { - "hashes": [ - "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f", - "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235" - ], - "markers": "python_version >= '3.7'", - "version": "==68.0.0" + "version": "==1.28.1" }, "six": { "hashes": [ @@ -650,7 +706,7 @@ "sha256:0347ca4cd23ea9d15a665da9d22950552fb66b95600e6c2ebae38ca883b3a4ed", "sha256:4a5dae406f3874b4003708ff120c02cb1a4c8eeead56cd163646347309fcd0f8" ], - "markers": "python_version >= '3.7'", + "markers": "python_full_version >= '3.7.0'", "version": "==5.2.0" }, "social-auth-core": { diff --git a/README.md b/README.md index 7009913..84ccf78 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,30 @@ DJANGO_SUPERUSER_EMAIL=admin@example.com 3. Save the .env file. +# Testing + +For testing this app, we are using [Django Test Case](https://docs.djangoproject.com/en/4.2/topics/testing/overview/) and [Django REST Framework API Test Case](https://www.django-rest-framework.org/api-guide/testing/#api-test-cases) along with [coverage.py](https://coverage.readthedocs.io/en/7.2.7/index.html) for test coverage reporting. + +To run tests: +```python manage.py test``` + +To skip a test that isn't finished, add the following before the test class: +```@unittest.skip("Test file is not ready yet")``` + +To run coverage for test: +```coverage run manage.py test``` + +After you run tests you can get the report in command-line by running: +```coverage report``` + +For an interactive html report, run: +```coverage html``` + +Then in the `htmlcov` folder of the project, open the file `index.html` in a browser. Here you can see an indepth analysis of coverage and what lines need testing. Click available links to view specific file coverage data. + +Here is some helpful information on testing in Django and Django REST Framework: https://www.rootstrap.com/blog/testing-in-django-django-rest-basics-useful-tools-good-practices + + # API Reference API URL - https://team-production-system.onrender.com diff --git a/team_production_system/custom_permissions.py b/team_production_system/custom_permissions.py index ea46c34..de3c3d0 100644 --- a/team_production_system/custom_permissions.py +++ b/team_production_system/custom_permissions.py @@ -3,7 +3,9 @@ class IsMentorMentee(BasePermission): def has_object_permission(self, request, view, obj): - return request.user.pk == obj.mentor.pk or request.user.pk == obj.mentee.pk + is_mentor = request.user.pk == obj.mentor.pk + is_mentee = request.user.pk == obj.mentee.pk + return is_mentor or is_mentee class NotificationSettingsPermission(BasePermission): diff --git a/team_production_system/serializers.py b/team_production_system/serializers.py index 6277988..17eb194 100644 --- a/team_production_system/serializers.py +++ b/team_production_system/serializers.py @@ -31,7 +31,7 @@ class AvailabilitySerializer(serializers.ModelSerializer): class Meta: model = Availability fields = ('pk', 'mentor', 'start_time', 'end_time',) - read_only_fields = ('mentor',) + read_only_fields = ('mentor', 'pk',) def create(self, validated_data): mentor = Mentor.objects.select_related('user').get( @@ -56,6 +56,29 @@ def create(self, validated_data): raise serializers.ValidationError( "Input overlaps with existing availability.") + def validate(self, data): + """ + Check that the start_time is before the end_time. + """ + start_time = data['start_time'] + end_time = data['end_time'] + if start_time >= end_time: + raise serializers.ValidationError( + 'End time must be after start time.') + return data + + def validate_end_time(self, value): + """ + Check that the end_time is in the future. + """ + if value <= timezone.now(): + raise serializers.ValidationError( + 'End time must be in the future.' + ) + return value + + # TODO: Add validation for start times + # Serializer for the mentor profile class MentorProfileSerializer(serializers.ModelSerializer): @@ -142,9 +165,9 @@ class SessionSerializer(serializers.ModelSerializer): class Meta: model = Session fields = ('pk', 'mentor_first_name', 'mentor_last_name', - 'mentor_availability', 'mentee', 'mentee_first_name', - 'mentee_last_name', 'start_time', 'end_time', 'status', - 'session_length') + 'mentor_availability', 'mentee', 'mentee_first_name', + 'mentee_last_name', 'start_time', 'end_time', 'status', + 'session_length',) read_only_fields = ('mentor', 'mentor_first_name', 'mentor_last_name', 'mentee', 'mentee_first_name', 'mentee_last_name') diff --git a/team_production_system/tests/__init__.py b/team_production_system/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/team_production_system/tests/end_to_end_tests/__init__.py b/team_production_system/tests/end_to_end_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/team_production_system/tests/end_to_end_tests/test_availability_list_create.py b/team_production_system/tests/end_to_end_tests/test_availability_list_create.py new file mode 100644 index 0000000..c45a15c --- /dev/null +++ b/team_production_system/tests/end_to_end_tests/test_availability_list_create.py @@ -0,0 +1,167 @@ +from django.urls import reverse +from django.utils import timezone +from rest_framework import status +from rest_framework.test import APITestCase, APIClient +from ...models import Availability, Mentor, CustomUser +from ...serializers import AvailabilitySerializer + + +class AvailabilityListCreateTestCase(APITestCase): + def setUp(self): + # Create a Mentor object + self.user = CustomUser.objects.create_user( + username='mentor', + email='mentor@example.com', + password='password' + ) + self.mentor = Mentor.objects.create(user=self.user) + + # Create two Availability objects associated with the Mentor + self.availability1 = Availability.objects.create( + mentor=self.mentor, + start_time=timezone.now(), + end_time=timezone.now() + timezone.timedelta(hours=1) + ) + self.availability2 = Availability.objects.create( + mentor=self.mentor, + start_time=timezone.now() + timezone.timedelta(days=1), + end_time=timezone.now() + timezone.timedelta(days=1, hours=1) + ) + # Create a Client + self.client = APIClient() + + def test_get_availability_list_without_authentication(self): + """ + Test that a GET request to retrieve the list of Availabilities without + authentication returns a status code of 401 UNAUTHORIZED. + """ + # Send a GET request to retrieve the list of Availabilities + url = reverse('availability') + response = self.client.get(url, format='json') + + # Check that the response has a status code of 401 UNAUTHORIZED + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_get_availability_list(self): + """ + Test that a GET request to retrieve the list of Availabilities returns + a status code of 200 OK and the correct serialized data. + """ + + # Authenticate as the Mentor + self.client.force_authenticate(user=self.user) + + # Send a GET request to retrieve the list of Availabilities + url = reverse('availability') + response = self.client.get(url, format='json') + + # Check that the response has a status code of 200 OK + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Check that the response data matches the serialized Availability obj + availabilities = Availability.objects.filter( + mentor=self.mentor, + end_time__gte=timezone.now() + ).select_related('mentor__user') + serializer = AvailabilitySerializer(availabilities, many=True) + self.assertEqual(response.data, serializer.data) + self.assertEqual(len(response.data), 2) + + def test_create_availability(self): + """ + Test that a POST request to create a new Availability with valid data + returns a status code of 201 CREATED. + """ + + # Authenticate as the Mentor + self.client.force_authenticate(user=self.user) + + # Send a POST request to create a new Availability + data = { + 'mentor': self.mentor.pk, + 'start_time': timezone.now() + timezone.timedelta(days=2), + 'end_time': timezone.now() + timezone.timedelta(days=2, hours=1) + } + url = reverse('availability') + response = self.client.post(url, data, format='json') + + # Check that the response has a status code of 201 CREATED + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + # Check that the created Availability object has the correct attributes + availability = Availability.objects.last() + self.assertEqual(availability.start_time, data['start_time']) + self.assertEqual(availability.end_time, data['end_time']) + self.assertEqual(availability.mentor, self.mentor) + + def test_create_availability_with_duplicate_start_time(self): + """ + Test that a POST request to create a new Availability with a start time + that has already been used returns a status code of 400 BAD REQUEST + and an error message. + """ + # Authenticate as the Mentor + self.client.force_authenticate(user=self.user) + + '''Send a POST request to create a new Availability with + a start time that has already been used''' + data = { + 'mentor': self.mentor.pk, + 'start_time': self.availability1.start_time, + 'end_time': timezone.now() + timezone.timedelta(hours=2) + } + url = reverse('availability') + response = self.client.post(url, data, format='json') + + # Check that the response has a status code of 400 BAD REQUEST + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + # Check that the response data contains an error message + self.assertEqual(response.data[0], + 'Input overlaps with existing availability.') + + def test_create_availability_with_duplicate_start_and_end_times(self): + """ + Test that a POST request to create a new Availability with a start time + and end time that have already been used returns a status code of + 400 BAD REQUEST and an error message. + """ + # Authenticate as the Mentor + self.client.force_authenticate(user=self.user) + + # Send a POST request to create a new Availability with duplicate + # start and end times + data = { + 'mentor': self.mentor.pk, + 'start_time': self.availability1.start_time, + 'end_time': self.availability1.end_time + } + url = reverse('availability') + response = self.client.post(url, data, format='json') + + # Check that the response has a status code of 400 BAD REQUEST + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + # Check that the response data contains an error message + self.assertEqual(response.data[0], + 'Input overlaps with existing availability.') + + def test_create_availability_with_end_time_before_start_time(self): + """ + Test that a POST request to create a new Availability with a start time + after the end time returns a status code of 400 BAD REQUEST. + """ + # Authenticate as the Mentor + self.client.force_authenticate(user=self.user) + + availability_data = { + 'start_time': '2022-01-01T14:00:00Z', + 'end_time': '2022-01-01T12:00:00Z', + 'mentor': self.mentor.pk + } + response = self.client.post('/availability/', + availability_data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(str(response.data['end_time'][0]), + 'End time must be in the future.') + self.assertEqual(Availability.objects.count(), 2) diff --git a/team_production_system/tests.py b/team_production_system/tests/test_availability.py similarity index 94% rename from team_production_system/tests.py rename to team_production_system/tests/test_availability.py index bd08491..ffe486c 100644 --- a/team_production_system/tests.py +++ b/team_production_system/tests/test_availability.py @@ -1,9 +1,11 @@ from django.test import TestCase from django.utils import timezone -from datetime import datetime, timedelta -from .models import CustomUser, Mentor, Mentee, Availability +from datetime import timedelta +from ..models import CustomUser, Mentor, Availability +import unittest +@unittest.skip("Test file is not ready yet") class AvailabilityTestCase(TestCase): def setUp(self): # Set up a mentor and an availability for testing diff --git a/team_production_system/tests/unit_tests/__init__.py b/team_production_system/tests/unit_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/team_production_system/tests/unit_tests/models/__init__.py b/team_production_system/tests/unit_tests/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/team_production_system/tests/unit_tests/models/test_availability_model.py b/team_production_system/tests/unit_tests/models/test_availability_model.py new file mode 100644 index 0000000..8d08bbb --- /dev/null +++ b/team_production_system/tests/unit_tests/models/test_availability_model.py @@ -0,0 +1,39 @@ +from django.test import TestCase +from datetime import datetime, timedelta, timezone +from ....models import Availability, Mentor, CustomUser + + +class AvailabilityTestCase(TestCase): + def setUp(self): + # Create a User and Mentor object + self.user = CustomUser.objects.create_user( + username='mentor', + email='mentor@example.com', + password='password' + ) + self.mentor = Mentor.objects.create(user=self.user) + + def test_create_availability(self): + # Set start and end times + start_time = datetime.now(timezone.utc) + end_time = start_time + timedelta(hours=1) + + # Create an Availability object + availability = Availability( + mentor=self.mentor, + start_time=start_time, + end_time=end_time) + + # Save an Availability object associated with the Mentor + availability.save() + + # Retrieve the saved Availability object + saved_availability = Availability.objects.first() + + # Check that the object was saved correctly + self.assertEqual(saved_availability.start_time, start_time) + self.assertEqual(saved_availability.end_time, end_time) + self.assertEqual(saved_availability.mentor, self.mentor) + self.assertEqual(str(saved_availability), f"{self.mentor} is available from {start_time} to {end_time}.") + + # TODO: Add test for self reference diff --git a/team_production_system/tests/unit_tests/views/__init__.py b/team_production_system/tests/unit_tests/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/team_production_system/tests/unit_tests/views/test_availability_create_list_view.py b/team_production_system/tests/unit_tests/views/test_availability_create_list_view.py new file mode 100644 index 0000000..76f1c64 --- /dev/null +++ b/team_production_system/tests/unit_tests/views/test_availability_create_list_view.py @@ -0,0 +1,105 @@ +from django.urls import reverse +from django.utils import timezone +from rest_framework import status +from rest_framework.test import APIClient +from django.test import TestCase +from ....models import Mentor, Availability, CustomUser +from ....serializers import AvailabilitySerializer +from unittest.mock import patch + + +class AvailabilityListCreateViewTestCase(TestCase): + def setUp(self): + # Create a Mentor instance for the test + self.user = CustomUser.objects.create_user( + username='testuser', + email='testuser@fake.com', + password='testpass') + self.mentor = Mentor.objects.create(user=self.user) + + # Create an Availability instance for the test + self.availability = Availability.objects.create( + mentor=self.mentor, + start_time=timezone.now() + timezone.timedelta(hours=1), + end_time=timezone.now() + timezone.timedelta(hours=2) + ) + + @patch('team_production_system.views.timezone') + def test_get_availability_list(self, mock_timezone): + """ + Test that the get method returns a list of Availabilities + belonging to the logged in mentor and with an end time + in the future. + """ + + self.client = APIClient() + self.client.force_authenticate(user=self.user) + + # Set up the mock timezone to return a fixed datetime + mock_timezone.now.return_value = timezone.datetime(2022, 1, 1, + tzinfo=timezone.utc) + + # Make a GET request to the AvailabilityList view + response = self.client.get('/availability/', format='json') + + # Check that the response status code is 200 OK + self.assertEqual(response.status_code, 200) + + # Check that the response data contains the expected Availability + self.assertEqual(len(response.data), 1) + self.assertEqual(response.data[0]['start_time'], + self.availability.start_time.isoformat().replace( + '+00:00', 'Z')) + + def test_create_availability(self): + """ + Test that a POST request to create a new Availability returns a status + code of 201 CREATED and the new Availability. + """ + # Authenticate as the Mentor + client = APIClient() + client.force_authenticate(user=self.user) + + # Send a POST request to create a new Availability + data = { + 'mentor': self.mentor.pk, + 'start_time': timezone.now() + timezone.timedelta(hours=3), + 'end_time': timezone.now() + timezone.timedelta(hours=4) + } + + url = reverse('availability') + response = client.post(url, data, format='json') + + # Check that the response has a status code of 201 CREATED + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + # Check that the response data is the new Availability + availability = Availability.objects.last() + serializer = AvailabilitySerializer(availability) + self.assertEqual(response.data['start_time'], + serializer.data['start_time']) + + def test_create_availability_with_invalid_data(self): + """ + Test that a POST request to create a new Availability with invalid data + returns a status code of 400 BAD REQUEST and an error message. + """ + # Authenticate as the Mentor + client = APIClient() + client.force_authenticate(user=self.user) + + # Send a POST request to create a new Availability with invalid data + data = { + 'mentor': self.mentor.pk, + 'start_time': timezone.now() + timezone.timedelta(hours=2), + 'end_time': timezone.now() + timezone.timedelta(hours=1) + } + url = reverse('availability') + response = client.post(url, data, format='json') + + # Check that the response has a status code of 400 BAD REQUEST + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + # Check that the response data contains an error message + self.assertEqual(response.data['non_field_errors'][0], + 'End time must be after start time.') diff --git a/team_production_system/urls.py b/team_production_system/urls.py index fe70e86..0f5e963 100644 --- a/team_production_system/urls.py +++ b/team_production_system/urls.py @@ -22,7 +22,7 @@ name='mentee-info-update'), # End points related to sessions - path('availability/', views.AvailabilityView.as_view(), name='availability'), + path('availability/', views.AvailabilityListCreateView.as_view(), name='availability'), # Delete availability path('availability//', views.AvailabilityDeleteView.as_view(), name='availability-delete'), diff --git a/team_production_system/views.py b/team_production_system/views.py index 1f935e9..55d0eac 100644 --- a/team_production_system/views.py +++ b/team_production_system/views.py @@ -1,21 +1,31 @@ -from .models import CustomUser, Mentee, Availability -from .models import Session, Mentor, NotificationSettings from rest_framework import generics, status -from .serializers import CustomUserSerializer, AvailabilitySerializer -from .serializers import SessionSerializer, NotificationSettingsSerializer -from .serializers import MentorListSerializer, MentorProfileSerializer -from .serializers import MenteeListSerializer, MenteeProfileSerializer from rest_framework.response import Response -from django.utils import timezone +from rest_framework.parsers import MultiPartParser from rest_framework.permissions import IsAuthenticated +from django.utils import timezone from django.db.models import Q -from .custom_permissions import IsMentorMentee, NotificationSettingsPermission, IsOwnerOrAdmin -from datetime import datetime, timedelta -from rest_framework.parsers import MultiPartParser from django.core.exceptions import ValidationError from django.conf import settings +from datetime import datetime, timedelta import boto3 from django.http import Http404 +from .serializers import (AvailabilitySerializer, + CustomUserSerializer, + MenteeListSerializer, + MenteeProfileSerializer, + MentorListSerializer, + MentorProfileSerializer, + NotificationSettingsSerializer, + SessionSerializer) +from .custom_permissions import (IsMentorMentee, + IsOwnerOrAdmin, + NotificationSettingsPermission) +from .models import (Availability, + CustomUser, + Mentee, + Mentor, + NotificationSettings, + Session) # View to update the user profile information @@ -29,17 +39,15 @@ def get_object(self): if not user.is_authenticated: return Response({'error': 'User is not authenticated.'}, status=status.HTTP_401_UNAUTHORIZED) - try: return user except CustomUser.DoesNotExist: return Response({'error': 'User not found.'}, status=status.HTTP_404_NOT_FOUND) - except Exception as e: - return Response({'error': 'An unexpected error occured.'}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - return user + except Exception: + return Response({ + 'error': 'An unexpected error occurred.' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) def patch(self, request, *args, **kwargs): user = self.request.user @@ -55,7 +63,8 @@ def patch(self, request, *args, **kwargs): if user.profile_photo: s3 = boto3.client('s3') s3.delete_object( - Bucket=settings.AWS_STORAGE_BUCKET_NAME, Key=user.profile_photo.name) + Bucket=settings.AWS_STORAGE_BUCKET_NAME, + Key=user.profile_photo.name) user.profile_photo = request.FILES['profile_photo'] @@ -68,7 +77,11 @@ def patch(self, request, *args, **kwargs): class MentorList(generics.ListAPIView): permission_classes = [IsAuthenticated] queryset = CustomUser.objects.filter( - is_mentor=True).select_related("mentor").prefetch_related("mentor__mentor_availability") + is_mentor=True + ).select_related( + "mentor" + ).prefetch_related( + "mentor__mentor_availability") serializer_class = MentorListSerializer def list(self, request, *args, **kwargs): @@ -131,11 +144,11 @@ def get(self, request, *args, **kwargs): try: queryset = CustomUser.objects.filter( is_mentee=True).select_related("mentee") - except Exception as e: + except Exception: return Response({"error": "Failed to retrieve mentee list."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - if not queryset: + if not queryset.exists(): return Response({"message": "No mentees found."}, status=status.HTTP_404_NOT_FOUND) @@ -169,7 +182,7 @@ def get_object(self): # Create and view all availabilities -class AvailabilityView(generics.ListCreateAPIView): +class AvailabilityListCreateView(generics.ListCreateAPIView): serializer_class = AvailabilitySerializer permission_classes = [IsAuthenticated] @@ -179,7 +192,8 @@ def get_queryset(self): # Exclude any availability that has an end time in the past # and filter availabilities belonging to the logged in user's mentor return Availability.objects.filter(mentor=mentor, - end_time__gte=timezone.now()).select_related('mentor__user') + end_time__gte=timezone.now() + ).select_related('mentor__user') # Delete an availability @@ -246,15 +260,18 @@ def perform_create(self, serializer): session_length=60, status__in=['Pending', 'Confirmed']) ).exists(): raise ValidationError( - 'A session with this mentor is already scheduled during this time.') + 'A session with this mentor is \ + already scheduled during this time.') elif Session.objects.filter( - Q(mentee=mentee, start_time=start_time, status__in=['Pending', 'Confirmed']) | + Q(mentee=mentee, start_time=start_time, + status__in=['Pending', 'Confirmed']) | Q(mentee=mentee, start_time=new_start_time, session_length=60, status__in=['Pending', 'Confirmed']) ).exists(): raise ValidationError( - 'A session with this mentee is already scheduled during this time.') + 'A session with this mentee is \ + already scheduled during this time.') # Set the mentor for the session else: @@ -272,13 +289,16 @@ def perform_create(self, serializer): after_start_time = time_convert(start_time, -30) if Session.objects.filter( - Q(mentor=mentor, start_time=start_time, status__in=['Pending', 'Confirmed']) | - Q(mentor=mentor, start_time=before_start_time, session_length=60, status__in=['Pending', 'Confirmed']) | + Q(mentor=mentor, start_time=start_time, + status__in=['Pending', 'Confirmed']) | + Q(mentor=mentor, start_time=before_start_time, + session_length=60, status__in=['Pending', 'Confirmed']) | Q(mentor=mentor, start_time=after_start_time, status__in=['Pending', 'Confirmed']) ).exists(): raise ValidationError( - 'A session with this mentor is already scheduled during this time.') + 'A session with this mentor is \ + already scheduled during this time.') elif Session.objects.filter( Q(mentee=mentee, start_time=start_time, @@ -289,7 +309,8 @@ def perform_create(self, serializer): status__in=['Pending', 'Confirmed']) ).exists(): raise ValidationError( - 'A session with this mentee is already scheduled during this time.') + 'A session with this mentee is already \ + scheduled during this time.') # Set the mentor for the session else: @@ -318,12 +339,15 @@ def perform_update(self, serializer): session.status = status session.save() - # If mentee cancels session, check mentor notification settings before notifying - if self.request.user.is_mentee and session.mentor.user.notification_settings.session_canceled: + # If mentee cancels session, check mentor notification + if self.request.user.is_mentee and \ + session.mentor.user.notification_settings.session_canceled: session.mentor_cancel_notify() - # If mentor cancels session, check mentee notification settings before notifying - elif self.request.user.is_mentor and session.mentee.user.notification_settings.session_canceled: + # If mentor cancels session, check mentee notification + + elif self.request.user.is_mentor and \ + session.mentee.user.notification_settings.session_canceled: session.mentee_cancel_notify() # Notify mentee when a mentor confirms session request @@ -355,7 +379,8 @@ def get_queryset(self): # order by sessions that are coming up next first. return Session.objects.filter(Q(mentor__user=self.request.user) | Q(mentee__user=self.request.user), - start_time__gt=timezone.now()).order_by('start_time') + start_time__gt=timezone.now() + ).order_by('start_time') # View to show mentor timeslots a mentee can sign up for @@ -367,7 +392,8 @@ class SessionSignupListView(generics.ListAPIView): def get_queryset(self): # Filter out completed sessions return Session.objects.exclude(status='Completed', - start_time__lt=timezone.now() - timedelta(hours=24)) + start_time__lt=timezone.now() - + timedelta(hours=24)) class ArchiveSessionView(generics.ListAPIView):