diff --git a/Pipfile b/Pipfile index adee21f..d3ee93b 100644 --- a/Pipfile +++ b/Pipfile @@ -35,6 +35,7 @@ gunicorn = "*" django-silk = "*" django-redis = "*" granian = "*" +black = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 3d0bf8b..518e401 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "c21d9d25e738eb8b3e90e5b0fabc46dbb99c2051f9aca5f8066f8859448b9c64" + "sha256": "0f3f9b982f191a112004df0d0b43a4739c1aa0cb961ea1adb3d3f68aa81cdc5a" }, "pipfile-spec": 6, "requires": { @@ -18,11 +18,11 @@ "default": { "2captcha-python": { "hashes": [ - "sha256:026a2e756eb5d507606a053b61d0837967ee5af02111c76980814cc65ea86762", - "sha256:3a8efdfd2ec6b256a2e05628cc88ef7a9292f9ac66ea7bf6b76ecb74bb3d4db2" + "sha256:139cafc7ef60c0f3cea5b95c6c4211e50aedd602ea489cc21b32b49425a62f73", + "sha256:afafed5d4045f9d343a65be4b424c510622025891a07e87a0ee2432a17078296" ], "index": "pypi", - "version": "==1.2.5" + "version": "==1.5.0" }, "amqp": { "hashes": [ @@ -50,27 +50,55 @@ }, "attrs": { "hashes": [ - "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", - "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" + "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", + "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" ], "markers": "python_version >= '3.7'", - "version": "==23.2.0" + "version": "==24.2.0" }, "autopep8": { "hashes": [ - "sha256:05418a981f038969d8bdcd5636bf15948db7555ae944b9f79b5a34b35f1370d4", - "sha256:d306a0581163ac29908280ad557773a95a9bede072c0fafed6f141f5311f43c1" + "sha256:8d6c87eba648fdcfc83e29b788910b8643171c395d9c4bcf115ece035b9c9dda", + "sha256:a203fe0fcad7939987422140ab17a930f684763bf7335bdb6709991dd7ef6c2d" ], "markers": "python_version >= '3.8'", - "version": "==2.2.0" + "version": "==2.3.1" }, "billiard": { "hashes": [ - "sha256:07aa978b308f334ff8282bd4a746e681b3513db5c9a514cbdd810cbbdc19714d", - "sha256:9a3c3184cb275aa17a732f93f65b20c525d3d9f253722d26a82194803ade5a2c" + "sha256:12b641b0c539073fc8d3f5b8b7be998956665c4233c7c1fcd66a7e677c4fb36f", + "sha256:40b59a4ac8806ba2c2369ea98d876bc6108b051c227baffd928c644d15d8f3cb" ], "markers": "python_version >= '3.7'", - "version": "==4.2.0" + "version": "==4.2.1" + }, + "black": { + "hashes": [ + "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f", + "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd", + "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea", + "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981", + "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b", + "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7", + "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8", + "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175", + "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d", + "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392", + "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad", + "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f", + "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f", + "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b", + "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875", + "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3", + "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800", + "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65", + "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2", + "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812", + "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50", + "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e" + ], + "index": "pypi", + "version": "==24.10.0" }, "celery": { "hashes": [ @@ -82,165 +110,195 @@ }, "certifi": { "hashes": [ - "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516", - "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56" + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" ], "markers": "python_version >= '3.6'", - "version": "==2024.6.2" + "version": "==2024.8.30" }, "cffi": { "hashes": [ - "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", - "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", - "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", - "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", - "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", - "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", - "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", - "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", - "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", - "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", - "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", - "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", - "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", - "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", - "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", - "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", - "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", - "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", - "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", - "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", - "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", - "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", - "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", - "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", - "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", - "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", - "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", - "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", - "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", - "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", - "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", - "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", - "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", - "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", - "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", - "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", - "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", - "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", - "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", - "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", - "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", - "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", - "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", - "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", - "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", - "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", - "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", - "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", - "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", - "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", - "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", - "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" + "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", + "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", + "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1", + "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", + "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", + "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", + "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", + "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", + "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", + "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", + "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc", + "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", + "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", + "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", + "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", + "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", + "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", + "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", + "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", + "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b", + "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", + "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", + "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c", + "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", + "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", + "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", + "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8", + "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1", + "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", + "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", + "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", + "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", + "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", + "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", + "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", + "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", + "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", + "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", + "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", + "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", + "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", + "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", + "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", + "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964", + "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", + "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", + "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", + "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", + "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", + "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", + "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", + "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", + "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", + "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", + "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", + "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", + "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", + "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9", + "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", + "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", + "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", + "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", + "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", + "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", + "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", + "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", + "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b" ], "markers": "os_name == 'nt' and implementation_name != 'pypy'", - "version": "==1.16.0" + "version": "==1.17.1" }, "charset-normalizer": { "hashes": [ - "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", - "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", - "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", - "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", - "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", - "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", - "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", - "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", - "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", - "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", - "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", - "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", - "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", - "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", - "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", - "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", - "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", - "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", - "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", - "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", - "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", - "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", - "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", - "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", - "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", - "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", - "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", - "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", - "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", - "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", - "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", - "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", - "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", - "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", - "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", - "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", - "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", - "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", - "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", - "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", - "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", - "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", - "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", - "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", - "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", - "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", - "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", - "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", - "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", - "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", - "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", - "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", - "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", - "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", - "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", - "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", - "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", - "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", - "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", - "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", - "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", - "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", - "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", - "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", - "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", - "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", - "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", - "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", - "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", - "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", - "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", - "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", - "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", - "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", - "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", - "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", - "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", - "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", - "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", - "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", - "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", - "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", - "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", - "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", - "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", - "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", - "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", - "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", - "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", - "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621", + "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", + "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", + "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912", + "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", + "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b", + "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d", + "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d", + "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95", + "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e", + "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", + "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", + "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab", + "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be", + "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", + "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907", + "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0", + "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2", + "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62", + "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62", + "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", + "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", + "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", + "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca", + "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455", + "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858", + "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", + "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", + "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", + "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", + "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", + "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea", + "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6", + "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", + "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749", + "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", + "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd", + "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99", + "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242", + "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee", + "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", + "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", + "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51", + "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", + "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8", + "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", + "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613", + "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742", + "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe", + "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3", + "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", + "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", + "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7", + "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", + "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", + "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", + "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417", + "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", + "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", + "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca", + "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa", + "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", + "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149", + "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41", + "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574", + "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0", + "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f", + "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", + "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654", + "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3", + "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19", + "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", + "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578", + "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", + "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", + "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51", + "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", + "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", + "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a", + "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", + "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade", + "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", + "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc", + "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6", + "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", + "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", + "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6", + "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2", + "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12", + "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf", + "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", + "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7", + "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", + "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", + "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b", + "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", + "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", + "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4", + "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", + "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", + "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a", + "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748", + "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b", + "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", + "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.2" + "version": "==3.4.0" }, "click": { "hashes": [ @@ -283,48 +341,43 @@ }, "cron-descriptor": { "hashes": [ - "sha256:7b1a00d7d25d6ae6896c0da4457e790b98cba778398a3d48e341e5e0d33f0488", - "sha256:a67ba21804983b1427ed7f3e1ec27ee77bf24c652b0430239c268c5ddfbf9dc0" + "sha256:736b3ae9d1a99bc3dbfc5b55b5e6e7c12031e7ba5de716625772f8b02dcd6013", + "sha256:f51ce4ffc1d1f2816939add8524f206c376a42c87a5fca3091ce26725b3b1bca" ], - "version": "==1.4.3" + "version": "==1.4.5" }, "cryptography": { "hashes": [ - "sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55", - "sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785", - "sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b", - "sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886", - "sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82", - "sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1", - "sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda", - "sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f", - "sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68", - "sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60", - "sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7", - "sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd", - "sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582", - "sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc", - "sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858", - "sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b", - "sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2", - "sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678", - "sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13", - "sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4", - "sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8", - "sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604", - "sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477", - "sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e", - "sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a", - "sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9", - "sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14", - "sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda", - "sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da", - "sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562", - "sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2", - "sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9" + "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", + "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", + "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa", + "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83", + "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff", + "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", + "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", + "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664", + "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08", + "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", + "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", + "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", + "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", + "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", + "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984", + "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", + "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3", + "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e", + "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", + "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2", + "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c", + "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", + "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", + "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", + "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7", + "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd", + "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7" ], "markers": "python_version >= '3.7'", - "version": "==42.0.7" + "version": "==43.0.3" }, "defusedxml": { "hashes": [ @@ -336,18 +389,19 @@ }, "django": { "hashes": [ - "sha256:8363ac062bb4ef7c3f12d078f6fa5d154031d129a15170a1066412af49d30905", - "sha256:ff1b61005004e476e0aeea47c7f79b85864c70124030e95146315396f1e7951f" + "sha256:bd7376f90c99f96b643722eee676498706c9fd7dc759f55ebfaf2c08ebcdf4f0", + "sha256:f11aa87ad8d5617171e3f77e1d5d16f004b79a2cf5d2e1d2b97a6a1f8e9ba5ed" ], "index": "pypi", - "version": "==5.0.6" + "version": "==5.1.2" }, "django-celery-beat": { "hashes": [ - "sha256:f75b2d129731f1214be8383e18fae6bfeacdb55dffb2116ce849222c0106f9ad" + "sha256:8482034925e09b698c05ad61c36ed2a8dbc436724a3fe119215193a4ca6dc967", + "sha256:851c680d8fbf608ca5fecd5836622beea89fa017bc2b3f94a5b8c648c32d84b1" ], "index": "pypi", - "version": "==2.6.0" + "version": "==2.7.0" }, "django-celery-results": { "hashes": [ @@ -359,11 +413,11 @@ }, "django-cors-headers": { "hashes": [ - "sha256:0b1fd19297e37417fc9f835d39e45c8c642938ddba1acce0c1753d3edef04f36", - "sha256:0bf65ef45e606aff1994d35503e6b677c0b26cafff6506f8fd7187f3be840207" + "sha256:14d76b4b4c8d39375baeddd89e4f08899051eeaf177cb02a29bd6eae8cf63aa8", + "sha256:8edbc0497e611c24d5150e0055d3b178c6534b8ed826fb6f53b21c63f5d48ba3" ], "index": "pypi", - "version": "==4.3.1" + "version": "==4.6.0" }, "django-extensions": { "hashes": [ @@ -390,19 +444,19 @@ }, "django-resized": { "hashes": [ - "sha256:52d727860f64ef4fdadbe2e74b66231c71c59df4d95949e338fcd320450f77fa", - "sha256:d55a8d4125838486a1e76ffb689f8364f7d579bc7562b04400065602ec2ba7cc" + "sha256:b5b0cf0d1db4f8db43362eef121d94d63487e2bff7a59c45046a1420788db353", + "sha256:c199f2b6e315816753df3a7a3889b923d3b73ce89fa16af93761cf2ea9bac4f7" ], "index": "pypi", - "version": "==1.0.2" + "version": "==1.0.3" }, "django-silk": { "hashes": [ - "sha256:34abb5852315f0f3303d45b7ab4a2caa9cf670102b614dbb2ac40a5d2d5cbffb", - "sha256:35a2051672b0be86af4ce734a0df0b6674c8c63f2df730b3756ec6e52923707d" + "sha256:39ddeda80469d5495d1cbb53590a9bdd4ce30a7474dc16ac26b6cbc0d9521f50", + "sha256:b3f01ccbf46611073603a6ac2b84f578bde978ad44785f42994c3d6f81398fdc" ], "index": "pypi", - "version": "==5.1.0" + "version": "==5.2.0" }, "django-simple-history": { "hashes": [ @@ -414,11 +468,11 @@ }, "django-storages": { "hashes": [ - "sha256:31f263389e95ce3a1b902fb5f739a7ed32895f7d8b80179fe7453ecc0dfe102e", - "sha256:95a12836cd998d4c7a4512347322331c662d9114c4344f932f5e9c0fce000608" + "sha256:69aca94d26e6714d14ad63f33d13619e697508ee33ede184e462ed766dc2a73f", + "sha256:d61930acb4a25e3aebebc6addaf946a3b1df31c803a6bf1af2f31c9047febaa3" ], "index": "pypi", - "version": "==1.14.3" + "version": "==1.14.4" }, "django-templated-mail": { "hashes": [ @@ -429,27 +483,27 @@ }, "django-timezone-field": { "hashes": [ - "sha256:0095f43da716552fcc606783cfb42cb025892514f1ec660ebfa96186eb83b74c", - "sha256:d40f7059d7bae4075725d04a9dae601af9fe3c7f0119a69b0e2c6194a782f797" + "sha256:3232e7ecde66ba4464abb6f9e6b8cc739b914efb9b29dc2cf2eee451f7cc2acb", + "sha256:aa6f4965838484317b7f08d22c0d91a53d64e7bbbd34264468ae83d4023898a7" ], "markers": "python_version >= '3.8' and python_version < '4.0'", - "version": "==6.1.0" + "version": "==7.0" }, "django-unfold": { "hashes": [ - "sha256:9394c4a2a59dce6935cecab3bac94ca102c368480ad6beebe9e0ef5034c6ea9c", - "sha256:be9eb71d97467dcfc9f8e883ee138875445af2a84577ad0a70671dcd524b1980" + "sha256:5038463cedb8e8110774f738bab0b50fbe51b269d7e9a2d4c0b6478a9f9d9d91", + "sha256:84d2255d63d7cf4917f6cbe8a8caca2eed5d9175576a994d3d3f9b47381e19a0" ], "index": "pypi", - "version": "==0.26.0" + "version": "==0.40.0" }, "djangorestframework": { "hashes": [ - "sha256:3ccc0475bce968608cf30d07fb17d8e52d1d7fc8bfe779c905463200750cbca6", - "sha256:f88fad74183dfc7144b2756d0d2ac716ea5b4c7c9840995ac3bfd8ec034333c1" + "sha256:2b8871b062ba1aefc2de01f773875441a961fefbf79f5eed1e32b2f096944b20", + "sha256:36fe88cd2d6c6bec23dca9804bab2ba5517a8bb9d8f47ebc68981b56840107ad" ], "index": "pypi", - "version": "==3.15.1" + "version": "==3.15.2" }, "djangorestframework-simplejwt": { "hashes": [ @@ -461,11 +515,11 @@ }, "djoser": { "hashes": [ - "sha256:9deb831a1c8781ceff325699e1407b4e1be8b4588e87071621d88ba31c09349f", - "sha256:efb91ad61e4d5b8d664db029b5947df9d34078289ef2680a1ab665e047144b74" + "sha256:1325e5c1ee3233560e3737fc8fe2ab4b014c5adb98a01682f30ef250aaabafba", + "sha256:28545bfb15096dd26e0d74b49b4dd267c0a8d5f9d562225ecc60c83289799131" ], "index": "pypi", - "version": "==2.2.2" + "version": "==2.2.3" }, "drf-spectacular": { "extras": [ @@ -480,10 +534,10 @@ }, "drf-spectacular-sidecar": { "hashes": [ - "sha256:5ad678c788dcb36697a668884c6fdac2c511a4094cb010978bd01a6345197bbb", - "sha256:eed744c26d2caff815fd67d89eca685f645479f07fb86c124d8ee26a13b1d960" + "sha256:5dc8b38ad153e90b328152674c7959bf114bf86360a617a5a4516e135cb832bc", + "sha256:beb992d6ece806a2d422ad626983e2472c0a5550de9647a7ed6764716a5abdfe" ], - "version": "==2024.6.1" + "version": "==2024.7.1" }, "flower": { "hashes": [ @@ -495,82 +549,89 @@ }, "gprof2dot": { "hashes": [ - "sha256:45b4d298bd36608fccf9511c3fd88a773f7a1abc04d6cd39445b11ba43133ec5", - "sha256:f165b3851d3c52ee4915eb1bd6cca571e5759823c2cd0f71a79bda93c2dc85d6" + "sha256:45b14ad7ce64e299c8f526881007b9eb2c6b75505d5613e96e66ee4d5ab33696", + "sha256:fa1420c60025a9eb7734f65225b4da02a10fc6dd741b37fa129bc6b41951e5ab" ], - "markers": "python_version >= '2.7'", - "version": "==2022.7.29" + "markers": "python_version >= '3.8'", + "version": "==2024.6.6" }, "granian": { "hashes": [ - "sha256:08e7fea29fbcda7edbfdda896d8ca46f3a2e3cbf91064a13a000668a8a69fc69", - "sha256:0b39fd5fa49c3afe5af9ba41633fe66a5c6c0d442b628ed42cf5ddcc8b32b641", - "sha256:11b215d461994701c68e3982d74bff679dc641539083ada534620de71b0a77ab", - "sha256:11d704431bfc85109ed5756de99c9ae4a02d4e03ffb1451077c77dd911b42773", - "sha256:16a5a7a1503be6eccffbe0b29a477be24626f8534c7a73a46268406b6878567a", - "sha256:23ea348e4a470cce54eda8f6f49dc9885d684bfcc280bd6b31b09e1e3e02def2", - "sha256:27c843bf874b24bee309b098c064429f8d1d7218294ade8d05b6813c3f6e5682", - "sha256:2d9e9a59dea5305e34ba49a6fe4fa3f41370a5a89c0d954765219ebb6e7aa739", - "sha256:2fecb1046e0a7c4a39b521f30f648db663f3c11487c1985db944ced3a78390bf", - "sha256:383a28e2021330820dafafece0e267ac31d6909dacbf56ef9d9826136edbb090", - "sha256:3d3b7abe806aa7dbc9b5da913172af00ee1259578d5ed36ab8ca7fe759696017", - "sha256:48be64d9a8dd84b298275fe89d6e27cc350ebf16c32d4079325bb92f141a955a", - "sha256:4a9a80f4c05e642ea935e95f0566bc82fdf462b10855363a8b80b6c04384995a", - "sha256:4f46a7babcc82ec75c4654c011ec345f42665b81abb140f2875dbf576a890865", - "sha256:519b11a42899948f71bb565b5008618fcfa9c2a838760bc099567330eac55c92", - "sha256:551ad05d334b67d91987219f1e191b53384e5d7f0b614e4cc1358d6979763678", - "sha256:57087fda5623bb49da2f04c5f31fec67a19fd854a07ecd328faa96cdc392cb00", - "sha256:59da27d54de1c0aab0ca1704e4a749f8b90801c68f45ee3bbcbc91a948e6877c", - "sha256:605276e28a1859a7f6e0674a1a79e10c2af106be6573404e972d4001eb1c6b74", - "sha256:6528ef091306cce6e0a0c23417ad550abe7fa7fcc6f037e9eebfe75919b48b89", - "sha256:6adc246e863a24f755cdbb012da2f48d77103fd84e70384d2588dc6ff91d393f", - "sha256:748b9a788acb3f8e152071fd0cd55cf2ca000ee34e506b89f9d8df57e1277c3c", - "sha256:76ed76bee24bcc39f4fca77c105a6befb4f3e5fcc8141c2bfb0987a5e130c80f", - "sha256:7e1d11da88deb102d5f76f5b8feff4c649a92316ac89231374b5d3b67dced8b0", - "sha256:7e2e50715e92c561b04b21dda2bc94b4a6b5bf92aab8020af54901077c3a7004", - "sha256:7fe1ec1f500f6d0ee1ad647e2cdc5b61d5d0ecd9d8e60b56eed9d1d52a0f94b1", - "sha256:823ac7fcfd92e9ad61d3302091ff93e80b9c771ad5162445422272355351b550", - "sha256:8338fa82c7deec7289a13f839bcea81d41fc2b43a9f3244f3f673a4cfb990349", - "sha256:849c7d9c3a75f1a1a2576c8a76263321489b59bb35980463023cd0766eaa1db5", - "sha256:85a772f52ff2cc87a19810e1990603929b283c70fa68b4ded3368e50317116ad", - "sha256:9335f1ed1c31f4cfeadb9e56ab5ab2a6f35234feddb1995ba032bd4335af8622", - "sha256:98c66870a54a64847a9867ce305af0ce0ecb4c2b49fbd882ed47512d0e5701be", - "sha256:a087036ba3124d8d8721195becae7503a404f3e17683b2f331355f25062377d6", - "sha256:a8160006b2716ecc443c477ffb1f7bec04525de76ad1f54139dbbca4d67e9093", - "sha256:a8c409fb5fb371c47096ab37366add4e433da08afc9bed73a7d9cb949916ec04", - "sha256:aa910bbdf4e4a9bac31b505f5632b20c1444d3e71a83e588110a1a8e7e4ed1cd", - "sha256:aaac86cac5d280f90db9bb17d9e65cb1606a1376daac239bcc0f33fb8ae2c45a", - "sha256:abee8147722be267d00b15a0621911e56b9827dec93dc0135b1db21b0bd98e32", - "sha256:ac7f33c92293f848197bc38f51c8878f150eadf9d2ca213a8b6893ef786b01e1", - "sha256:b6711c46611207aa29895755519a030d232f1b00d04fc693d095918e42fb5e8c", - "sha256:b88bfda8c51ee6b2915b86c468d53368afa958c81c91fc20d5fe175a22b1f194", - "sha256:bb7fcc24d13465fc57c94db37e5492843b8401dd05cfa13c32ea6336b4a304a5", - "sha256:bc009cc9efe0cd6e97ac249feb1f69ce50a66b20c5aa79d4f1dd117f7d4edfb4", - "sha256:c0d640ae1550ab5093fb03aa7178d407ba9c6dd7d80daee04df107d45d6956c3", - "sha256:cb4e8e8976f4fffeb98bf163b593a11520fa8a7057315fa23d9af27f831287d0", - "sha256:cba1bc68b31ca3d17e29a18330c536eead57215ebb9b56c86906a72ab3b094d9", - "sha256:cbeba831ba804ba53fadaf0e385b989554c6fa72322bf40b11d6b102eb51b3cf", - "sha256:cf64ef80a3313f75d3feeeca3f3513b6473366e23374ebf706a05189f5a54a19", - "sha256:d10509fe69b5a062a6f5f63704701b29d00ced5881e0b939e695ae76b38269fb", - "sha256:d78865e1f9283e91c6538509452848b42d03b74685c131f8e4f24f90440c088c", - "sha256:d96c8581d53ecbe436a17c653d36cb08f99970b21a81c3a0381ed52f2b7b655e", - "sha256:dada2b8f8668817d6b7ffd74f50aa82ddab925108ffac84c72602021154d4c2b", - "sha256:e7a09e6b295bdeaa28e377698a9be12cf10b4ce038959a1f610890712b9edeef", - "sha256:eaa073c785706cebddb5fce0edd7bf2888d1d96854b75766daba7b0b2263f1d1", - "sha256:efc7930edb1957dcb4be20a38ca35e43b7a7fe2a32596411a7eec0d907b46083", - "sha256:fa41fa81b2e3bcf0f78104ecaf1dc5eb84b5bf4a197a72eef125897fdd20b5c6", - "sha256:fd6394bad8e9e9c4b8849fb6a056dc9b5d8b5e2f8d02cee41a066f574f063770" + "sha256:011154ce1f898037c3cad56a6756287e3f0eafa6f81e67052ece79fa9baae839", + "sha256:01b07913f3597cec96845fbb19348dfa33a11c1ebb9ff774e5033882c378493e", + "sha256:03b2e2e549ced9b88808e7dcf624ccc51ad018e992d0e4106335fd3c00397df8", + "sha256:05f5e9d6ec9f05cd34a761d2c2e9623bd45821d5abc4ed29562bfb74db74891a", + "sha256:072bfb32a0a2c620538aa7d0aa8510768ad6018969b50a4e6237ac78232ac654", + "sha256:103cd6f55a5fda69c9a65700b08a4ef6f2e3acc3e725bf5f7a74cdecd5270beb", + "sha256:159e823616a735a98124bee9c2d29300688b61f01af418ec9e4dc3af3c6d093f", + "sha256:1630526c7d1783f4074adae98a1977e3e888651c30e53423b1ff27d6dfaf1884", + "sha256:1ece398f68b6edebd90a053049c29e202eada76f8a3a948d8d3c73f64cc8d5c5", + "sha256:1f6e922f79cea0471a9c3e782105edce9043e95f7a86ed87534a24c396ff91a2", + "sha256:29055e1f9048f49c231c029c72af6d0221cb4f62f2cec6501f2bcda193d73059", + "sha256:2943e4b3be967a200586bed800f1af44cd31f7cf84cb0c17012ef17e7db07421", + "sha256:32a3c27f62eafb30ac8175e65de75f799cd31d40de757e9c786fcfb969f1a8ef", + "sha256:33396d610dd61bf7ce635aa8cefa0cc7ac26adc1f9d1d18703b40dca0dc761de", + "sha256:344b903673ef5da6457836d3464226b375fff9213fca469334c594c192d12182", + "sha256:37a94bcc93ce2358d0648125ea18049e58c292fb5a516213073e7be477f08672", + "sha256:4103ae9c92206eee33ad4ec0b8668295c022f78b06a00679bc0d818206b07a6a", + "sha256:41dc977453dffa351e4c265de5b8de2deb78bb77c536c4caf7190471b8e500b9", + "sha256:44325aa5fc9b7e2d4d1d21344c67388bebe6b471979d5371667c49d3aadf4bec", + "sha256:450b2acd42814b59b70cad3ffda56f7ad2ae5c75fe5757b393d6cfd1c17b71d0", + "sha256:479606359446e3713847cfa22926f491659552b95eff88b169cbcf30b2353e30", + "sha256:564c836119811b45ea7a8ed3cbd77900e51918a09bf795ae2c9477b7c79dd600", + "sha256:575369b9823edde6971dd94569d807b778abe0168c7a0f661f3ae40a841068ab", + "sha256:5d1ef9fa5fe8acaba0f0fc15c663600cfac9c7f4fffb46a9a427e75dc7400a86", + "sha256:5daa1ac67b97a36de8668af708b7d9d732ef8338224bf5a7333594fb78798710", + "sha256:6617bd837fea5e5a39835ba4190f2de72e129eee7bf25ca3ad71c8c65bd4a4d1", + "sha256:6bc0b584c75a54c039e5381b95391a92964920104a8ff8319edf68272e34e130", + "sha256:70f8b59882adfbbd45851655d12897c2d9735fb3f25a029d979eeeb1f49c3895", + "sha256:718949b6031135297d8724516fc67d08defbda175d68834417880d36968a0917", + "sha256:7347ad7a5ac779b6483ee5455ae180918462f38f7cc74aa51127bdbc6c5bf605", + "sha256:7f93cd82b41b31bb0d5582c50ed6088a94c3d055bb3bbff9987fe70cb1c27786", + "sha256:897395510fa38a410aceaea3ab88f0cf9b7ef984642af8a625df7716ec4ed5ff", + "sha256:91781dd34601c9906647dbffc1679cc06ccbb675a3fd396a46fadd959a208191", + "sha256:91f4fa15387b28001d770e74573e09530eee0ee95745c5aadb7380c146bae522", + "sha256:9b978ebe5c920d44de8707d00791b736d396a84d6d5c5cebb04c55b2d2982b8b", + "sha256:9bb7b79af4c977fedc5b530b40ce0d271dac0eb063fb835360ffe832416a4302", + "sha256:9c658cd6516c517575571460f56d7838658de803031a569d908d9bbcf441073b", + "sha256:9decea15810f55fd60fbf6a727e1510e9bd56c9088e039aa7be3716dd7b464bd", + "sha256:9df321ca53023bcf35163f465fa14660c7e7f621caa7a2d4b017baf6b545d4f4", + "sha256:a777f8265bbb55fb191303be1a4c8a92fb3fe5c6b67d8ac0b9d171c801fba2d0", + "sha256:ade697aa216c736ca72f81bd9c375406f43501c99e683af5791fd315ecd2cb23", + "sha256:b43e2316a363c9acf23b2ffef2aa60672d3fa8540e421caf81cfe1d6fef6068b", + "sha256:b4accf60f48bdeb77e3eacdabddcdd28a46f4e716b9eb4348ec3b95b42add22d", + "sha256:bb4fbb1b5690a59ab0ab52c629d85ea53d6b20fdf2941239451def9c581ae45b", + "sha256:bd1f2c73d23f133d0c38039719b7de70fc5d7a6cfe3d0656e4b79bbb693de53d", + "sha256:ca10098d5b01cbd9464d15850f82a38109009ac62ad5920c1fdbca33a9fe8034", + "sha256:cd5c049ae87be486858d2f3a5d2c4a2eb890a6fa811fa4b48ffce699cbe3800f", + "sha256:cdb49cd0a713f16f0f7bc879b4ea1d5a7fd72ff036da3a4e31f057fabb3662a3", + "sha256:cebd3a610f49f213c7de7b91bc080780212dd9e9164c09fac37bfa04cb75ffc0", + "sha256:d2351934805b3828cacb9601849e535bf95ed0d84f3e57d83fdf88531a28575f", + "sha256:d3fc0e694b7ac8529138f18bcc7f0d9adb2b7bb64875630265642d3fa46a605e", + "sha256:d9d29adfb9b8b2400a9879984388bbc99b90f83be8dfc479d7fbe1bb2478abd4", + "sha256:de96d8c87e5e5e7fd3f795eb676010379d9176fb5fbb2b3b11d332c6cbe8f33f", + "sha256:e158a9e074ad7509b985e1e94bdce8a9b6fda3a5b235193978ca761e41d25cab", + "sha256:e23a1112ccff34d7c7758e4ea0461b129fcc54833d4d52b629bb414d4aa487b4", + "sha256:e359e14f7cf826f25d5598fbb6223b6403e09be14cff822942d2c737fba84b29", + "sha256:e49fdbf39995d2272c8edf3dea466c89a1de2167bd77fd894975bbe776cd9741", + "sha256:e57b5beb23f4941ba478097cb9d8d41a554a0c1cb676f08dc07401ad3dc4cfce", + "sha256:e5c8ebac4af9457cb4de3955fce51f1f0615dff4d4954b8991b8a40271e78886", + "sha256:e7cf13942b8683c8f469924d2aedcbe22613eec9106c20c55208ed71b22a5818", + "sha256:e8be2824acae19438eed1d5b472ed46e65431e7d8a1e90167a02bfe4cd4a0855", + "sha256:eb2a36ff33f8dcdcdf4f64a6ee0ca1e9ce2b4b35b5bcf306af34bdd0bca6c2db", + "sha256:edf6a6cb8318b11bf1bb90c3c1ad5af426b283849b28749e80d26e3aba3f8ad8", + "sha256:f4ea547a9cb850eaa6cd448374051c657eaa503bb720d2b0939945193da1f548" ], "index": "pypi", - "version": "==1.4.1" + "version": "==1.6.3" }, "gunicorn": { "hashes": [ - "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9", - "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63" + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" ], "index": "pypi", - "version": "==22.0.0" + "version": "==23.0.0" }, "h11": { "hashes": [ @@ -582,19 +643,19 @@ }, "humanize": { "hashes": [ - "sha256:582a265c931c683a7e9b8ed9559089dea7edcf6cc95be39a3cbc2c5d5ac2bcfa", - "sha256:ce284a76d5b1377fd8836733b983bfb0b76f1aa1c090de2566fcf008d7f6ab16" + "sha256:b53caaec8532bcb2fff70c8826f904c35943f8cecaca29d272d9df38092736c0", + "sha256:e66f36020a2d5a974c504bd2555cf770621dbdbb6d82f94a6857c0b1ea2608be" ], - "markers": "python_version >= '3.8'", - "version": "==4.9.0" + "markers": "python_version >= '3.9'", + "version": "==4.11.0" }, "idna": { "hashes": [ - "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", - "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" ], - "markers": "python_version >= '3.5'", - "version": "==3.7" + "markers": "python_version >= '3.6'", + "version": "==3.10" }, "inflection": { "hashes": [ @@ -606,27 +667,35 @@ }, "jsonschema": { "hashes": [ - "sha256:5b22d434a45935119af990552c862e5d6d564e8f6601206b305a61fdf661a2b7", - "sha256:ff4cfd6b1367a40e7bc6411caec72effadd3db0bbe5017de188f2d6108335802" + "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", + "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566" ], "markers": "python_version >= '3.8'", - "version": "==4.22.0" + "version": "==4.23.0" }, "jsonschema-specifications": { "hashes": [ - "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc", - "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c" + "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", + "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf" ], - "markers": "python_version >= '3.8'", - "version": "==2023.12.1" + "markers": "python_version >= '3.9'", + "version": "==2024.10.1" }, "kombu": { "hashes": [ - "sha256:011c4cd9a355c14a1de8d35d257314a1d2456d52b7140388561acac3cf1a97bf", - "sha256:5634c511926309c7f9789f1433e9ed402616b56836ef9878f01bd59267b4c7a9" + "sha256:14212f5ccf022fc0a70453bb025a1dcc32782a588c49ea866884047d66e14763", + "sha256:eef572dd2fd9fc614b37580e3caeafdd5af46c1eff31e7fba89138cdb406f2cf" ], "index": "pypi", - "version": "==5.3.7" + "version": "==5.4.2" + }, + "mypy-extensions": { + "hashes": [ + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.0" }, "oauthlib": { "hashes": [ @@ -646,129 +715,147 @@ }, "packaging": { "hashes": [ - "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", - "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9" + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" ], - "markers": "python_version >= '3.7'", - "version": "==24.0" + "markers": "python_version >= '3.8'", + "version": "==24.1" + }, + "pathspec": { + "hashes": [ + "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", + "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" + ], + "markers": "python_version >= '3.8'", + "version": "==0.12.1" }, "pillow": { "hashes": [ - "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c", - "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2", - "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb", - "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d", - "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa", - "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3", - "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1", - "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a", - "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd", - "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8", - "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999", - "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599", - "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936", - "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375", - "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d", - "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b", - "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60", - "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572", - "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3", - "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced", - "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f", - "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b", - "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19", - "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f", - "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d", - "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383", - "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795", - "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355", - "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57", - "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09", - "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b", - "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462", - "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf", - "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f", - "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a", - "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad", - "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9", - "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d", - "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45", - "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994", - "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d", - "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338", - "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463", - "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451", - "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591", - "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c", - "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd", - "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32", - "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9", - "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf", - "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5", - "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828", - "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3", - "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5", - "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2", - "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b", - "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2", - "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475", - "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3", - "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb", - "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef", - "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015", - "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002", - "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170", - "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84", - "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57", - "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f", - "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27", - "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a" + "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" ], "index": "pypi", - "version": "==10.3.0" + "version": "==11.0.0" + }, + "platformdirs": { + "hashes": [ + "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", + "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb" + ], + "markers": "python_version >= '3.8'", + "version": "==4.3.6" }, "prometheus-client": { "hashes": [ - "sha256:287629d00b147a32dcb2be0b9df905da599b2d82f80377083ec8463309a4bb89", - "sha256:cde524a85bce83ca359cc837f28b8c0db5cac7aa653a588fd7e84ba061c329e7" + "sha256:4fa6b4dd0ac16d58bb587c04b1caae65b8c5043e85f778f42f5f632f6af2e166", + "sha256:96c83c606b71ff2b0a433c98889d275f51ffec6c5e267de37c7a2b5c9aa9233e" ], "markers": "python_version >= '3.8'", - "version": "==0.20.0" + "version": "==0.21.0" }, "prompt-toolkit": { "hashes": [ - "sha256:07c60ee4ab7b7e90824b61afa840c8f5aad2d46b3e2e10acc33d8ecc94a49089", - "sha256:a29b89160e494e3ea8622b09fa5897610b437884dcdcd054fdc1308883326c2a" + "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90", + "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.0.45" + "version": "==3.0.48" }, "psycopg2": { "hashes": [ - "sha256:121081ea2e76729acfb0673ff33755e8703d45e926e416cb59bae3a86c6a4981", - "sha256:38a8dcc6856f569068b47de286b472b7c473ac7977243593a288ebce0dc89516", - "sha256:426f9f29bde126913a20a96ff8ce7d73fd8a216cfb323b1f04da402d452853c3", - "sha256:5e0d98cade4f0e0304d7d6f25bbfbc5bd186e07b38eac65379309c4ca3193efa", - "sha256:7e2dacf8b009a1c1e843b5213a87f7c544b2b042476ed7755be813eaf4e8347a", - "sha256:a7653d00b732afb6fc597e29c50ad28087dcb4fbfb28e86092277a559ae4e693", - "sha256:ade01303ccf7ae12c356a5e10911c9e1c51136003a9a1d92f7aa9d010fb98372", - "sha256:bac58c024c9922c23550af2a581998624d6e02350f4ae9c5f0bc642c633a2d5e", - "sha256:c92811b2d4c9b6ea0285942b2e7cac98a59e166d59c588fe5cfe1eda58e72d59", - "sha256:d1454bde93fb1e224166811694d600e746430c006fbb031ea06ecc2ea41bf156", - "sha256:d735786acc7dd25815e89cc4ad529a43af779db2e25aa7c626de864127e5a024", - "sha256:de80739447af31525feddeb8effd640782cf5998e1a4e9192ebdf829717e3913", - "sha256:ff432630e510709564c01dafdbe996cb552e0b9f3f065eb89bdce5bd31fabf4c" + "sha256:0435034157049f6846e95103bd8f5a668788dd913a7c30162ca9503fdf542cb4", + "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11", + "sha256:47c4f9875125344f4c2b870e41b6aad585901318068acd01de93f3677a6522c2", + "sha256:4a579d6243da40a7b3182e0430493dbd55950c493d8c68f4eec0b302f6bbf20e", + "sha256:5df2b672140f95adb453af93a7d669d7a7bf0a56bcd26f1502329166f4a61716", + "sha256:65a63d7ab0e067e2cdb3cf266de39663203d38d6a8ed97f5ca0cb315c73fe067", + "sha256:88138c8dedcbfa96408023ea2b0c369eda40fe5d75002c0964c78f46f11fa442", + "sha256:9d5b3b94b79a844a986d029eee38998232451119ad653aea42bb9220a8c5066b", + "sha256:c6f7b8561225f9e711a9c47087388a97fdc948211c10a4bccbf0ba68ab7b3b5a" ], "index": "pypi", - "version": "==2.9.9" + "version": "==2.9.10" }, "pycodestyle": { "hashes": [ - "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f", - "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67" + "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3", + "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521" ], "markers": "python_version >= '3.8'", - "version": "==2.11.1" + "version": "==2.12.1" }, "pycparser": { "hashes": [ @@ -780,18 +867,18 @@ }, "pygraphviz": { "hashes": [ - "sha256:6ad8aa2f26768830a5a1cfc8a14f022d13df170a8f6fdfd68fd1aa1267000964" + "sha256:c10df02377f4e39b00ae17c862f4ee7e5767317f1c6b2dfd04cea6acc7fc2bea" ], "index": "pypi", - "version": "==1.13" + "version": "==1.14" }, "pyjwt": { "hashes": [ - "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de", - "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320" + "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", + "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c" ], - "markers": "python_version >= '3.7'", - "version": "==2.8.0" + "markers": "python_version >= '3.8'", + "version": "==2.9.0" }, "pysocks": { "hashes": [ @@ -803,9 +890,10 @@ }, "python-crontab": { "hashes": [ - "sha256:f4ea1605d24533b67fa7a634ef26cb59a5f2e7954f6e677d2d7a2229959a2fc8" + "sha256:40067d1dd39ade3460b2ad8557c7651514cd3851deffff61c5c60e1227c5c36b", + "sha256:82cb9b6a312d41ff66fd3caf3eed7115c28c195bfb50711bc2b4b9592feb9fe5" ], - "version": "==3.1.0" + "version": "==3.2.0" }, "python-dateutil": { "hashes": [ @@ -840,75 +928,77 @@ }, "pytz": { "hashes": [ - "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812", - "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319" + "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", + "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725" ], - "version": "==2024.1" + "version": "==2024.2" }, "pyyaml": { "hashes": [ - "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", - "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", - "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", - "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", - "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", - "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", - "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", - "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", - "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", - "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", - "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", - "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", - "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", - "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", - "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", - "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", - "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", - "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", - "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", - "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", - "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", - "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", - "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", - "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", - "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", - "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", - "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", - "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", - "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", - "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", - "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", - "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", - "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", - "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", - "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", - "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", - "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", - "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", - "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", - "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", - "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", - "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", - "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", - "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", - "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", - "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", - "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", - "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", - "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", - "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", - "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" ], - "markers": "python_version >= '3.6'", - "version": "==6.0.1" + "markers": "python_version >= '3.8'", + "version": "==6.0.2" }, "redis": { "hashes": [ - "sha256:7adc2835c7a9b5033b7ad8f8918d09b7344188228809c98df07af226d39dec91", - "sha256:ec31f2ed9675cc54c21ba854cfe0462e6faf1d83c8ce5944709db8a4700b9c61" + "sha256:0b1087665a771b1ff2e003aa5bdd354f15a70c9e25d5a7dbf9c722c16528a7b0", + "sha256:ae174f2bb3b1bf2b09d54bf3e51fbc1469cf6c10aa03e21141f51969801a7897" ], "index": "pypi", - "version": "==5.0.4" + "version": "==5.2.0" }, "referencing": { "hashes": [ @@ -936,116 +1026,120 @@ }, "rpds-py": { "hashes": [ - "sha256:05f3d615099bd9b13ecf2fc9cf2d839ad3f20239c678f461c753e93755d629ee", - "sha256:06d218939e1bf2ca50e6b0ec700ffe755e5216a8230ab3e87c059ebb4ea06afc", - "sha256:07f2139741e5deb2c5154a7b9629bc5aa48c766b643c1a6750d16f865a82c5fc", - "sha256:08d74b184f9ab6289b87b19fe6a6d1a97fbfea84b8a3e745e87a5de3029bf944", - "sha256:0abeee75434e2ee2d142d650d1e54ac1f8b01e6e6abdde8ffd6eeac6e9c38e20", - "sha256:154bf5c93d79558b44e5b50cc354aa0459e518e83677791e6adb0b039b7aa6a7", - "sha256:17c6d2155e2423f7e79e3bb18151c686d40db42d8645e7977442170c360194d4", - "sha256:1805d5901779662d599d0e2e4159d8a82c0b05faa86ef9222bf974572286b2b6", - "sha256:19ba472b9606c36716062c023afa2484d1e4220548751bda14f725a7de17b4f6", - "sha256:19e515b78c3fc1039dd7da0a33c28c3154458f947f4dc198d3c72db2b6b5dc93", - "sha256:1d54f74f40b1f7aaa595a02ff42ef38ca654b1469bef7d52867da474243cc633", - "sha256:207c82978115baa1fd8d706d720b4a4d2b0913df1c78c85ba73fe6c5804505f0", - "sha256:2625f03b105328729f9450c8badda34d5243231eef6535f80064d57035738360", - "sha256:27bba383e8c5231cd559affe169ca0b96ec78d39909ffd817f28b166d7ddd4d8", - "sha256:2c3caec4ec5cd1d18e5dd6ae5194d24ed12785212a90b37f5f7f06b8bedd7139", - "sha256:2cc7c1a47f3a63282ab0f422d90ddac4aa3034e39fc66a559ab93041e6505da7", - "sha256:2fc24a329a717f9e2448f8cd1f960f9dac4e45b6224d60734edeb67499bab03a", - "sha256:312fe69b4fe1ffbe76520a7676b1e5ac06ddf7826d764cc10265c3b53f96dbe9", - "sha256:32b7daaa3e9389db3695964ce8e566e3413b0c43e3394c05e4b243a4cd7bef26", - "sha256:338dee44b0cef8b70fd2ef54b4e09bb1b97fc6c3a58fea5db6cc083fd9fc2724", - "sha256:352a88dc7892f1da66b6027af06a2e7e5d53fe05924cc2cfc56495b586a10b72", - "sha256:35b2b771b13eee8729a5049c976197ff58a27a3829c018a04341bcf1ae409b2b", - "sha256:38e14fb4e370885c4ecd734f093a2225ee52dc384b86fa55fe3f74638b2cfb09", - "sha256:3c20f05e8e3d4fc76875fc9cb8cf24b90a63f5a1b4c5b9273f0e8225e169b100", - "sha256:3dd3cd86e1db5aadd334e011eba4e29d37a104b403e8ca24dcd6703c68ca55b3", - "sha256:489bdfe1abd0406eba6b3bb4fdc87c7fa40f1031de073d0cfb744634cc8fa261", - "sha256:48c2faaa8adfacefcbfdb5f2e2e7bdad081e5ace8d182e5f4ade971f128e6bb3", - "sha256:4a98a1f0552b5f227a3d6422dbd61bc6f30db170939bd87ed14f3c339aa6c7c9", - "sha256:4adec039b8e2928983f885c53b7cc4cda8965b62b6596501a0308d2703f8af1b", - "sha256:4e0ee01ad8260184db21468a6e1c37afa0529acc12c3a697ee498d3c2c4dcaf3", - "sha256:51584acc5916212e1bf45edd17f3a6b05fe0cbb40482d25e619f824dccb679de", - "sha256:531796fb842b53f2695e94dc338929e9f9dbf473b64710c28af5a160b2a8927d", - "sha256:5463c47c08630007dc0fe99fb480ea4f34a89712410592380425a9b4e1611d8e", - "sha256:5c45a639e93a0c5d4b788b2613bd637468edd62f8f95ebc6fcc303d58ab3f0a8", - "sha256:6031b25fb1b06327b43d841f33842b383beba399884f8228a6bb3df3088485ff", - "sha256:607345bd5912aacc0c5a63d45a1f73fef29e697884f7e861094e443187c02be5", - "sha256:618916f5535784960f3ecf8111581f4ad31d347c3de66d02e728de460a46303c", - "sha256:636a15acc588f70fda1661234761f9ed9ad79ebed3f2125d44be0862708b666e", - "sha256:673fdbbf668dd958eff750e500495ef3f611e2ecc209464f661bc82e9838991e", - "sha256:6afd80f6c79893cfc0574956f78a0add8c76e3696f2d6a15bca2c66c415cf2d4", - "sha256:6b5ff7e1d63a8281654b5e2896d7f08799378e594f09cf3674e832ecaf396ce8", - "sha256:6c4c4c3f878df21faf5fac86eda32671c27889e13570645a9eea0a1abdd50922", - "sha256:6cd8098517c64a85e790657e7b1e509b9fe07487fd358e19431cb120f7d96338", - "sha256:6d1e42d2735d437e7e80bab4d78eb2e459af48c0a46e686ea35f690b93db792d", - "sha256:6e30ac5e329098903262dc5bdd7e2086e0256aa762cc8b744f9e7bf2a427d3f8", - "sha256:70a838f7754483bcdc830444952fd89645569e7452e3226de4a613a4c1793fb2", - "sha256:720edcb916df872d80f80a1cc5ea9058300b97721efda8651efcd938a9c70a72", - "sha256:732672fbc449bab754e0b15356c077cc31566df874964d4801ab14f71951ea80", - "sha256:740884bc62a5e2bbb31e584f5d23b32320fd75d79f916f15a788d527a5e83644", - "sha256:7700936ef9d006b7ef605dc53aa364da2de5a3aa65516a1f3ce73bf82ecfc7ae", - "sha256:7732770412bab81c5a9f6d20aeb60ae943a9b36dcd990d876a773526468e7163", - "sha256:7750569d9526199c5b97e5a9f8d96a13300950d910cf04a861d96f4273d5b104", - "sha256:7f1944ce16401aad1e3f7d312247b3d5de7981f634dc9dfe90da72b87d37887d", - "sha256:81c5196a790032e0fc2464c0b4ab95f8610f96f1f2fa3d4deacce6a79852da60", - "sha256:8352f48d511de5f973e4f2f9412736d7dea76c69faa6d36bcf885b50c758ab9a", - "sha256:8927638a4d4137a289e41d0fd631551e89fa346d6dbcfc31ad627557d03ceb6d", - "sha256:8c7672e9fba7425f79019db9945b16e308ed8bc89348c23d955c8c0540da0a07", - "sha256:8d2e182c9ee01135e11e9676e9a62dfad791a7a467738f06726872374a83db49", - "sha256:910e71711d1055b2768181efa0a17537b2622afeb0424116619817007f8a2b10", - "sha256:942695a206a58d2575033ff1e42b12b2aece98d6003c6bc739fbf33d1773b12f", - "sha256:9437ca26784120a279f3137ee080b0e717012c42921eb07861b412340f85bae2", - "sha256:967342e045564cef76dfcf1edb700b1e20838d83b1aa02ab313e6a497cf923b8", - "sha256:998125738de0158f088aef3cb264a34251908dd2e5d9966774fdab7402edfab7", - "sha256:9e6934d70dc50f9f8ea47081ceafdec09245fd9f6032669c3b45705dea096b88", - "sha256:a3d456ff2a6a4d2adcdf3c1c960a36f4fd2fec6e3b4902a42a384d17cf4e7a65", - "sha256:a7b28c5b066bca9a4eb4e2f2663012debe680f097979d880657f00e1c30875a0", - "sha256:a888e8bdb45916234b99da2d859566f1e8a1d2275a801bb8e4a9644e3c7e7909", - "sha256:aa3679e751408d75a0b4d8d26d6647b6d9326f5e35c00a7ccd82b78ef64f65f8", - "sha256:aaa71ee43a703c321906813bb252f69524f02aa05bf4eec85f0c41d5d62d0f4c", - "sha256:b646bf655b135ccf4522ed43d6902af37d3f5dbcf0da66c769a2b3938b9d8184", - "sha256:b906b5f58892813e5ba5c6056d6a5ad08f358ba49f046d910ad992196ea61397", - "sha256:b9bb1f182a97880f6078283b3505a707057c42bf55d8fca604f70dedfdc0772a", - "sha256:bd1105b50ede37461c1d51b9698c4f4be6e13e69a908ab7751e3807985fc0346", - "sha256:bf18932d0003c8c4d51a39f244231986ab23ee057d235a12b2684ea26a353590", - "sha256:c273e795e7a0f1fddd46e1e3cb8be15634c29ae8ff31c196debb620e1edb9333", - "sha256:c69882964516dc143083d3795cb508e806b09fc3800fd0d4cddc1df6c36e76bb", - "sha256:c827576e2fa017a081346dce87d532a5310241648eb3700af9a571a6e9fc7e74", - "sha256:cbfbea39ba64f5e53ae2915de36f130588bba71245b418060ec3330ebf85678e", - "sha256:ce0bb20e3a11bd04461324a6a798af34d503f8d6f1aa3d2aa8901ceaf039176d", - "sha256:d0cee71bc618cd93716f3c1bf56653740d2d13ddbd47673efa8bf41435a60daa", - "sha256:d21be4770ff4e08698e1e8e0bce06edb6ea0626e7c8f560bc08222880aca6a6f", - "sha256:d31dea506d718693b6b2cffc0648a8929bdc51c70a311b2770f09611caa10d53", - "sha256:d44607f98caa2961bab4fa3c4309724b185b464cdc3ba6f3d7340bac3ec97cc1", - "sha256:d58ad6317d188c43750cb76e9deacf6051d0f884d87dc6518e0280438648a9ac", - "sha256:d70129cef4a8d979caa37e7fe957202e7eee8ea02c5e16455bc9808a59c6b2f0", - "sha256:d85164315bd68c0806768dc6bb0429c6f95c354f87485ee3593c4f6b14def2bd", - "sha256:d960de62227635d2e61068f42a6cb6aae91a7fe00fca0e3aeed17667c8a34611", - "sha256:dc48b479d540770c811fbd1eb9ba2bb66951863e448efec2e2c102625328e92f", - "sha256:e1735502458621921cee039c47318cb90b51d532c2766593be6207eec53e5c4c", - "sha256:e2be6e9dd4111d5b31ba3b74d17da54a8319d8168890fbaea4b9e5c3de630ae5", - "sha256:e4c39ad2f512b4041343ea3c7894339e4ca7839ac38ca83d68a832fc8b3748ab", - "sha256:ed402d6153c5d519a0faf1bb69898e97fb31613b49da27a84a13935ea9164dfc", - "sha256:ee17cd26b97d537af8f33635ef38be873073d516fd425e80559f4585a7b90c43", - "sha256:f3027be483868c99b4985fda802a57a67fdf30c5d9a50338d9db646d590198da", - "sha256:f5bab211605d91db0e2995a17b5c6ee5edec1270e46223e513eaa20da20076ac", - "sha256:f6f8e3fecca256fefc91bb6765a693d96692459d7d4c644660a9fff32e517843", - "sha256:f7afbfee1157e0f9376c00bb232e80a60e59ed716e3211a80cb8506550671e6e", - "sha256:fa242ac1ff583e4ec7771141606aafc92b361cd90a05c30d93e343a0c2d82a89", - "sha256:fab6ce90574645a0d6c58890e9bcaac8d94dff54fb51c69e5522a7358b80ab64" + "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c", + "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585", + "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5", + "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6", + "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef", + "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2", + "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29", + "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318", + "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b", + "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399", + "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739", + "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee", + "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174", + "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a", + "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344", + "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2", + "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03", + "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5", + "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22", + "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e", + "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96", + "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91", + "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752", + "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075", + "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253", + "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee", + "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad", + "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5", + "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce", + "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7", + "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b", + "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8", + "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57", + "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3", + "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec", + "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209", + "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921", + "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045", + "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074", + "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580", + "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7", + "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5", + "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3", + "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0", + "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24", + "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139", + "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db", + "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc", + "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789", + "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f", + "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2", + "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c", + "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232", + "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6", + "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c", + "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29", + "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489", + "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94", + "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751", + "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2", + "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda", + "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9", + "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51", + "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c", + "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8", + "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989", + "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511", + "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1", + "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2", + "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150", + "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c", + "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965", + "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f", + "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58", + "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b", + "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f", + "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d", + "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821", + "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de", + "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121", + "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855", + "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272", + "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60", + "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02", + "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1", + "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140", + "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879", + "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940", + "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364", + "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4", + "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e", + "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420", + "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5", + "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24", + "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c", + "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf", + "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f", + "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e", + "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab", + "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08", + "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92", + "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a", + "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8" ], "markers": "python_version >= '3.8'", - "version": "==0.18.1" + "version": "==0.20.0" }, "selenium": { "hashes": [ - "sha256:4770ffe5a5264e609de7dc914be6b89987512040d5a8efb2abb181330d097993", - "sha256:650dbfa5159895ff00ad16e5ddb6ceecb86b90c7ed2012b3f041f64e6e4904fe" + "sha256:48013f36e812de5b3948ef53d04e73f77bc923ee3e1d7d99eaf0618179081b99", + "sha256:f0780f85f10310aa5d085b81e79d73d3c93b83d8de121d0400d543a50ee963e8" ], "index": "pypi", - "version": "==4.21.0" + "version": "==4.26.0" }, "six": { "hashes": [ @@ -1065,11 +1159,11 @@ }, "social-auth-app-django": { "hashes": [ - "sha256:2a43cde559dd34fdc7132417b6c52c780fa99ec2332dee9f405b4763f371c367", - "sha256:7519f186c63c50f2d364457b236f051338d194bcface55e318a6a705c5213477" + "sha256:0c041a31707921aef9a930f143183c65d8c7b364381364a50f3f7c6fcc9d62f6", + "sha256:c8832c6cf13da6ad76f5613bcda2647d89ae7cfbc5217fadd13477a3406feaa8" ], "markers": "python_version >= '3.8'", - "version": "==5.4.1" + "version": "==5.4.2" }, "social-auth-core": { "hashes": [ @@ -1088,44 +1182,44 @@ }, "sqlparse": { "hashes": [ - "sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93", - "sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663" + "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4", + "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e" ], "markers": "python_version >= '3.8'", - "version": "==0.5.0" + "version": "==0.5.1" }, "stripe": { "hashes": [ - "sha256:3cf4bab592afecaaff69c12ecb99c8376a00bc22e26ea7130a0596e209bf3e88", - "sha256:c42d8f6b4463a54f3a025581810f4e632e2d5a71de6100fc595d75581f69a492" + "sha256:4c53d61d7b596070324bfa5d7215843145fe5466e48973d828aab41ad209b5ce", + "sha256:dec812eabc95488862be40e6c799acdaf2e1225d686490a793f949fab745fdd0" ], "index": "pypi", - "version": "==9.9.0" + "version": "==11.2.0" }, "tornado": { "hashes": [ - "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0", - "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63", - "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263", - "sha256:6f8a6c77900f5ae93d8b4ae1196472d0ccc2775cc1dfdc9e7727889145c45052", - "sha256:71ddfc23a0e03ef2df1c1397d859868d158c8276a0603b96cf86892bff58149f", - "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee", - "sha256:88b84956273fbd73420e6d4b8d5ccbe913c65d31351b4c004ae362eba06e1f78", - "sha256:e43bc2e5370a6a8e413e1e1cd0c91bedc5bd62a74a532371042a18ef19e10579", - "sha256:f0251554cdd50b4b44362f73ad5ba7126fc5b2c2895cc62b14a1c2d7ea32f212", - "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e", - "sha256:fd03192e287fbd0899dd8f81c6fb9cbbc69194d2074b38f384cb6fa72b80e9c2" + "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8", + "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f", + "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4", + "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3", + "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14", + "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842", + "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9", + "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698", + "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7", + "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d", + "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4" ], "markers": "python_version >= '3.8'", - "version": "==6.4" + "version": "==6.4.1" }, "trio": { "hashes": [ - "sha256:9f5314f014ea3af489e77b001861c535005c3858d38ec46b6b071ebfa339d7fb", - "sha256:e42617ba091e7b2e50c899052e83a3c403101841de925187f61e7b7eaebdf3fb" + "sha256:1dcc95ab1726b2da054afea8fd761af74bad79bd52381b84eae408e983c76831", + "sha256:68eabbcf8f457d925df62da780eff15ff5dc68fd6b367e2dde59f7aaf2a0b884" ], "markers": "python_version >= '3.8'", - "version": "==0.25.1" + "version": "==0.27.0" }, "trio-websocket": { "hashes": [ @@ -1137,19 +1231,19 @@ }, "typing-extensions": { "hashes": [ - "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a", - "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1" + "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", + "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" ], "markers": "python_version >= '3.8'", - "version": "==4.12.1" + "version": "==4.12.2" }, "tzdata": { "hashes": [ - "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd", - "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252" + "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", + "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd" ], "markers": "python_version >= '2'", - "version": "==2024.1" + "version": "==2024.2" }, "undetected-chromedriver": { "hashes": [ @@ -1171,11 +1265,11 @@ "socks" ], "hashes": [ - "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", - "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19" + "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", + "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9" ], "markers": "python_version >= '3.8'", - "version": "==2.2.1" + "version": "==2.2.3" }, "vine": { "hashes": [ @@ -1192,91 +1286,113 @@ ], "version": "==0.2.13" }, - "websockets": { + "websocket-client": { "hashes": [ - "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b", - "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6", - "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df", - "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b", - "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205", - "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892", - "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53", - "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2", - "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed", - "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c", - "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd", - "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b", - "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931", - "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30", - "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370", - "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be", - "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec", - "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf", - "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62", - "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b", - "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402", - "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f", - "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123", - "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9", - "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603", - "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45", - "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558", - "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4", - "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438", - "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137", - "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480", - "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447", - "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8", - "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04", - "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c", - "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb", - "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967", - "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b", - "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d", - "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def", - "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c", - "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92", - "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2", - "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113", - "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b", - "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28", - "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7", - "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d", - "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f", - "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468", - "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8", - "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae", - "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611", - "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d", - "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9", - "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca", - "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f", - "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2", - "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077", - "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2", - "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6", - "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374", - "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc", - "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e", - "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53", - "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399", - "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547", - "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3", - "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870", - "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5", - "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8", - "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7" + "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", + "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da" ], "markers": "python_version >= '3.8'", - "version": "==12.0" + "version": "==1.8.0" + }, + "websockets": { + "hashes": [ + "sha256:004280a140f220c812e65f36944a9ca92d766b6cc4560be652a0a3883a79ed8a", + "sha256:035233b7531fb92a76beefcbf479504db8c72eb3bff41da55aecce3a0f729e54", + "sha256:149e622dc48c10ccc3d2760e5f36753db9cacf3ad7bc7bbbfd7d9c819e286f23", + "sha256:163e7277e1a0bd9fb3c8842a71661ad19c6aa7bb3d6678dc7f89b17fbcc4aeb7", + "sha256:18503d2c5f3943e93819238bf20df71982d193f73dcecd26c94514f417f6b135", + "sha256:1971e62d2caa443e57588e1d82d15f663b29ff9dfe7446d9964a4b6f12c1e700", + "sha256:204e5107f43095012b00f1451374693267adbb832d29966a01ecc4ce1db26faf", + "sha256:2510c09d8e8df777177ee3d40cd35450dc169a81e747455cc4197e63f7e7bfe5", + "sha256:25c35bf84bf7c7369d247f0b8cfa157f989862c49104c5cf85cb5436a641d93e", + "sha256:2f85cf4f2a1ba8f602298a853cec8526c2ca42a9a4b947ec236eaedb8f2dc80c", + "sha256:308e20f22c2c77f3f39caca508e765f8725020b84aa963474e18c59accbf4c02", + "sha256:325b1ccdbf5e5725fdcb1b0e9ad4d2545056479d0eee392c291c1bf76206435a", + "sha256:327b74e915cf13c5931334c61e1a41040e365d380f812513a255aa804b183418", + "sha256:346bee67a65f189e0e33f520f253d5147ab76ae42493804319b5716e46dddf0f", + "sha256:38377f8b0cdeee97c552d20cf1865695fcd56aba155ad1b4ca8779a5b6ef4ac3", + "sha256:3c78383585f47ccb0fcf186dcb8a43f5438bd7d8f47d69e0b56f71bf431a0a68", + "sha256:4059f790b6ae8768471cddb65d3c4fe4792b0ab48e154c9f0a04cefaabcd5978", + "sha256:459bf774c754c35dbb487360b12c5727adab887f1622b8aed5755880a21c4a20", + "sha256:463e1c6ec853202dd3657f156123d6b4dad0c546ea2e2e38be2b3f7c5b8e7295", + "sha256:4676df3fe46956fbb0437d8800cd5f2b6d41143b6e7e842e60554398432cf29b", + "sha256:485307243237328c022bc908b90e4457d0daa8b5cf4b3723fd3c4a8012fce4c6", + "sha256:48a2ef1381632a2f0cb4efeff34efa97901c9fbc118e01951ad7cfc10601a9bb", + "sha256:4b889dbd1342820cc210ba44307cf75ae5f2f96226c0038094455a96e64fb07a", + "sha256:586a356928692c1fed0eca68b4d1c2cbbd1ca2acf2ac7e7ebd3b9052582deefa", + "sha256:58cf7e75dbf7e566088b07e36ea2e3e2bd5676e22216e4cad108d4df4a7402a0", + "sha256:5993260f483d05a9737073be197371940c01b257cc45ae3f1d5d7adb371b266a", + "sha256:5dd6da9bec02735931fccec99d97c29f47cc61f644264eb995ad6c0c27667238", + "sha256:5f2e75431f8dc4a47f31565a6e1355fb4f2ecaa99d6b89737527ea917066e26c", + "sha256:5f9fee94ebafbc3117c30be1844ed01a3b177bb6e39088bc6b2fa1dc15572084", + "sha256:61fc0dfcda609cda0fc9fe7977694c0c59cf9d749fbb17f4e9483929e3c48a19", + "sha256:624459daabeb310d3815b276c1adef475b3e6804abaf2d9d2c061c319f7f187d", + "sha256:62d516c325e6540e8a57b94abefc3459d7dab8ce52ac75c96cad5549e187e3a7", + "sha256:6548f29b0e401eea2b967b2fdc1c7c7b5ebb3eeb470ed23a54cd45ef078a0db9", + "sha256:6d2aad13a200e5934f5a6767492fb07151e1de1d6079c003ab31e1823733ae79", + "sha256:6d6855bbe70119872c05107e38fbc7f96b1d8cb047d95c2c50869a46c65a8e96", + "sha256:70c5be9f416aa72aab7a2a76c90ae0a4fe2755c1816c153c1a2bcc3333ce4ce6", + "sha256:730f42125ccb14602f455155084f978bd9e8e57e89b569b4d7f0f0c17a448ffe", + "sha256:7a43cfdcddd07f4ca2b1afb459824dd3c6d53a51410636a2c7fc97b9a8cf4842", + "sha256:7bd6abf1e070a6b72bfeb71049d6ad286852e285f146682bf30d0296f5fbadfa", + "sha256:7c1e90228c2f5cdde263253fa5db63e6653f1c00e7ec64108065a0b9713fa1b3", + "sha256:7c65ffa900e7cc958cd088b9a9157a8141c991f8c53d11087e6fb7277a03f81d", + "sha256:80c421e07973a89fbdd93e6f2003c17d20b69010458d3a8e37fb47874bd67d51", + "sha256:82d0ba76371769d6a4e56f7e83bb8e81846d17a6190971e38b5de108bde9b0d7", + "sha256:83f91d8a9bb404b8c2c41a707ac7f7f75b9442a0a876df295de27251a856ad09", + "sha256:87c6e35319b46b99e168eb98472d6c7d8634ee37750d7693656dc766395df096", + "sha256:8d23b88b9388ed85c6faf0e74d8dec4f4d3baf3ecf20a65a47b836d56260d4b9", + "sha256:9156c45750b37337f7b0b00e6248991a047be4aa44554c9886fe6bdd605aab3b", + "sha256:91a0fa841646320ec0d3accdff5b757b06e2e5c86ba32af2e0815c96c7a603c5", + "sha256:95858ca14a9f6fa8413d29e0a585b31b278388aa775b8a81fa24830123874678", + "sha256:95df24ca1e1bd93bbca51d94dd049a984609687cb2fb08a7f2c56ac84e9816ea", + "sha256:9b37c184f8b976f0c0a231a5f3d6efe10807d41ccbe4488df8c74174805eea7d", + "sha256:9b6f347deb3dcfbfde1c20baa21c2ac0751afaa73e64e5b693bb2b848efeaa49", + "sha256:9d75baf00138f80b48f1eac72ad1535aac0b6461265a0bcad391fc5aba875cfc", + "sha256:9ef8aa8bdbac47f4968a5d66462a2a0935d044bf35c0e5a8af152d58516dbeb5", + "sha256:a11e38ad8922c7961447f35c7b17bffa15de4d17c70abd07bfbe12d6faa3e027", + "sha256:a1b54689e38d1279a51d11e3467dd2f3a50f5f2e879012ce8f2d6943f00e83f0", + "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878", + "sha256:a569eb1b05d72f9bce2ebd28a1ce2054311b66677fcd46cf36204ad23acead8c", + "sha256:a7affedeb43a70351bb811dadf49493c9cfd1ed94c9c70095fd177e9cc1541fa", + "sha256:a9a396a6ad26130cdae92ae10c36af09d9bfe6cafe69670fd3b6da9b07b4044f", + "sha256:a9ab1e71d3d2e54a0aa646ab6d4eebfaa5f416fe78dfe4da2839525dc5d765c6", + "sha256:a9cd1af7e18e5221d2878378fbc287a14cd527fdd5939ed56a18df8a31136bb2", + "sha256:a9dcaf8b0cc72a392760bb8755922c03e17a5a54e08cca58e8b74f6902b433cf", + "sha256:b9d7439d7fab4dce00570bb906875734df13d9faa4b48e261c440a5fec6d9708", + "sha256:bcc03c8b72267e97b49149e4863d57c2d77f13fae12066622dc78fe322490fe6", + "sha256:c11d4d16e133f6df8916cc5b7e3e96ee4c44c936717d684a94f48f82edb7c92f", + "sha256:c1dca61c6db1166c48b95198c0b7d9c990b30c756fc2923cc66f68d17dc558fd", + "sha256:c518e84bb59c2baae725accd355c8dc517b4a3ed8db88b4bc93c78dae2974bf2", + "sha256:c7934fd0e920e70468e676fe7f1b7261c1efa0d6c037c6722278ca0228ad9d0d", + "sha256:c7e72ce6bda6fb9409cc1e8164dd41d7c91466fb599eb047cfda72fe758a34a7", + "sha256:c90d6dec6be2c7d03378a574de87af9b1efea77d0c52a8301dd831ece938452f", + "sha256:ceec59f59d092c5007e815def4ebb80c2de330e9588e101cf8bd94c143ec78a5", + "sha256:cf1781ef73c073e6b0f90af841aaf98501f975d306bbf6221683dd594ccc52b6", + "sha256:d04f13a1d75cb2b8382bdc16ae6fa58c97337253826dfe136195b7f89f661557", + "sha256:d6d300f8ec35c24025ceb9b9019ae9040c1ab2f01cddc2bcc0b518af31c75c14", + "sha256:d8dbb1bf0c0a4ae8b40bdc9be7f644e2f3fb4e8a9aca7145bfa510d4a374eeb7", + "sha256:de58647e3f9c42f13f90ac7e5f58900c80a39019848c5547bc691693098ae1bd", + "sha256:deeb929efe52bed518f6eb2ddc00cc496366a14c726005726ad62c2dd9017a3c", + "sha256:df01aea34b6e9e33572c35cd16bae5a47785e7d5c8cb2b54b2acdb9678315a17", + "sha256:e2620453c075abeb0daa949a292e19f56de518988e079c36478bacf9546ced23", + "sha256:e4450fc83a3df53dec45922b576e91e94f5578d06436871dce3a6be38e40f5db", + "sha256:e54affdeb21026329fb0744ad187cf812f7d3c2aa702a5edb562b325191fcab6", + "sha256:e9875a0143f07d74dc5e1ded1c4581f0d9f7ab86c78994e2ed9e95050073c94d", + "sha256:f1c3cf67185543730888b20682fb186fc8d0fa6f07ccc3ef4390831ab4b388d9", + "sha256:f48c749857f8fb598fb890a75f540e3221d0976ed0bf879cf3c7eef34151acee", + "sha256:f779498eeec470295a2b1a5d97aa1bc9814ecd25e1eb637bd9d1c73a327387f6" + ], + "markers": "python_version >= '3.8'", + "version": "==13.1" }, "whitenoise": { "hashes": [ - "sha256:8998f7370973447fac1e8ef6e8ded2c5209a7b1f67c1012866dbcd09681c3251", - "sha256:b1f9db9bf67dc183484d760b99f4080185633136a273a03f6436034a41064146" + "sha256:486bd7267a375fa9650b136daaec156ac572971acc8bf99add90817a530dd1d4", + "sha256:df12dce147a043d1956d81d288c6f0044147c6d2ab9726e5772ac50fb45d2280" ], "index": "pypi", - "version": "==6.6.0" + "version": "==6.8.2" }, "wsproto": { "hashes": [ diff --git a/backend/accounts/admin.py b/backend/accounts/admin.py index 1909611..0612f79 100644 --- a/backend/accounts/admin.py +++ b/backend/accounts/admin.py @@ -6,11 +6,13 @@ from .models import CustomUser class CustomUserAdmin(UserAdmin): model = CustomUser - list_display = ('id', 'is_active', 'user_group',) + UserAdmin.list_display + list_display = ( + "id", + "is_active", + "user_group", + ) + UserAdmin.list_display # Editable fields per instance - fieldsets = UserAdmin.fieldsets + ( - (None, {'fields': ('avatar',)}), - ) + fieldsets = UserAdmin.fieldsets + ((None, {"fields": ("avatar",)}),) admin.site.register(CustomUser, CustomUserAdmin) diff --git a/backend/accounts/apps.py b/backend/accounts/apps.py index 1bc78fc..dcdbb2c 100644 --- a/backend/accounts/apps.py +++ b/backend/accounts/apps.py @@ -2,8 +2,8 @@ from django.apps import AppConfig class AccountsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'accounts' + default_auto_field = "django.db.models.BigAutoField" + name = "accounts" def ready(self): import accounts.signals diff --git a/backend/accounts/migrations/0001_initial.py b/backend/accounts/migrations/0001_initial.py index f1205fc..738addf 100644 --- a/backend/accounts/migrations/0001_initial.py +++ b/backend/accounts/migrations/0001_initial.py @@ -13,38 +13,145 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('auth', '0012_alter_user_first_name_max_length'), - ('user_groups', '0001_initial'), + ("auth", "0012_alter_user_first_name_max_length"), + ("user_groups", "0001_initial"), ] operations = [ migrations.CreateModel( - name='CustomUser', + name="CustomUser", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('avatar', django_resized.forms.ResizedImageField(crop=None, force_format='WEBP', keep_meta=True, null=True, quality=100, scale=None, size=[1920, 1080], upload_to='avatars/')), - ('onboarding', models.BooleanField(default=True)), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), - ('user_group', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='user_groups.usergroup')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), + ), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ( + "username", + models.CharField( + error_messages={ + "unique": "A user with that username already exists." + }, + help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", + max_length=150, + unique=True, + validators=[ + django.contrib.auth.validators.UnicodeUsernameValidator() + ], + verbose_name="username", + ), + ), + ( + "first_name", + models.CharField( + blank=True, max_length=150, verbose_name="first name" + ), + ), + ( + "last_name", + models.CharField( + blank=True, max_length=150, verbose_name="last name" + ), + ), + ( + "email", + models.EmailField( + blank=True, max_length=254, verbose_name="email address" + ), + ), + ( + "is_staff", + models.BooleanField( + default=False, + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + ), + ), + ( + "date_joined", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="date joined" + ), + ), + ( + "avatar", + django_resized.forms.ResizedImageField( + crop=None, + force_format="WEBP", + keep_meta=True, + null=True, + quality=100, + scale=None, + size=[1920, 1080], + upload_to="avatars/", + ), + ), + ("onboarding", models.BooleanField(default=True)), + ( + "groups", + models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.group", + verbose_name="groups", + ), + ), + ( + "user_group", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="user_groups.usergroup", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.permission", + verbose_name="user permissions", + ), + ), ], options={ - 'verbose_name': 'user', - 'verbose_name_plural': 'users', - 'abstract': False, + "verbose_name": "user", + "verbose_name_plural": "users", + "abstract": False, }, managers=[ - ('objects', django.contrib.auth.models.UserManager()), + ("objects", django.contrib.auth.models.UserManager()), ], ), ] diff --git a/backend/accounts/models.py b/backend/accounts/models.py index 1a8eb3e..a412105 100644 --- a/backend/accounts/models.py +++ b/backend/accounts/models.py @@ -15,14 +15,16 @@ class CustomUser(AbstractUser): # is_admin inherited from base user class avatar = ResizedImageField( - null=True, force_format="WEBP", quality=100, upload_to='avatars/') + null=True, force_format="WEBP", quality=100, upload_to="avatars/" + ) # Used for onboarding processes # Set this to False later on once the user makes actions onboarding = models.BooleanField(default=True) user_group = models.ForeignKey( - 'user_groups.UserGroup', on_delete=models.SET_NULL, null=True) + "user_groups.UserGroup", on_delete=models.SET_NULL, null=True + ) @property def group_member(self): @@ -57,4 +59,4 @@ class CustomUser(AbstractUser): @property def admin_url(self): - return reverse('admin:users_customuser_change', args=(self.pk,)) + return reverse("admin:users_customuser_change", args=(self.pk,)) diff --git a/backend/accounts/serializers.py b/backend/accounts/serializers.py index 9df1d89..c869dce 100644 --- a/backend/accounts/serializers.py +++ b/backend/accounts/serializers.py @@ -8,13 +8,14 @@ from django.core.cache import cache from django.core import exceptions as django_exceptions from rest_framework.settings import api_settings from django.contrib.auth.password_validation import validate_password + # There can be multiple subject instances with the same name, only differing in course, year level, and semester. We filter them here class SimpleCustomUserSerializer(ModelSerializer): class Meta(BaseUserSerializer.Meta): model = CustomUser - fields = ('id', 'username', 'email', 'full_name') + fields = ("id", "username", "email", "full_name") class CustomUserSerializer(BaseUserSerializer): @@ -22,19 +23,36 @@ class CustomUserSerializer(BaseUserSerializer): class Meta(BaseUserSerializer.Meta): model = CustomUser - fields = ('id', 'username', 'email', 'avatar', 'first_name', - 'last_name', 'user_group', 'group_member', 'group_owner') - read_only_fields = ('id', 'username', 'email', 'user_group', - 'group_member', 'group_owner') + fields = ( + "id", + "username", + "email", + "avatar", + "first_name", + "is_new", + "last_name", + "user_group", + "group_member", + "group_owner", + ) + read_only_fields = ( + "id", + "username", + "email", + "user_group", + "group_member", + "group_owner", + ) def to_representation(self, instance): representation = super().to_representation(instance) - representation['user_group'] = SimpleUserGroupSerializer( - instance.user_group, many=False).data + representation["user_group"] = SimpleUserGroupSerializer( + instance.user_group, many=False + ).data return representation def update(self, instance, validated_data): - cache.delete(f'user:{instance.id}') + cache.delete(f"user:{instance.id}") return super().update(instance, validated_data) @@ -42,16 +60,18 @@ class UserRegistrationSerializer(serializers.ModelSerializer): email = serializers.EmailField(required=True) username = serializers.CharField(required=True) password = serializers.CharField( - write_only=True, style={'input_type': 'password', 'placeholder': 'Password'}) + write_only=True, style={"input_type": "password", "placeholder": "Password"} + ) first_name = serializers.CharField( - required=True, allow_blank=False, allow_null=False) + required=True, allow_blank=False, allow_null=False + ) last_name = serializers.CharField( - required=True, allow_blank=False, allow_null=False) + required=True, allow_blank=False, allow_null=False + ) class Meta: model = CustomUser - fields = ['email', 'username', 'password', - 'first_name', 'last_name'] + fields = ["email", "username", "password", "first_name", "last_name"] def validate(self, attrs): user_attrs = attrs.copy() @@ -69,14 +89,15 @@ class UserRegistrationSerializer(serializers.ModelSerializer): raise serializers.ValidationError({"password": errors}) if self.Meta.model.objects.filter(username=attrs.get("username")).exists(): raise serializers.ValidationError( - "A user with that username already exists.") + "A user with that username already exists." + ) return super().validate(attrs) def create(self, validated_data): user = self.Meta.model(**validated_data) - user.username = validated_data['username'] + user.username = validated_data["username"] user.is_active = False - user.set_password(validated_data['password']) + user.set_password(validated_data["password"]) user.save() return user diff --git a/backend/accounts/signals.py b/backend/accounts/signals.py index 7e29149..185781a 100644 --- a/backend/accounts/signals.py +++ b/backend/accounts/signals.py @@ -12,38 +12,37 @@ import json @receiver(post_migrate) def create_users(sender, **kwargs): if sender.name == "accounts": - with open(os.path.join(ROOT_DIR, 'seed_data.json'), "r") as f: + with open(os.path.join(ROOT_DIR, "seed_data.json"), "r") as f: seed_data = json.loads(f.read()) - for user in seed_data['users']: - USER = CustomUser.objects.filter( - email=user['email']).first() + for user in seed_data["users"]: + USER = CustomUser.objects.filter(email=user["email"]).first() if not USER: - if user['password'] == 'USE_REGULAR': - password = get_secret('SEED_DATA_PASSWORD') - elif user['password'] == 'USE_ADMIN': - password = get_secret('SEED_DATA_ADMIN_PASSWORD') + if user["password"] == "USE_REGULAR": + password = get_secret("SEED_DATA_PASSWORD") + elif user["password"] == "USE_ADMIN": + password = get_secret("SEED_DATA_ADMIN_PASSWORD") else: - password = user['password'] - if (user['is_superuser'] == True): + password = user["password"] + if user["is_superuser"] == True: # Admin users are created regardless of SEED_DATA value USER = CustomUser.objects.create_superuser( - username=user['username'], - email=user['email'], + username=user["username"], + email=user["email"], password=password, ) - print('Created Superuser:', user['email']) + print("Created Superuser:", user["email"]) else: # Only create non-admin users if SEED_DATA=True if SEED_DATA: USER = CustomUser.objects.create_user( - username=user['email'], - email=user['email'], + username=user["email"], + email=user["email"], password=password, ) - print('Created User:', user['email']) + print("Created User:", user["email"]) - USER.first_name = user['first_name'] - USER.last_name = user['last_name'] + USER.first_name = user["first_name"] + USER.last_name = user["last_name"] USER.is_active = True USER.save() @@ -51,53 +50,57 @@ def create_users(sender, **kwargs): @receiver(post_migrate) def create_celery_beat_schedules(sender, **kwargs): if sender.name == "django_celery_beat": - with open(os.path.join(ROOT_DIR, 'seed_data.json'), "r") as f: + with open(os.path.join(ROOT_DIR, "seed_data.json"), "r") as f: seed_data = json.loads(f.read()) # Creating Schedules - for schedule in seed_data['schedules']: - if schedule['type'] == 'crontab': + for schedule in seed_data["schedules"]: + if schedule["type"] == "crontab": # Check if Schedule already exists - SCHEDULE = CrontabSchedule.objects.filter(minute=schedule['minute'], - hour=schedule['hour'], - day_of_week=schedule['day_of_week'], - day_of_month=schedule['day_of_month'], - month_of_year=schedule['month_of_year'], - timezone=schedule['timezone'] - ).first() + SCHEDULE = CrontabSchedule.objects.filter( + minute=schedule["minute"], + hour=schedule["hour"], + day_of_week=schedule["day_of_week"], + day_of_month=schedule["day_of_month"], + month_of_year=schedule["month_of_year"], + timezone=schedule["timezone"], + ).first() # If it does not exist, create a new Schedule if not SCHEDULE: SCHEDULE = CrontabSchedule.objects.create( - minute=schedule['minute'], - hour=schedule['hour'], - day_of_week=schedule['day_of_week'], - day_of_month=schedule['day_of_month'], - month_of_year=schedule['month_of_year'], - timezone=schedule['timezone'] + minute=schedule["minute"], + hour=schedule["hour"], + day_of_week=schedule["day_of_week"], + day_of_month=schedule["day_of_month"], + month_of_year=schedule["month_of_year"], + timezone=schedule["timezone"], ) print( - f'Created Crontab Schedule for Hour:{SCHEDULE.hour},Minute:{SCHEDULE.minute}') + f"Created Crontab Schedule for Hour:{SCHEDULE.hour},Minute:{SCHEDULE.minute}" + ) else: print( - f'Crontab Schedule for Hour:{SCHEDULE.hour},Minute:{SCHEDULE.minute} already exists') - for task in seed_data['scheduled_tasks']: - TASK = PeriodicTask.objects.filter(name=task['name']).first() + f"Crontab Schedule for Hour:{SCHEDULE.hour},Minute:{SCHEDULE.minute} already exists" + ) + for task in seed_data["scheduled_tasks"]: + TASK = PeriodicTask.objects.filter(name=task["name"]).first() if not TASK: - if task['schedule']['type'] == 'crontab': - SCHEDULE = CrontabSchedule.objects.filter(minute=task['schedule']['minute'], - hour=task['schedule']['hour'], - day_of_week=task['schedule']['day_of_week'], - day_of_month=task['schedule']['day_of_month'], - month_of_year=task['schedule']['month_of_year'], - timezone=task['schedule']['timezone'] - ).first() + if task["schedule"]["type"] == "crontab": + SCHEDULE = CrontabSchedule.objects.filter( + minute=task["schedule"]["minute"], + hour=task["schedule"]["hour"], + day_of_week=task["schedule"]["day_of_week"], + day_of_month=task["schedule"]["day_of_month"], + month_of_year=task["schedule"]["month_of_year"], + timezone=task["schedule"]["timezone"], + ).first() TASK = PeriodicTask.objects.create( crontab=SCHEDULE, - name=task['name'], - task=task['task'], - enabled=task['enabled'] + name=task["name"], + task=task["task"], + enabled=task["enabled"], ) - print(f'Created Periodic Task: {TASK.name}') + print(f"Created Periodic Task: {TASK.name}") else: - raise Exception('Schedule for Periodic Task not found') + raise Exception("Schedule for Periodic Task not found") else: - print(f'Periodic Task: {TASK.name} already exists') + print(f"Periodic Task: {TASK.name} already exists") diff --git a/backend/accounts/tasks.py b/backend/accounts/tasks.py index 784d5a9..1ae8d55 100644 --- a/backend/accounts/tasks.py +++ b/backend/accounts/tasks.py @@ -4,20 +4,26 @@ from celery import shared_task @shared_task def get_paying_users(): from subscriptions.models import UserSubscription + # Get a list of user subscriptions - active_subscriptions = UserSubscription.objects.filter( - valid=True).distinct('user') + active_subscriptions = UserSubscription.objects.filter(valid=True).distinct("user") # Get paying users active_users = [] # Paying regular users active_users += [ - subscription.user.id for subscription in active_subscriptions if subscription.user is not None and subscription.user.user_group is None] + subscription.user.id + for subscription in active_subscriptions + if subscription.user is not None and subscription.user.user_group is None + ] # Paying users within groups active_users += [ - subscription.user_group.members for subscription in active_subscriptions if subscription.user_group is not None and subscription.user is None] + subscription.user_group.members + for subscription in active_subscriptions + if subscription.user_group is not None and subscription.user is None + ] # Return paying users return active_users diff --git a/backend/accounts/urls.py b/backend/accounts/urls.py index 5408e39..af98b55 100644 --- a/backend/accounts/urls.py +++ b/backend/accounts/urls.py @@ -3,10 +3,10 @@ from rest_framework.routers import DefaultRouter from accounts import views router = DefaultRouter() -router.register(r'users', views.CustomUserViewSet, basename='users') +router.register(r"users", views.CustomUserViewSet, basename="users") urlpatterns = [ - path('', include(router.urls)), - path('', include('djoser.urls')), - path('', include('djoser.urls.jwt')), + path("", include(router.urls)), + path("", include("djoser.urls")), + path("", include("djoser.urls.jwt")), ] diff --git a/backend/accounts/validators.py b/backend/accounts/validators.py index 4eb10ef..c573e46 100644 --- a/backend/accounts/validators.py +++ b/backend/accounts/validators.py @@ -1,4 +1,3 @@ - from django.core.exceptions import ValidationError from django.utils.translation import gettext as _ import re @@ -6,9 +5,10 @@ import re class UppercaseValidator(object): def validate(self, password, user=None): - if not re.findall('[A-Z]', password): + if not re.findall("[A-Z]", password): raise ValidationError( - _("The password must contain at least 1 uppercase letter (A-Z).")) + _("The password must contain at least 1 uppercase letter (A-Z).") + ) def get_help_text(self): return _("Your password must contain at least 1 uppercase letter (A-Z).") @@ -16,9 +16,10 @@ class UppercaseValidator(object): class LowercaseValidator(object): def validate(self, password, user=None): - if not re.findall('[a-z]', password): + if not re.findall("[a-z]", password): raise ValidationError( - _("The password must contain at least 1 lowercase letter (a-z).")) + _("The password must contain at least 1 lowercase letter (a-z).") + ) def get_help_text(self): return _("Your password must contain at least 1 lowercase letter (a-z).") @@ -26,19 +27,25 @@ class LowercaseValidator(object): class SpecialCharacterValidator(object): def validate(self, password, user=None): - if not re.findall('[@#$%^&*()_+/\<>;:!?]', password): + if not re.findall("[@#$%^&*()_+/\<>;:!?]", password): raise ValidationError( - _("The password must contain at least 1 special character (@, #, $, etc.).")) + _( + "The password must contain at least 1 special character (@, #, $, etc.)." + ) + ) def get_help_text(self): - return _("Your password must contain at least 1 special character (@, #, $, etc.).") + return _( + "Your password must contain at least 1 special character (@, #, $, etc.)." + ) class NumberValidator(object): def validate(self, password, user=None): if not any(char.isdigit() for char in password): raise ValidationError( - _("The password must contain at least one numerical digit (0-9).")) + _("The password must contain at least one numerical digit (0-9).") + ) def get_help_text(self): return _("Your password must contain at least numerical digit (0-9).") diff --git a/backend/accounts/views.py b/backend/accounts/views.py index aac81c8..5b6d1da 100644 --- a/backend/accounts/views.py +++ b/backend/accounts/views.py @@ -22,28 +22,27 @@ class CustomUserViewSet(DjoserUserViewSet): user = self.request.user # If user is admin, show all active users if user.is_superuser: - key = 'users' + key = "users" # Get cache queryset = cache.get(key) # Set cache if stale or does not exist if not queryset: queryset = CustomUser.objects.filter(is_active=True) - cache.set(key, queryset, 60*60) + cache.set(key, queryset, 60 * 60) return queryset elif not user.user_group: - key = f'user:{user.id}' + key = f"user:{user.id}" queryset = cache.get(key) if not queryset: queryset = CustomUser.objects.filter(is_active=True) - cache.set(key, queryset, 60*60) + cache.set(key, queryset, 60 * 60) return queryset elif user.user_group: - key = f'usergroup_users:{user.user_group.id}' + key = f"usergroup_users:{user.user_group.id}" queryset = cache.get(key) if not queryset: - queryset = CustomUser.objects.filter( - user_group=user.user_group) - cache.set(key, queryset, 60*60) + queryset = CustomUser.objects.filter(user_group=user.user_group) + cache.set(key, queryset, 60 * 60) return queryset else: return CustomUser.objects.none() @@ -52,10 +51,10 @@ class CustomUserViewSet(DjoserUserViewSet): user = self.request.user # Clear cache - cache.delete(f'users') - cache.delete(f'user:{user.id}') + cache.delete(f"users") + cache.delete(f"user:{user.id}") if user.user_group: - cache.delete(f'usergroup_users:{user.user_group.id}') + cache.delete(f"usergroup_users:{user.user_group.id}") super().perform_update(serializer, *args, **kwargs) user = serializer.instance @@ -84,16 +83,18 @@ class CustomUserViewSet(DjoserUserViewSet): settings.EMAIL.confirmation(self.request, context).send(to) # Clear cache - cache.delete('users') - cache.delete(f'user:{user.id}') + cache.delete("users") + cache.delete(f"user:{user.id}") if user.user_group: - cache.delete(f'usergroup_users:{user.user_group.id}') + cache.delete(f"usergroup_users:{user.user_group.id}") except Exception as e: - print('Warning: Unable to send email') + print("Warning: Unable to send email") print(e) - @action(methods=['post'], detail=False, url_path='activation', url_name='activation') + @action( + methods=["post"], detail=False, url_path="activation", url_name="activation" + ) def activation(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) @@ -103,16 +104,16 @@ class CustomUserViewSet(DjoserUserViewSet): # Construct a response with user's first name, last name, and email user_data = { - 'first_name': user.first_name, - 'last_name': user.last_name, - 'email': user.email, - 'username': user.username + "first_name": user.first_name, + "last_name": user.last_name, + "email": user.email, + "username": user.username, } # Clear cache - cache.delete('users') - cache.delete(f'user:{user.id}') + cache.delete("users") + cache.delete(f"user:{user.id}") if user.user_group: - cache.delete(f'usergroup_users:{user.user_group.id}') + cache.delete(f"usergroup_users:{user.user_group.id}") return Response(user_data, status=status.HTTP_200_OK) diff --git a/backend/api/urls.py b/backend/api/urls.py index a11b671..0606328 100644 --- a/backend/api/urls.py +++ b/backend/api/urls.py @@ -1,27 +1,31 @@ from django.conf.urls.static import static from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.urls import path, include -from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView +from drf_spectacular.views import ( + SpectacularAPIView, + SpectacularRedocView, + SpectacularSwaggerView, +) from django.contrib import admin from config.settings import DEBUG, SERVE_MEDIA, MEDIA_ROOT + urlpatterns = [ - path('accounts/', include('accounts.urls')), - path('subscriptions/', include('subscriptions.urls')), - path('notifications/', include('notifications.urls')), - path('billing/', include('billing.urls')), - path('stripe/', include('payments.urls')), - path('admin/', admin.site.urls), - path('schema/', SpectacularAPIView.as_view(), name='schema'), - path('swagger/', - SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), - path('redoc/', - SpectacularRedocView.as_view(url_name='schema'), name='redoc'), + path("accounts/", include("accounts.urls")), + path("subscriptions/", include("subscriptions.urls")), + path("notifications/", include("notifications.urls")), + path("billing/", include("billing.urls")), + path("stripe/", include("payments.urls")), + path("admin/", admin.site.urls), + path("schema/", SpectacularAPIView.as_view(), name="schema"), + path( + "swagger/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui" + ), + path("redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"), ] # URLs for local development if DEBUG and SERVE_MEDIA: urlpatterns += staticfiles_urlpatterns() - urlpatterns += static( - 'media/', document_root=MEDIA_ROOT) + urlpatterns += static("media/", document_root=MEDIA_ROOT) if DEBUG: - urlpatterns += [path('silk/', include('silk.urls', namespace='silk'))] + urlpatterns += [path("silk/", include("silk.urls", namespace="silk"))] diff --git a/backend/billing/urls.py b/backend/billing/urls.py index 138f256..ab2bb02 100644 --- a/backend/billing/urls.py +++ b/backend/billing/urls.py @@ -2,6 +2,5 @@ from django.urls import path from billing import views urlpatterns = [ - path('', - views.BillingHistoryView.as_view()), + path("", views.BillingHistoryView.as_view()), ] diff --git a/backend/billing/views.py b/backend/billing/views.py index effadca..e81f83c 100644 --- a/backend/billing/views.py +++ b/backend/billing/views.py @@ -24,7 +24,7 @@ class BillingHistoryView(APIView): email = requesting_user.email # Check cache - key = f'billing_user:{requesting_user.id}' + key = f"billing_user:{requesting_user.id}" billing_history = cache.get(key) if not billing_history: @@ -39,23 +39,25 @@ class BillingHistoryView(APIView): if len(customers.data) > 0: # Retrieve the customer's charges (billing history) - charges = stripe.Charge.list( - limit=10, customer=customer.id) + charges = stripe.Charge.list(limit=10, customer=customer.id) # Prepare the response billing_history = [ { - 'email': charge['billing_details']['email'], - 'amount_charged': int(charge['amount']/100), - 'paid': charge['paid'], - 'refunded': int(charge['amount_refunded']/100) > 0, - 'amount_refunded': int(charge['amount_refunded']/100), - 'last_4': charge['payment_method_details']['card']['last4'], - 'receipt_link': charge['receipt_url'], - 'timestamp': datetime.fromtimestamp(charge['created']).strftime("%m-%d-%Y %I:%M %p"), - } for charge in charges.auto_paging_iter() + "email": charge["billing_details"]["email"], + "amount_charged": int(charge["amount"] / 100), + "paid": charge["paid"], + "refunded": int(charge["amount_refunded"] / 100) > 0, + "amount_refunded": int(charge["amount_refunded"] / 100), + "last_4": charge["payment_method_details"]["card"]["last4"], + "receipt_link": charge["receipt_url"], + "timestamp": datetime.fromtimestamp( + charge["created"] + ).strftime("%m-%d-%Y %I:%M %p"), + } + for charge in charges.auto_paging_iter() ] - cache.set(key, billing_history, 60*60) + cache.set(key, billing_history, 60 * 60) return Response(billing_history, status=status.HTTP_200_OK) diff --git a/backend/config/__init__.py b/backend/config/__init__.py index fb989c4..53f4ccb 100644 --- a/backend/config/__init__.py +++ b/backend/config/__init__.py @@ -1,3 +1,3 @@ from .celery import app as celery_app -__all__ = ('celery_app',) +__all__ = ("celery_app",) diff --git a/backend/config/asgi.py b/backend/config/asgi.py index 874e199..421729d 100644 --- a/backend/config/asgi.py +++ b/backend/config/asgi.py @@ -11,6 +11,6 @@ import os from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") application = get_asgi_application() diff --git a/backend/config/celery.py b/backend/config/celery.py index 700a668..0def3f9 100644 --- a/backend/config/celery.py +++ b/backend/config/celery.py @@ -3,15 +3,15 @@ import os # Set the default Django settings module for the 'celery' program. -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") -app = Celery('config') +app = Celery("config") # Using a string here means the worker doesn't have to serialize # the configuration object to child processes. # - namespace='CELERY' means all celery-related configuration keys # should have a `CELERY_` prefix. -app.config_from_object('django.conf:settings', namespace='CELERY') +app.config_from_object("django.conf:settings", namespace="CELERY") # Load task modules from all registered Django apps. app.autodiscover_tasks() diff --git a/backend/config/settings.py b/backend/config/settings.py index 5888156..9fa14d6 100644 --- a/backend/config/settings.py +++ b/backend/config/settings.py @@ -10,9 +10,9 @@ BASE_DIR = Path(__file__).resolve().parent.parent ROOT_DIR = Path(__file__).resolve().parent.parent.parent # If you're hosting this with a secret provider, have this set to True -USE_VAULT = bool(os.getenv('USE_VAULT', False) == 'True') +USE_VAULT = bool(os.getenv("USE_VAULT", False) == "True") # Have this set to True to serve media and static contents directly via Django -SERVE_MEDIA = bool(os.getenv('SERVE_MEDIA', False) == 'True') +SERVE_MEDIA = bool(os.getenv("SERVE_MEDIA", False) == "True") load_dotenv(find_dotenv()) @@ -35,98 +35,97 @@ def get_secret(secret_name): # URL Prefixes -URL_SCHEME = 'https' if (get_secret('USE_HTTPS') == 'True') else 'http' +URL_SCHEME = "https" if (get_secret("USE_HTTPS") == "True") else "http" # Backend -BACKEND_ADDRESS = get_secret('BACKEND_ADDRESS') -BACKEND_PORT = get_secret('BACKEND_PORT') +BACKEND_ADDRESS = get_secret("BACKEND_ADDRESS") +BACKEND_PORT = get_secret("BACKEND_PORT") # Frontend -FRONTEND_ADDRESS = get_secret('FRONTEND_ADDRESS') -FRONTEND_PORT = get_secret('FRONTEND_PORT') +FRONTEND_ADDRESS = get_secret("FRONTEND_ADDRESS") +FRONTEND_PORT = get_secret("FRONTEND_PORT") -ALLOWED_HOSTS = ['*'] +ALLOWED_HOSTS = ["*"] CSRF_TRUSTED_ORIGINS = [ # Frontend - f'{URL_SCHEME}://{FRONTEND_ADDRESS}:{FRONTEND_PORT}', - f'{URL_SCHEME}://{FRONTEND_ADDRESS}', # For external domains + f"{URL_SCHEME}://{FRONTEND_ADDRESS}:{FRONTEND_PORT}", + f"{URL_SCHEME}://{FRONTEND_ADDRESS}", # For external domains # Backend - f'{URL_SCHEME}://{BACKEND_ADDRESS}:{BACKEND_PORT}', - f'{URL_SCHEME}://{BACKEND_ADDRESS}' # For external domains + f"{URL_SCHEME}://{BACKEND_ADDRESS}:{BACKEND_PORT}", + f"{URL_SCHEME}://{BACKEND_ADDRESS}", # For external domains # You can also set up https://*.name.xyz for wildcards here ] # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = (get_secret('BACKEND_DEBUG') == 'True') +DEBUG = get_secret("BACKEND_DEBUG") == "True" # Determines whether or not to insert test data within tables -SEED_DATA = (get_secret('SEED_DATA') == 'True') +SEED_DATA = get_secret("SEED_DATA") == "True" # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = get_secret('SECRET_KEY') +SECRET_KEY = get_secret("SECRET_KEY") # Selenium Config # Initiate CAPTCHA solver in test mode -CAPTCHA_TESTING = (get_secret('CAPTCHA_TESTING') == 'True') +CAPTCHA_TESTING = get_secret("CAPTCHA_TESTING") == "True" # If using Selenium and/or the provided CAPTCHA solver, determines whether or not to use proxies -USE_PROXY = (get_secret('USE_PROXY') == 'True') +USE_PROXY = get_secret("USE_PROXY") == "True" # Stripe (For payments) -STRIPE_SECRET_KEY = get_secret( - "STRIPE_SECRET_KEY") -STRIPE_SECRET_WEBHOOK = get_secret('STRIPE_SECRET_WEBHOOK') -STRIPE_CHECKOUT_URL = f'' +STRIPE_SECRET_KEY = get_secret("STRIPE_SECRET_KEY") +STRIPE_SECRET_WEBHOOK = get_secret("STRIPE_SECRET_WEBHOOK") +STRIPE_CHECKOUT_URL = f"" # Email credentials -EMAIL_HOST = get_secret('EMAIL_HOST') -EMAIL_HOST_USER = get_secret('EMAIL_HOST_USER') -EMAIL_HOST_PASSWORD = get_secret('EMAIL_HOST_PASSWORD') -EMAIL_PORT = get_secret('EMAIL_PORT') -EMAIL_USE_TLS = (get_secret('EMAIL_USE_TLS') == 'True') -EMAIL_ADDRESS = (get_secret('EMAIL_ADDRESS') == 'True') +EMAIL_HOST = get_secret("EMAIL_HOST") +EMAIL_HOST_USER = get_secret("EMAIL_HOST_USER") +EMAIL_HOST_PASSWORD = get_secret("EMAIL_HOST_PASSWORD") +EMAIL_PORT = get_secret("EMAIL_PORT") +EMAIL_USE_TLS = get_secret("EMAIL_USE_TLS") == "True" +EMAIL_ADDRESS = get_secret("EMAIL_ADDRESS") == "True" # Application definition INSTALLED_APPS = [ - 'config', - 'unfold', - 'unfold.contrib.filters', - 'unfold.contrib.simple_history', - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'storages', - 'django_extensions', - 'rest_framework', - 'rest_framework_simplejwt', - 'django_celery_results', - 'django_celery_beat', - 'simple_history', - 'djoser', - 'corsheaders', - 'drf_spectacular', - 'drf_spectacular_sidecar', - 'webdriver', - 'accounts', - 'user_groups', - 'subscriptions', - 'payments', - 'billing', - 'emails', - 'notifications', - 'search_results' + "config", + "unfold", + "unfold.contrib.filters", + "unfold.contrib.simple_history", + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "storages", + "django_extensions", + "rest_framework", + "rest_framework_simplejwt", + "django_celery_results", + "django_celery_beat", + "simple_history", + "djoser", + "corsheaders", + "drf_spectacular", + "drf_spectacular_sidecar", + "webdriver", + "accounts", + "user_groups", + "subscriptions", + "payments", + "billing", + "emails", + "notifications", + "search_results", ] if DEBUG: - INSTALLED_APPS += ['silk'] + INSTALLED_APPS += ["silk"] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', + "django.middleware.security.SecurityMiddleware", "silk.middleware.SilkyMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "corsheaders.middleware.CorsMiddleware", - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] DJANGO_LOG_LEVEL = "DEBUG" # Enables VS Code debugger to break on raised exceptions @@ -146,111 +145,101 @@ if DEBUG: } else: MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', + "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "corsheaders.middleware.CorsMiddleware", - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.2/howto/static-files/ -ROOT_URLCONF = 'config.urls' +ROOT_URLCONF = "config.urls" if SERVE_MEDIA: # Cloud Storage Settings # This is assuming you use the same bucket for media and static containers - CLOUD_BUCKET = get_secret('CLOUD_BUCKET') - MEDIA_CONTAINER = get_secret('MEDIA_CONTAINER') - STATIC_CONTAINER = get_secret('STATIC_CONTAINER') + CLOUD_BUCKET = get_secret("CLOUD_BUCKET") + MEDIA_CONTAINER = get_secret("MEDIA_CONTAINER") + STATIC_CONTAINER = get_secret("STATIC_CONTAINER") - MEDIA_URL = f'https://{CLOUD_BUCKET}/{MEDIA_CONTAINER}/' - MEDIA_ROOT = f'https://{CLOUD_BUCKET}/' + MEDIA_URL = f"https://{CLOUD_BUCKET}/{MEDIA_CONTAINER}/" + MEDIA_ROOT = f"https://{CLOUD_BUCKET}/" - STATIC_URL = f'https://{CLOUD_BUCKET}/{STATIC_CONTAINER}/' - STATIC_ROOT = f'https://{CLOUD_BUCKET}/{STATIC_CONTAINER}/' + STATIC_URL = f"https://{CLOUD_BUCKET}/{STATIC_CONTAINER}/" + STATIC_ROOT = f"https://{CLOUD_BUCKET}/{STATIC_CONTAINER}/" # Consult django-storages documentation when filling in these values. This will vary depending on your cloud service provider STORAGES = { - 'default': { + "default": { # TODO: Set this up here if you're using cloud storage - 'BACKEND': None, - 'OPTIONS': { + "BACKEND": None, + "OPTIONS": { # Optional parameters }, }, - 'staticfiles': { + "staticfiles": { # TODO: Set this up here if you're using cloud storage - 'BACKEND': None, - 'OPTIONS': { + "BACKEND": None, + "OPTIONS": { # Optional parameters }, }, } else: - STATIC_URL = 'static/' - STATIC_ROOT = os.path.join(BASE_DIR, 'static') + STATIC_URL = "static/" + STATIC_ROOT = os.path.join(BASE_DIR, "static") STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" - MEDIA_URL = 'api/v1/media/' - MEDIA_ROOT = os.path.join(BASE_DIR, 'media') - ROOT_URLCONF = 'config.urls' + MEDIA_URL = "api/v1/media/" + MEDIA_ROOT = os.path.join(BASE_DIR, "media") + ROOT_URLCONF = "config.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [ - BASE_DIR / 'emails/templates/', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [ + BASE_DIR / "emails/templates/", ], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] REST_FRAMEWORK = { - 'DEFAULT_AUTHENTICATION_CLASSES': ( - 'rest_framework_simplejwt.authentication.JWTAuthentication', + "DEFAULT_AUTHENTICATION_CLASSES": ( + "rest_framework_simplejwt.authentication.JWTAuthentication", ), - 'DEFAULT_THROTTLE_CLASSES': [ - - 'rest_framework.throttling.AnonRateThrottle', - - 'rest_framework.throttling.UserRateThrottle' - + "DEFAULT_THROTTLE_CLASSES": [ + "rest_framework.throttling.AnonRateThrottle", + "rest_framework.throttling.UserRateThrottle", ], - - 'DEFAULT_THROTTLE_RATES': { - - 'anon': '360/min', - - 'user': '1440/min' - - }, - 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', + "DEFAULT_THROTTLE_RATES": {"anon": "360/min", "user": "1440/min"}, + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", } # DRF-Spectacular SPECTACULAR_SETTINGS = { - 'TITLE': 'DRF-Template', - 'DESCRIPTION': 'A Template Project by Keannu Bernasol', - 'VERSION': '1.0.0', - 'SERVE_INCLUDE_SCHEMA': False, - 'SWAGGER_UI_DIST': 'SIDECAR', - 'SWAGGER_UI_FAVICON_HREF': 'SIDECAR', - 'REDOC_DIST': 'SIDECAR', + "TITLE": "DRF-Template", + "DESCRIPTION": "A Template Project by Keannu Bernasol", + "VERSION": "1.0.0", + "SERVE_INCLUDE_SCHEMA": False, + "SWAGGER_UI_DIST": "SIDECAR", + "SWAGGER_UI_FAVICON_HREF": "SIDECAR", + "REDOC_DIST": "SIDECAR", } -WSGI_APPLICATION = 'config.wsgi.application' +WSGI_APPLICATION = "config.wsgi.application" # If you're using an external connection bouncer (eg. PgBouncer), server side cursors must be disabled to avoid any issues USE_BOUNCER = get_secret("USE_BOUNCER") @@ -266,15 +255,13 @@ else: DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", - 'DISABLE_SERVER_SIDE_CURSORS': DISABLE_SERVER_SIDE_CURSORS, + "DISABLE_SERVER_SIDE_CURSORS": DISABLE_SERVER_SIDE_CURSORS, "NAME": get_secret("DB_DATABASE"), "USER": get_secret("DB_USERNAME"), "PASSWORD": get_secret("DB_PASSWORD"), "HOST": DB_HOST, "PORT": DB_PORT, - "OPTIONS": { - "sslmode": get_secret("DB_SSL_MODE") - }, + "OPTIONS": {"sslmode": get_secret("DB_SSL_MODE")}, } } # Django Cache @@ -284,34 +271,34 @@ CACHES = { "LOCATION": f"redis://{get_secret('REDIS_HOST')}:{get_secret('REDIS_PORT')}/2", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", - } + }, } } -AUTH_USER_MODEL = 'accounts.CustomUser' +AUTH_USER_MODEL = "accounts.CustomUser" DJOSER = { - 'SEND_ACTIVATION_EMAIL': True, - 'SEND_CONFIRMATION_EMAIL': True, - 'PASSWORD_RESET_CONFIRM_URL': 'reset_password_confirm/{uid}/{token}', - 'ACTIVATION_URL': 'activation/{uid}/{token}', - 'USER_AUTHENTICATION_RULES': ['djoser.authentication.TokenAuthenticationRule'], - 'EMAIL': { - 'activation': 'emails.templates.ActivationEmail', - 'password_reset': 'emails.templates.PasswordResetEmail' + "SEND_ACTIVATION_EMAIL": True, + "SEND_CONFIRMATION_EMAIL": True, + "PASSWORD_RESET_CONFIRM_URL": "reset_password_confirm/{uid}/{token}", + "ACTIVATION_URL": "activation/{uid}/{token}", + "USER_AUTHENTICATION_RULES": ["djoser.authentication.TokenAuthenticationRule"], + "EMAIL": { + "activation": "emails.templates.ActivationEmail", + "password_reset": "emails.templates.PasswordResetEmail", }, - 'SERIALIZERS': { - 'user': 'accounts.serializers.CustomUserSerializer', - 'current_user': 'accounts.serializers.CustomUserSerializer', - 'user_create': 'accounts.serializers.UserRegistrationSerializer', + "SERIALIZERS": { + "user": "accounts.serializers.CustomUserSerializer", + "current_user": "accounts.serializers.CustomUserSerializer", + "user_create": "accounts.serializers.UserRegistrationSerializer", }, - 'PERMISSIONS': { + "PERMISSIONS": { # Disable some unneeded endpoints by setting them to admin only - 'username_reset': ['rest_framework.permissions.IsAdminUser'], - 'username_reset_confirm': ['rest_framework.permissions.IsAdminUser'], - 'set_username': ['rest_framework.permissions.IsAdminUser'], - 'set_password': ['rest_framework.permissions.IsAdminUser'], - } + "username_reset": ["rest_framework.permissions.IsAdminUser"], + "username_reset_confirm": ["rest_framework.permissions.IsAdminUser"], + "set_username": ["rest_framework.permissions.IsAdminUser"], + "set_password": ["rest_framework.permissions.IsAdminUser"], + }, } # Password validation @@ -319,32 +306,32 @@ DJOSER = { AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", "OPTIONS": { "min_length": 8, - } + }, }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, # Additional password validators { - 'NAME': 'accounts.validators.SpecialCharacterValidator', + "NAME": "accounts.validators.SpecialCharacterValidator", }, { - 'NAME': 'accounts.validators.LowercaseValidator', + "NAME": "accounts.validators.LowercaseValidator", }, { - 'NAME': 'accounts.validators.UppercaseValidator', + "NAME": "accounts.validators.UppercaseValidator", }, { - 'NAME': 'accounts.validators.NumberValidator', + "NAME": "accounts.validators.NumberValidator", }, ] @@ -352,9 +339,9 @@ AUTH_PASSWORD_VALIDATORS = [ # Internationalization # https://docs.djangoproject.com/en/4.2/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = get_secret('TIMEZONE') +TIME_ZONE = get_secret("TIMEZONE") USE_I18N = True @@ -364,14 +351,14 @@ USE_TZ = True # Default primary key field type # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" -SITE_NAME = 'DRF-Template' +SITE_NAME = "DRF-Template" # JWT Token Lifetimes SIMPLE_JWT = { "ACCESS_TOKEN_LIFETIME": timedelta(hours=1), - "REFRESH_TOKEN_LIFETIME": timedelta(days=3) + "REFRESH_TOKEN_LIFETIME": timedelta(days=3), } CORS_ALLOW_ALL_ORIGINS = True @@ -388,11 +375,19 @@ CELERY_RESULT_BACKEND = get_secret("CELERY_RESULT_BACKEND") CELERY_RESULT_EXTENDED = True # Celery Beat Options -CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler' +CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" # Maximum number of rows that can be updated within the Django admin panel DATA_UPLOAD_MAX_NUMBER_FIELDS = 20480 GRAPH_MODELS = { - 'app_labels': ['accounts', 'user_groups', 'billing', 'emails', 'payments', 'subscriptions', 'search_results'] + "app_labels": [ + "accounts", + "user_groups", + "billing", + "emails", + "payments", + "subscriptions", + "search_results", + ] } diff --git a/backend/config/urls.py b/backend/config/urls.py index 4bb0a88..d9e0921 100644 --- a/backend/config/urls.py +++ b/backend/config/urls.py @@ -1,5 +1,5 @@ from django.urls import path, include urlpatterns = [ - path('api/v1/', include('api.urls')), + path("api/v1/", include("api.urls")), ] diff --git a/backend/config/wsgi.py b/backend/config/wsgi.py index 8f9c92f..b77141c 100644 --- a/backend/config/wsgi.py +++ b/backend/config/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") application = get_wsgi_application() diff --git a/backend/emails/apps.py b/backend/emails/apps.py index 20e925a..e28480c 100644 --- a/backend/emails/apps.py +++ b/backend/emails/apps.py @@ -2,5 +2,5 @@ from django.apps import AppConfig class EmailsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'emails' + default_auto_field = "django.db.models.BigAutoField" + name = "emails" diff --git a/backend/emails/templates.py b/backend/emails/templates.py index 8ffd36b..02fde41 100644 --- a/backend/emails/templates.py +++ b/backend/emails/templates.py @@ -3,11 +3,11 @@ from django.utils import timezone class ActivationEmail(email.ActivationEmail): - template_name = 'email_activation.html' + template_name = "email_activation.html" class PasswordResetEmail(email.PasswordResetEmail): - template_name = 'password_change.html' + template_name = "password_change.html" class SubscriptionAvailedEmail(email.BaseEmailMessage): @@ -19,7 +19,7 @@ class SubscriptionAvailedEmail(email.BaseEmailMessage): context["subscription_plan"] = context.get("subscription_plan") context["subscription"] = context.get("subscription") context["price_paid"] = context.get("price_paid") - context['date'] = timezone.now().strftime("%B %d, %I:%M %p") + context["date"] = timezone.now().strftime("%B %d, %I:%M %p") context.update(self.context) return context @@ -32,7 +32,7 @@ class SubscriptionRefundedEmail(email.BaseEmailMessage): context["user"] = context.get("user") context["subscription_plan"] = context.get("subscription_plan") context["refund"] = context.get("refund") - context['date'] = timezone.now().strftime("%B %d, %I:%M %p") + context["date"] = timezone.now().strftime("%B %d, %I:%M %p") context.update(self.context) return context @@ -44,6 +44,6 @@ class SubscriptionCancelledEmail(email.BaseEmailMessage): context = super().get_context_data() context["user"] = context.get("user") context["subscription_plan"] = context.get("subscription_plan") - context['date'] = timezone.now().strftime("%B %d, %I:%M %p") + context["date"] = timezone.now().strftime("%B %d, %I:%M %p") context.update(self.context) return context diff --git a/backend/manage.py b/backend/manage.py index 8e7ac79..d28672e 100644 --- a/backend/manage.py +++ b/backend/manage.py @@ -6,7 +6,7 @@ import sys def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -18,5 +18,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/backend/notifications/admin.py b/backend/notifications/admin.py index 6a0122a..c6668f7 100644 --- a/backend/notifications/admin.py +++ b/backend/notifications/admin.py @@ -6,5 +6,5 @@ from .models import Notification @admin.register(Notification) class NotificationAdmin(ModelAdmin): model = Notification - search_fields = ('id', 'content') - list_display = ['id', 'dismissed'] + search_fields = ("id", "content") + list_display = ["id", "dismissed"] diff --git a/backend/notifications/apps.py b/backend/notifications/apps.py index 05047b5..113f910 100644 --- a/backend/notifications/apps.py +++ b/backend/notifications/apps.py @@ -2,8 +2,8 @@ from django.apps import AppConfig class NotificationsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'notifications' + default_auto_field = "django.db.models.BigAutoField" + name = "notifications" def ready(self): import notifications.signals diff --git a/backend/notifications/migrations/0001_initial.py b/backend/notifications/migrations/0001_initial.py index f2bddc7..7db222f 100644 --- a/backend/notifications/migrations/0001_initial.py +++ b/backend/notifications/migrations/0001_initial.py @@ -15,13 +15,27 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Notification', + name="Notification", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content', models.CharField(max_length=1000, null=True)), - ('timestamp', models.DateTimeField(auto_now_add=True)), - ('dismissed', models.BooleanField(default=False)), - ('recipient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("content", models.CharField(max_length=1000, null=True)), + ("timestamp", models.DateTimeField(auto_now_add=True)), + ("dismissed", models.BooleanField(default=False)), + ( + "recipient", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), ] diff --git a/backend/notifications/models.py b/backend/notifications/models.py index 75e0b4f..c76576e 100644 --- a/backend/notifications/models.py +++ b/backend/notifications/models.py @@ -2,8 +2,7 @@ from django.db import models class Notification(models.Model): - recipient = models.ForeignKey( - 'accounts.CustomUser', on_delete=models.CASCADE) + recipient = models.ForeignKey("accounts.CustomUser", on_delete=models.CASCADE) content = models.CharField(max_length=1000, null=True) timestamp = models.DateTimeField(auto_now_add=True, editable=False) dismissed = models.BooleanField(default=False) diff --git a/backend/notifications/serializers.py b/backend/notifications/serializers.py index 7432852..e6e5cb1 100644 --- a/backend/notifications/serializers.py +++ b/backend/notifications/serializers.py @@ -3,10 +3,9 @@ from notifications.models import Notification class NotificationSerializer(serializers.ModelSerializer): - timestamp = serializers.DateTimeField( - format="%m-%d-%Y %I:%M %p", read_only=True) + timestamp = serializers.DateTimeField(format="%m-%d-%Y %I:%M %p", read_only=True) class Meta: model = Notification - fields = '__all__' - read_only_fields = ('id', 'recipient', 'content', 'timestamp') + fields = "__all__" + read_only_fields = ("id", "recipient", "content", "timestamp") diff --git a/backend/notifications/signals.py b/backend/notifications/signals.py index c2d7539..35cfedf 100644 --- a/backend/notifications/signals.py +++ b/backend/notifications/signals.py @@ -9,5 +9,5 @@ from django.core.cache import cache @receiver(post_save, sender=Notification) def clear_cache_after_notification_update(sender, instance, **kwargs): # Clear cache - cache.delete('notifications') - cache.delete(f'notifications_user:{instance.recipient.id}') + cache.delete("notifications") + cache.delete(f"notifications_user:{instance.recipient.id}") diff --git a/backend/notifications/tasks.py b/backend/notifications/tasks.py index d6dd821..67bab89 100644 --- a/backend/notifications/tasks.py +++ b/backend/notifications/tasks.py @@ -9,5 +9,4 @@ def cleanup_notifications(): three_days_ago = timezone.now() - timezone.timedelta(days=3) # Delete notifications that are older than 3 days and dismissed - Notification.objects.filter( - dismissed=True, timestamp__lte=three_days_ago).delete() + Notification.objects.filter(dismissed=True, timestamp__lte=three_days_ago).delete() diff --git a/backend/notifications/urls.py b/backend/notifications/urls.py index 9cbf42a..5d62fd1 100644 --- a/backend/notifications/urls.py +++ b/backend/notifications/urls.py @@ -3,8 +3,7 @@ from notifications.views import NotificationViewSet from rest_framework.routers import DefaultRouter router = DefaultRouter() -router.register(r'', NotificationViewSet, - basename="Notifications") +router.register(r"", NotificationViewSet, basename="Notifications") urlpatterns = [ - path('', include(router.urls)), + path("", include(router.urls)), ] diff --git a/backend/notifications/views.py b/backend/notifications/views.py index 12a79fa..feb0413 100644 --- a/backend/notifications/views.py +++ b/backend/notifications/views.py @@ -6,30 +6,33 @@ from django.core.cache import cache class NotificationViewSet(viewsets.ModelViewSet): - http_method_names = ['get', 'patch', 'delete'] + http_method_names = ["get", "patch", "delete"] serializer_class = NotificationSerializer queryset = Notification.objects.all() def get_queryset(self): user = self.request.user - key = f'notifications_user:{user.id}' + key = f"notifications_user:{user.id}" queryset = cache.get(key) if not queryset: - queryset = Notification.objects.filter( - recipient=user).order_by('-timestamp') - cache.set(key, queryset, 60*60) + queryset = Notification.objects.filter(recipient=user).order_by( + "-timestamp" + ) + cache.set(key, queryset, 60 * 60) return queryset def update(self, request, *args, **kwargs): instance = self.get_object() if instance.recipient != request.user: raise PermissionDenied( - "You do not have permission to update this notification.") + "You do not have permission to update this notification." + ) return super().update(request, *args, **kwargs) def destroy(self, request, *args, **kwargs): instance = self.get_object() if instance.recipient != request.user: raise PermissionDenied( - "You do not have permission to delete this notification.") + "You do not have permission to delete this notification." + ) return super().destroy(request, *args, **kwargs) diff --git a/backend/payments/apps.py b/backend/payments/apps.py index 4886655..61898af 100644 --- a/backend/payments/apps.py +++ b/backend/payments/apps.py @@ -2,5 +2,5 @@ from django.apps import AppConfig class PaymentsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'payments' + default_auto_field = "django.db.models.BigAutoField" + name = "payments" diff --git a/backend/payments/urls.py b/backend/payments/urls.py index 66ac721..6354e7f 100644 --- a/backend/payments/urls.py +++ b/backend/payments/urls.py @@ -3,6 +3,6 @@ from payments import views urlpatterns = [ - path('checkout_session/', views.StripeCheckoutView.as_view()), - path('webhook/', views.stripe_webhook_view, name='Stripe Webhook'), + path("checkout_session/", views.StripeCheckoutView.as_view()), + path("webhook/", views.stripe_webhook_view, name="Stripe Webhook"), ] diff --git a/backend/payments/views.py b/backend/payments/views.py index 3b8bc15..3adac0b 100644 --- a/backend/payments/views.py +++ b/backend/payments/views.py @@ -1,4 +1,10 @@ -from config.settings import STRIPE_SECRET_KEY, STRIPE_SECRET_WEBHOOK, URL_SCHEME, FRONTEND_ADDRESS, FRONTEND_PORT +from config.settings import ( + STRIPE_SECRET_KEY, + STRIPE_SECRET_WEBHOOK, + URL_SCHEME, + FRONTEND_ADDRESS, + FRONTEND_PORT, +) from rest_framework.permissions import IsAuthenticated from rest_framework.views import APIView from rest_framework.response import Response @@ -12,16 +18,19 @@ from accounts.models import CustomUser from rest_framework.decorators import api_view from subscriptions.tasks import get_user_subscription import json -from emails.templates import SubscriptionAvailedEmail, SubscriptionRefundedEmail, SubscriptionCancelledEmail +from emails.templates import ( + SubscriptionAvailedEmail, + SubscriptionRefundedEmail, + SubscriptionCancelledEmail, +) from django.core.cache import cache from payments.serializers import CheckoutSerializer from drf_spectacular.utils import extend_schema + stripe.api_key = STRIPE_SECRET_KEY -@extend_schema( - request=CheckoutSerializer -) +@extend_schema(request=CheckoutSerializer) class StripeCheckoutView(APIView): permission_classes = [IsAuthenticated] @@ -30,41 +39,46 @@ class StripeCheckoutView(APIView): # Get subscription ID from POST USER = CustomUser.objects.get(id=self.request.user.id) data = json.loads(request.body) - subscription_id = data.get('subscription_id') - annual = data.get('annual') + subscription_id = data.get("subscription_id") + annual = data.get("annual") # Validation for subscription_id field try: subscription_id = int(subscription_id) except: - return Response({ - 'error': 'Invalid value specified in subscription_id field' - }, status=status.HTTP_403_FORBIDDEN) + return Response( + {"error": "Invalid value specified in subscription_id field"}, + status=status.HTTP_403_FORBIDDEN, + ) # Validation for annual field try: annual = bool(annual) except: - return Response({ - 'error': 'Invalid value specified in annual field' - }, status=status.HTTP_403_FORBIDDEN) + return Response( + {"error": "Invalid value specified in annual field"}, + status=status.HTTP_403_FORBIDDEN, + ) # Return an error if the user already has an active subscription EXISTING_SUBSCRIPTION = get_user_subscription(USER.id) if EXISTING_SUBSCRIPTION: - return Response({ - 'error': f'User is already subscribed to: {EXISTING_SUBSCRIPTION.subscription.name}' - }, status=status.HTTP_403_FORBIDDEN) + return Response( + { + "error": f"User is already subscribed to: {EXISTING_SUBSCRIPTION.subscription.name}" + }, + status=status.HTTP_403_FORBIDDEN, + ) # Attempt to query the subscription - SUBSCRIPTION = SubscriptionPlan.objects.filter( - id=subscription_id).first() + SUBSCRIPTION = SubscriptionPlan.objects.filter(id=subscription_id).first() # Return an error if the plan does not exist if not SUBSCRIPTION: - return Response({ - 'error': 'Subscription plan not found' - }, status=status.HTTP_404_NOT_FOUND) + return Response( + {"error": "Subscription plan not found"}, + status=status.HTTP_404_NOT_FOUND, + ) # Get the stripe_price_id from the related StripePrice instances if annual: @@ -74,52 +88,58 @@ class StripeCheckoutView(APIView): # Return 404 if no price is set if not PRICE: - return Response({ - 'error': 'Specified price does not exist for plan' - }, status=status.HTTP_404_NOT_FOUND) + return Response( + {"error": "Specified price does not exist for plan"}, + status=status.HTTP_404_NOT_FOUND, + ) PRICE_ID = PRICE.stripe_price_id prorated = PRICE.prorated # Return an error if a user is in a user_group and is availing pro-rated plans if not USER.user_group and SUBSCRIPTION.group_exclusive: - return Response({ - 'error': 'Regular users cannot avail prorated plans' - }, status=status.HTTP_403_FORBIDDEN) + return Response( + {"error": "Regular users cannot avail prorated plans"}, + status=status.HTTP_403_FORBIDDEN, + ) - success_url = f'{URL_SCHEME}://{FRONTEND_ADDRESS}:{FRONTEND_PORT}' + \ - '/user/subscription/payment?success=true&agency=False&session_id={CHECKOUT_SESSION_ID}' - cancel_url = f'{URL_SCHEME}://{FRONTEND_ADDRESS}:{FRONTEND_PORT}' + \ - '/user/subscription/payment?success=false&user_group=False' + success_url = ( + f"{URL_SCHEME}://{FRONTEND_ADDRESS}:{FRONTEND_PORT}" + + "/user/subscription/payment?success=true&agency=False&session_id={CHECKOUT_SESSION_ID}" + ) + cancel_url = ( + f"{URL_SCHEME}://{FRONTEND_ADDRESS}:{FRONTEND_PORT}" + + "/user/subscription/payment?success=false&user_group=False" + ) checkout_session = stripe.checkout.Session.create( line_items=[ - { - 'price': PRICE_ID, - 'quantity': 1 - } if not prorated else - { - 'price': PRICE_ID, - } + ( + {"price": PRICE_ID, "quantity": 1} + if not prorated + else { + "price": PRICE_ID, + } + ) ], - mode='subscription', - payment_method_types=['card'], + mode="subscription", + payment_method_types=["card"], success_url=success_url, cancel_url=cancel_url, ) return Response({"url": checkout_session.url}) except Exception as e: logging.error(str(e)) - return Response({ - 'error': str(e) - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return Response( + {"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) -@ api_view(['POST']) -@ csrf_exempt +@api_view(["POST"]) +@csrf_exempt def stripe_webhook_view(request): payload = request.body - sig_header = request.META['HTTP_STRIPE_SIGNATURE'] + sig_header = request.META["HTTP_STRIPE_SIGNATURE"] event = None try: @@ -133,12 +153,12 @@ def stripe_webhook_view(request): # Invalid signature return Response(status=401) - if event['type'] == 'customer.subscription.created': - subscription = event['data']['object'] + if event["type"] == "customer.subscription.created": + subscription = event["data"]["object"] # Get the Invoice object from the Subscription object - invoice = stripe.Invoice.retrieve(subscription['latest_invoice']) + invoice = stripe.Invoice.retrieve(subscription["latest_invoice"]) # Get the Charge object from the Invoice object - charge = stripe.Charge.retrieve(invoice['charge']) + charge = stripe.Charge.retrieve(invoice["charge"]) # Get paying user customer = stripe.Customer.retrieve(subscription["customer"]) @@ -146,18 +166,20 @@ def stripe_webhook_view(request): product = subscription["items"]["data"][0] SUBSCRIPTION_PLAN = SubscriptionPlan.objects.get( - stripe_product_id=product["plan"]["product"]) + stripe_product_id=product["plan"]["product"] + ) SUBSCRIPTION = UserSubscription.objects.create( subscription=SUBSCRIPTION_PLAN, annual=product["plan"]["interval"] == "year", valid=True, user=USER, - stripe_id=subscription['id']) + stripe_id=subscription["id"], + ) email = SubscriptionAvailedEmail() paid = { - "amount": charge['amount']/100, - "currency": str(charge['currency']).upper() + "amount": charge["amount"] / 100, + "currency": str(charge["currency"]).upper(), } email.context = { @@ -169,19 +191,20 @@ def stripe_webhook_view(request): email.send(to=[customer.email]) # Clear cache - cache.delete(f'billing_user:{USER.id}') - cache.delete(f'subscriptions_user:{USER.id}') + cache.delete(f"billing_user:{USER.id}") + cache.delete(f"subscriptions_user:{USER.id}") # On chargebacks/refunds, invalidate the subscription - elif event['type'] == 'charge.refunded': - charge = event['data']['object'] + elif event["type"] == "charge.refunded": + charge = event["data"]["object"] # Get the Invoice object from the Charge object - invoice = stripe.Invoice.retrieve(charge['invoice']) + invoice = stripe.Invoice.retrieve(charge["invoice"]) # Check if the subscription exists SUBSCRIPTION = UserSubscription.objects.filter( - stripe_id=invoice['subscription']).first() + stripe_id=invoice["subscription"] + ).first() if not (SUBSCRIPTION): return HttpResponse(status=404) @@ -196,8 +219,8 @@ def stripe_webhook_view(request): SUBSCRIPTION_PLAN = SUBSCRIPTION.subscription refund = { - "amount": charge['amount_refunded']/100, - "currency": str(charge['currency']).upper() + "amount": charge["amount_refunded"] / 100, + "currency": str(charge["currency"]).upper(), } # Send an email @@ -206,13 +229,13 @@ def stripe_webhook_view(request): email.context = { "user": USER, "subscription_plan": SUBSCRIPTION_PLAN, - "refund": refund + "refund": refund, } email.send(to=[USER.email]) # Clear cache - cache.delete(f'billing_user:{USER.id}') + cache.delete(f"billing_user:{USER.id}") elif SUBSCRIPTION.user_group: OWNER = SUBSCRIPTION.user_group.owner @@ -223,8 +246,8 @@ def stripe_webhook_view(request): SUBSCRIPTION_PLAN = SUBSCRIPTION.subscription refund = { - "amount": charge['amount_refunded']/100, - "currency": str(charge['currency']).upper() + "amount": charge["amount_refunded"] / 100, + "currency": str(charge["currency"]).upper(), } # Send en email @@ -233,36 +256,38 @@ def stripe_webhook_view(request): email.context = { "user": OWNER, "subscription_plan": SUBSCRIPTION_PLAN, - "refund": refund + "refund": refund, } email.send(to=[OWNER.email]) # Clear cache - cache.delete(f'billing_user:{USER.id}') - cache.delete(f'subscriptions_user:{USER.id}') + cache.delete(f"billing_user:{USER.id}") + cache.delete(f"subscriptions_user:{USER.id}") - elif event['type'] == 'customer.subscription.updated': - subscription = event['data']['object'] + elif event["type"] == "customer.subscription.updated": + subscription = event["data"]["object"] # Check if the subscription exists SUBSCRIPTION = UserSubscription.objects.filter( - stripe_id=subscription['id']).first() + stripe_id=subscription["id"] + ).first() if not (SUBSCRIPTION): return HttpResponse(status=404) # Check if a subscription has been upgraded/downgraded - new_stripe_product_id = subscription['items']['data'][0]['plan']['product'] + new_stripe_product_id = subscription["items"]["data"][0]["plan"]["product"] current_stripe_product_id = SUBSCRIPTION.subscription.stripe_product_id if new_stripe_product_id != current_stripe_product_id: SUBSCRIPTION_PLAN = SubscriptionPlan.objects.get( - stripe_product_id=new_stripe_product_id) + stripe_product_id=new_stripe_product_id + ) SUBSCRIPTION.subscription = SUBSCRIPTION_PLAN SUBSCRIPTION.save() # TODO: Add a plan upgraded email message here # Subscription activation/reactivation - if subscription['status'] == 'active': + if subscription["status"] == "active": SUBSCRIPTION.valid = True SUBSCRIPTION.save() @@ -270,26 +295,24 @@ def stripe_webhook_view(request): USER = SUBSCRIPTION.user # Clear cache - cache.delete(f'billing_user:{USER.id}') - cache.delete( - f'subscriptions_user:{USER.id}') + cache.delete(f"billing_user:{USER.id}") + cache.delete(f"subscriptions_user:{USER.id}") elif SUBSCRIPTION.user_group: OWNER = SUBSCRIPTION.user_group.owner # Clear cache - cache.delete(f'billing_user:{OWNER.id}') - cache.delete( - f'subscriptions_usergroup:{SUBSCRIPTION.user_group.id}') + cache.delete(f"billing_user:{OWNER.id}") + cache.delete(f"subscriptions_usergroup:{SUBSCRIPTION.user_group.id}") # TODO: Add notification here to inform users if their plan has been reactivated - elif subscription['status'] == 'past_due': + elif subscription["status"] == "past_due": # TODO: Add notification here to inform users if their payment method for an existing subscription payment is failing pass # If subscriptions get cancelled due to non-payment, invalidate the UserSubscription - elif subscription['status'] == 'cancelled': + elif subscription["status"] == "cancelled": if SUBSCRIPTION.user: USER = SUBSCRIPTION.user @@ -310,8 +333,8 @@ def stripe_webhook_view(request): email.send(to=[USER.email]) # Clear cache - cache.delete(f'billing_user:{USER.id}') - cache.delete(f'subscriptions_user:{USER.id}') + cache.delete(f"billing_user:{USER.id}") + cache.delete(f"subscriptions_user:{USER.id}") elif SUBSCRIPTION.user_group: OWNER = SUBSCRIPTION.user_group.owner @@ -325,24 +348,21 @@ def stripe_webhook_view(request): SUBSCRIPTION_PLAN = SUBSCRIPTION.subscription - email.context = { - "user": OWNER, - "subscription_plan": SUBSCRIPTION_PLAN - } + email.context = {"user": OWNER, "subscription_plan": SUBSCRIPTION_PLAN} email.send(to=[OWNER.email]) # Clear cache - cache.delete(f'billing_user:{OWNER.id}') - cache.delete( - f'subscriptions_usergroup:{SUBSCRIPTION.user_group.id}') + cache.delete(f"billing_user:{OWNER.id}") + cache.delete(f"subscriptions_usergroup:{SUBSCRIPTION.user_group.id}") # If a subscription gets cancelled, invalidate it - elif event['type'] == 'customer.subscription.deleted': - subscription = event['data']['object'] + elif event["type"] == "customer.subscription.deleted": + subscription = event["data"]["object"] # Check if the subscription exists SUBSCRIPTION = UserSubscription.objects.filter( - stripe_id=subscription['id']).first() + stripe_id=subscription["id"] + ).first() if not (SUBSCRIPTION): return HttpResponse(status=404) @@ -367,7 +387,7 @@ def stripe_webhook_view(request): email.send(to=[USER.email]) # Clear cache - cache.delete(f'billing_user:{USER.id}') + cache.delete(f"billing_user:{USER.id}") elif SUBSCRIPTION.user_group: OWNER = SUBSCRIPTION.user_group.owner @@ -381,14 +401,11 @@ def stripe_webhook_view(request): SUBSCRIPTION_PLAN = SUBSCRIPTION.subscription - email.context = { - "user": OWNER, - "subscription_plan": SUBSCRIPTION_PLAN - } + email.context = {"user": OWNER, "subscription_plan": SUBSCRIPTION_PLAN} email.send(to=[OWNER.email]) # Clear cache - cache.delete(f'billing_user:{OWNER.id}') + cache.delete(f"billing_user:{OWNER.id}") # Passed signature verification return HttpResponse(status=200) diff --git a/backend/schema.yml b/backend/schema.yml index 7162675..158ce04 100644 --- a/backend/schema.yml +++ b/backend/schema.yml @@ -805,6 +805,9 @@ components: first_name: type: string maxLength: 150 + is_new: + type: string + readOnly: true last_name: type: string maxLength: 150 @@ -824,6 +827,7 @@ components: - group_member - group_owner - id + - is_new - user_group - username Notification: @@ -885,6 +889,9 @@ components: first_name: type: string maxLength: 150 + is_new: + type: string + readOnly: true last_name: type: string maxLength: 150 diff --git a/backend/search_results/admin.py b/backend/search_results/admin.py index fc50b24..b2415f9 100644 --- a/backend/search_results/admin.py +++ b/backend/search_results/admin.py @@ -7,12 +7,11 @@ from unfold.contrib.filters.admin import RangeDateFilter @admin.register(SearchResult) class SearchResultAdmin(ModelAdmin): model = SearchResult - search_fields = ('id', 'title', 'link') - list_display = ['id', 'title', 'timestamp'] + search_fields = ("id", "title", "link") + list_display = ["id", "title", "timestamp"] list_filter_submit = True - list_filter = (( - "timestamp", RangeDateFilter - ), ( - "timestamp", RangeDateFilter - ),) + list_filter = ( + ("timestamp", RangeDateFilter), + ("timestamp", RangeDateFilter), + ) diff --git a/backend/search_results/apps.py b/backend/search_results/apps.py index 0c558c0..1463bc3 100644 --- a/backend/search_results/apps.py +++ b/backend/search_results/apps.py @@ -2,5 +2,5 @@ from django.apps import AppConfig class SearchResultsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'search_results' + default_auto_field = "django.db.models.BigAutoField" + name = "search_results" diff --git a/backend/search_results/migrations/0001_initial.py b/backend/search_results/migrations/0001_initial.py index 101fa86..3083708 100644 --- a/backend/search_results/migrations/0001_initial.py +++ b/backend/search_results/migrations/0001_initial.py @@ -7,17 +7,24 @@ class Migration(migrations.Migration): initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='SearchResult', + name="SearchResult", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=1000)), - ('link', models.CharField(max_length=1000)), - ('timestamp', models.DateTimeField(auto_now_add=True)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=1000)), + ("link", models.CharField(max_length=1000)), + ("timestamp", models.DateTimeField(auto_now_add=True)), ], ), ] diff --git a/backend/search_results/tasks.py b/backend/search_results/tasks.py index 61ceb77..7c35008 100644 --- a/backend/search_results/tasks.py +++ b/backend/search_results/tasks.py @@ -1,16 +1,13 @@ - - from celery import shared_task from .models import SearchResult -@shared_task(autoretry_for=(Exception,), retry_kwargs={'max_retries': 0, 'countdown': 5}) +@shared_task( + autoretry_for=(Exception,), retry_kwargs={"max_retries": 0, "countdown": 5} +) def create_search_result(title, link): if SearchResult.objects.filter(title=title, link=link).exists(): - return ("SearchResult entry already exists") + return "SearchResult entry already exists" else: - SearchResult.objects.create( - title=title, - link=link - ) + SearchResult.objects.create(title=title, link=link) return f"Created new SearchResult entry titled: {title}" diff --git a/backend/subscriptions/admin.py b/backend/subscriptions/admin.py index 1bbade1..aafe040 100644 --- a/backend/subscriptions/admin.py +++ b/backend/subscriptions/admin.py @@ -6,10 +6,24 @@ from unfold.contrib.filters.admin import RangeDateFilter @admin.register(StripePrice) class StripePriceAdmin(ModelAdmin): - search_fields = ["id", "lookup_key", - "stripe_price_id","price","currency", "prorated", "annual"] - list_display = ["id", "lookup_key", - "stripe_price_id", "price", "currency", "prorated", "annual"] + search_fields = [ + "id", + "lookup_key", + "stripe_price_id", + "price", + "currency", + "prorated", + "annual", + ] + list_display = [ + "id", + "lookup_key", + "stripe_price_id", + "price", + "currency", + "prorated", + "annual", + ] @admin.register(SubscriptionPlan) @@ -21,9 +35,6 @@ class SubscriptionPlanAdmin(ModelAdmin): @admin.register(UserSubscription) class UserSubscriptionAdmin(ModelAdmin): list_filter_submit = True - list_filter = (( - "date", RangeDateFilter - ),) - list_display = ["id", "__str__", "valid", "annual", - "date"] + list_filter = (("date", RangeDateFilter),) + list_display = ["id", "__str__", "valid", "annual", "date"] search_fields = ["id", "date"] diff --git a/backend/subscriptions/apps.py b/backend/subscriptions/apps.py index 120d081..2ecbb71 100644 --- a/backend/subscriptions/apps.py +++ b/backend/subscriptions/apps.py @@ -2,8 +2,8 @@ from django.apps import AppConfig class SubscriptionConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'subscriptions' + default_auto_field = "django.db.models.BigAutoField" + name = "subscriptions" def ready(self): import subscriptions.signals diff --git a/backend/subscriptions/migrations/0001_initial.py b/backend/subscriptions/migrations/0001_initial.py index 46ddb84..4124b79 100644 --- a/backend/subscriptions/migrations/0001_initial.py +++ b/backend/subscriptions/migrations/0001_initial.py @@ -11,46 +11,118 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('user_groups', '0001_initial'), + ("user_groups", "0001_initial"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='StripePrice', + name="StripePrice", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('annual', models.BooleanField(default=False)), - ('stripe_price_id', models.CharField(max_length=100)), - ('price', models.DecimalField(decimal_places=2, default=0.0, max_digits=10)), - ('currency', models.CharField(max_length=20)), - ('lookup_key', models.CharField(blank=True, max_length=100, null=True)), - ('prorated', models.BooleanField(default=False)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("annual", models.BooleanField(default=False)), + ("stripe_price_id", models.CharField(max_length=100)), + ( + "price", + models.DecimalField(decimal_places=2, default=0.0, max_digits=10), + ), + ("currency", models.CharField(max_length=20)), + ("lookup_key", models.CharField(blank=True, max_length=100, null=True)), + ("prorated", models.BooleanField(default=False)), ], ), migrations.CreateModel( - name='SubscriptionPlan', + name="SubscriptionPlan", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100)), - ('description', models.TextField(max_length=1024, null=True)), - ('stripe_product_id', models.CharField(max_length=100)), - ('group_exclusive', models.BooleanField(default=False)), - ('annual_price', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='annual_plan', to='subscriptions.stripeprice')), - ('monthly_price', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='monthly_plan', to='subscriptions.stripeprice')), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=100)), + ("description", models.TextField(max_length=1024, null=True)), + ("stripe_product_id", models.CharField(max_length=100)), + ("group_exclusive", models.BooleanField(default=False)), + ( + "annual_price", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="annual_plan", + to="subscriptions.stripeprice", + ), + ), + ( + "monthly_price", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="monthly_plan", + to="subscriptions.stripeprice", + ), + ), ], ), migrations.CreateModel( - name='UserSubscription', + name="UserSubscription", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('stripe_id', models.CharField(max_length=100)), - ('date', models.DateTimeField(default=django.utils.timezone.now, editable=False)), - ('valid', models.BooleanField()), - ('annual', models.BooleanField()), - ('subscription', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='subscriptions.subscriptionplan')), - ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ('user_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='user_groups.usergroup')), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("stripe_id", models.CharField(max_length=100)), + ( + "date", + models.DateTimeField( + default=django.utils.timezone.now, editable=False + ), + ), + ("valid", models.BooleanField()), + ("annual", models.BooleanField()), + ( + "subscription", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="subscriptions.subscriptionplan", + ), + ), + ( + "user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "user_group", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="user_groups.usergroup", + ), + ), ], ), ] diff --git a/backend/subscriptions/models.py b/backend/subscriptions/models.py index 3147c50..871c2ea 100644 --- a/backend/subscriptions/models.py +++ b/backend/subscriptions/models.py @@ -1,4 +1,3 @@ - from django.db import models from accounts.models import CustomUser from user_groups.models import UserGroup @@ -25,9 +24,11 @@ class SubscriptionPlan(models.Model): description = models.TextField(max_length=1024, null=True) stripe_product_id = models.CharField(max_length=100) annual_price = models.ForeignKey( - StripePrice, on_delete=models.SET_NULL, related_name='annual_plan', null=True) + StripePrice, on_delete=models.SET_NULL, related_name="annual_plan", null=True + ) monthly_price = models.ForeignKey( - StripePrice, on_delete=models.SET_NULL, related_name='monthly_plan', null=True) + StripePrice, on_delete=models.SET_NULL, related_name="monthly_plan", null=True + ) group_exclusive = models.BooleanField(default=False) def __str__(self): @@ -39,11 +40,14 @@ class SubscriptionPlan(models.Model): class UserSubscription(models.Model): user = models.ForeignKey( - CustomUser, on_delete=models.CASCADE, blank=True, null=True) + CustomUser, on_delete=models.CASCADE, blank=True, null=True + ) user_group = models.ForeignKey( - UserGroup, on_delete=models.CASCADE, blank=True, null=True) + UserGroup, on_delete=models.CASCADE, blank=True, null=True + ) subscription = models.ForeignKey( - SubscriptionPlan, on_delete=models.SET_NULL, blank=True, null=True) + SubscriptionPlan, on_delete=models.SET_NULL, blank=True, null=True + ) stripe_id = models.CharField(max_length=100) date = models.DateTimeField(default=now, editable=False) valid = models.BooleanField() @@ -51,6 +55,6 @@ class UserSubscription(models.Model): def __str__(self): if self.user: - return f'Subscription {self.subscription.name} for {self.user}' + return f"Subscription {self.subscription.name} for {self.user}" else: - return f'Subscription {self.subscription.name} for {self.user_group}' + return f"Subscription {self.subscription.name} for {self.user_group}" diff --git a/backend/subscriptions/serializers.py b/backend/subscriptions/serializers.py index 973bbe0..00243b4 100644 --- a/backend/subscriptions/serializers.py +++ b/backend/subscriptions/serializers.py @@ -7,38 +7,46 @@ class SimpleStripePriceSerializer(serializers.ModelSerializer): class Meta: model = StripePrice - fields = ['price', 'currency', 'prorated'] + fields = ["price", "currency", "prorated"] class SubscriptionPlanSerializer(serializers.ModelSerializer): class Meta: model = SubscriptionPlan - fields = ['id', 'name', 'description', - 'annual_price', 'monthly_price', 'group_exclusive'] + fields = [ + "id", + "name", + "description", + "annual_price", + "monthly_price", + "group_exclusive", + ] def to_representation(self, instance): representation = super().to_representation(instance) - representation['annual_price'] = SimpleStripePriceSerializer( - instance.annual_price, many=False).data - representation['monthly_price'] = SimpleStripePriceSerializer( - instance.monthly_price, many=False).data + representation["annual_price"] = SimpleStripePriceSerializer( + instance.annual_price, many=False + ).data + representation["monthly_price"] = SimpleStripePriceSerializer( + instance.monthly_price, many=False + ).data return representation class UserSubscriptionSerializer(serializers.ModelSerializer): - date = serializers.DateTimeField( - format="%m-%d-%Y %I:%M %p", read_only=True) + date = serializers.DateTimeField(format="%m-%d-%Y %I:%M %p", read_only=True) class Meta: model = UserSubscription - fields = ['id', 'user', 'user_group', 'subscription', - 'date', 'valid', 'annual'] + fields = ["id", "user", "user_group", "subscription", "date", "valid", "annual"] def to_representation(self, instance): representation = super().to_representation(instance) - representation['user'] = SimpleCustomUserSerializer( - instance.user, many=False).data - representation['subscription'] = SubscriptionPlanSerializer( - instance.subscription, many=False).data + representation["user"] = SimpleCustomUserSerializer( + instance.user, many=False + ).data + representation["subscription"] = SubscriptionPlanSerializer( + instance.subscription, many=False + ).data return representation diff --git a/backend/subscriptions/signals.py b/backend/subscriptions/signals.py index e12006b..c47c738 100644 --- a/backend/subscriptions/signals.py +++ b/backend/subscriptions/signals.py @@ -4,6 +4,7 @@ from .models import UserSubscription, StripePrice, SubscriptionPlan from django.core.cache import cache from config.settings import STRIPE_SECRET_KEY import stripe + stripe.api_key = STRIPE_SECRET_KEY # Template for running actions after user have paid for a subscription @@ -12,7 +13,7 @@ stripe.api_key = STRIPE_SECRET_KEY @receiver(post_save, sender=SubscriptionPlan) def clear_cache_after_plan_updates(sender, instance, **kwargs): # Clear cache - cache.delete('subscriptionplans') + cache.delete("subscriptionplans") @receiver(post_save, sender=UserSubscription) @@ -25,8 +26,8 @@ def scan_after_payment(sender, instance, **kwargs): @receiver(post_migrate) def create_subscriptions(sender, **kwargs): - if sender.name == 'subscriptions': - print('Importing data from Stripe') + if sender.name == "subscriptions": + print("Importing data from Stripe") created_prices = 0 created_plans = 0 skipped_prices = 0 @@ -35,16 +36,19 @@ def create_subscriptions(sender, **kwargs): prices = stripe.Price.list(expand=["data.tiers"], active=True) # Create the StripePrice - for price in prices['data']: - annual = (price['recurring']['interval'] == - 'year') if price['recurring'] else False + for price in prices["data"]: + annual = ( + (price["recurring"]["interval"] == "year") + if price["recurring"] + else False + ) STRIPE_PRICE, CREATED = StripePrice.objects.get_or_create( - stripe_price_id=price['id'], - price=price['unit_amount'] / 100, + stripe_price_id=price["id"], + price=price["unit_amount"] / 100, annual=annual, - lookup_key=price['lookup_key'], - prorated=price['recurring']['usage_type'] == 'metered', - currency=price['currency'] + lookup_key=price["lookup_key"], + prorated=price["recurring"]["usage_type"] == "metered", + currency=price["currency"], ) if CREATED: created_prices += 1 @@ -52,13 +56,13 @@ def create_subscriptions(sender, **kwargs): skipped_prices += 1 # Create the SubscriptionPlan - for product in products['data']: + for product in products["data"]: ANNUAL_PRICE = None MONTHLY_PRICE = None - for price in prices['data']: - if price['product'] == product['id']: + for price in prices["data"]: + if price["product"] == product["id"]: STRIPE_PRICE = StripePrice.objects.get( - stripe_price_id=price['id'], + stripe_price_id=price["id"], ) if STRIPE_PRICE.annual: ANNUAL_PRICE = STRIPE_PRICE @@ -66,12 +70,12 @@ def create_subscriptions(sender, **kwargs): MONTHLY_PRICE = STRIPE_PRICE if ANNUAL_PRICE or MONTHLY_PRICE: SUBSCRIPTION_PLAN, CREATED = SubscriptionPlan.objects.get_or_create( - name=product['name'], - description=product['description'], - stripe_product_id=product['id'], + name=product["name"], + description=product["description"], + stripe_product_id=product["id"], annual_price=ANNUAL_PRICE, monthly_price=MONTHLY_PRICE, - group_exclusive=product['metadata']['group_exclusive'] == 'True' + group_exclusive=product["metadata"]["group_exclusive"] == "True", ) if CREATED: created_plans += 1 @@ -79,13 +83,12 @@ def create_subscriptions(sender, **kwargs): skipped_plans += 1 # Skip over plans with missing pricing rates else: - print('Skipping plan' + - product['name'] + 'with missing pricing data') + print("Skipping plan" + product["name"] + "with missing pricing data") # Assign the StripePrice to the SubscriptionPlan SUBSCRIPTION_PLAN.save() - print('Created', created_plans, 'new plans') - print('Skipped', skipped_plans, 'existing plans') - print('Created', created_prices, 'new prices') - print('Skipped', skipped_prices, 'existing prices') + print("Created", created_plans, "new plans") + print("Skipped", skipped_plans, "existing plans") + print("Created", created_prices, "new prices") + print("Skipped", skipped_prices, "existing prices") diff --git a/backend/subscriptions/tasks.py b/backend/subscriptions/tasks.py index e50fa6a..a7f8d1f 100644 --- a/backend/subscriptions/tasks.py +++ b/backend/subscriptions/tasks.py @@ -12,10 +12,10 @@ def get_user_subscription(user_id): active_subscriptions = None if USER.user_group: active_subscriptions = UserSubscription.objects.filter( - user_group=USER.user_group, valid=True) + user_group=USER.user_group, valid=True + ) else: - active_subscriptions = UserSubscription.objects.filter( - user=USER, valid=True) + active_subscriptions = UserSubscription.objects.filter(user=USER, valid=True) # Return first valid subscription if there is one if len(active_subscriptions) > 0: @@ -33,7 +33,8 @@ def get_user_group_subscription(user_group): # Get a list of subscriptions for the specified user active_subscriptions = None active_subscriptions = UserSubscription.objects.filter( - user_group=USER_GROUP, valid=True) + user_group=USER_GROUP, valid=True + ) # Return first valid subscription if there is one if len(active_subscriptions) > 0: diff --git a/backend/subscriptions/urls.py b/backend/subscriptions/urls.py index 0a8afe1..5dbbb5a 100644 --- a/backend/subscriptions/urls.py +++ b/backend/subscriptions/urls.py @@ -3,12 +3,11 @@ from subscriptions import views from rest_framework.routers import DefaultRouter router = DefaultRouter() -router.register(r'plans', views.SubscriptionPlanViewset, - basename="Subscription Plans") -router.register(r'self', views.UserSubscriptionViewset, - basename="Self Subscriptions") -router.register(r'user_group', views.UserGroupSubscriptionViewet, - basename="Group Subscriptions") +router.register(r"plans", views.SubscriptionPlanViewset, basename="Subscription Plans") +router.register(r"self", views.UserSubscriptionViewset, basename="Self Subscriptions") +router.register( + r"user_group", views.UserGroupSubscriptionViewet, basename="Group Subscriptions" +) urlpatterns = [ - path('', include(router.urls)), + path("", include(router.urls)), ] diff --git a/backend/subscriptions/views.py b/backend/subscriptions/views.py index 4cfe9aa..49b3e2d 100644 --- a/backend/subscriptions/views.py +++ b/backend/subscriptions/views.py @@ -1,4 +1,7 @@ -from subscriptions.serializers import SubscriptionPlanSerializer, UserSubscriptionSerializer +from subscriptions.serializers import ( + SubscriptionPlanSerializer, + UserSubscriptionSerializer, +) from subscriptions.models import SubscriptionPlan, UserSubscription from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework import viewsets @@ -6,38 +9,38 @@ from django.core.cache import cache class SubscriptionPlanViewset(viewsets.ModelViewSet): - http_method_names = ['get'] + http_method_names = ["get"] serializer_class = SubscriptionPlanSerializer permission_classes = [AllowAny] queryset = SubscriptionPlan.objects.all() def get_queryset(self): - key = 'subscriptionplans' + key = "subscriptionplans" queryset = cache.get(key) if not queryset: queryset = super().get_queryset() - cache.set(key, queryset, 60*60) + cache.set(key, queryset, 60 * 60) return queryset class UserSubscriptionViewset(viewsets.ModelViewSet): - http_method_names = ['get'] + http_method_names = ["get"] serializer_class = UserSubscriptionSerializer permission_classes = [IsAuthenticated] queryset = UserSubscription.objects.all() def get_queryset(self): user = self.request.user - key = f'subscriptions_user:{user.id}' + key = f"subscriptions_user:{user.id}" queryset = cache.get(key) if not queryset: queryset = UserSubscription.objects.filter(user=user) - cache.set(key, queryset, 60*60) + cache.set(key, queryset, 60 * 60) return queryset class UserGroupSubscriptionViewet(viewsets.ModelViewSet): - http_method_names = ['get'] + http_method_names = ["get"] serializer_class = UserSubscriptionSerializer permission_classes = [IsAuthenticated] queryset = UserSubscription.objects.all() @@ -47,10 +50,9 @@ class UserGroupSubscriptionViewet(viewsets.ModelViewSet): if not user.user_group: return UserSubscription.objects.none() else: - key = f'subscriptions_usergroup:{user.user_group.id}' + key = f"subscriptions_usergroup:{user.user_group.id}" queryset = cache.get(key) if not cache: - queryset = UserSubscription.objects.filter( - user_group=user.user_group) - cache.set(key, queryset, 60*60) + queryset = UserSubscription.objects.filter(user_group=user.user_group) + cache.set(key, queryset, 60 * 60) return queryset diff --git a/backend/user_groups/admin.py b/backend/user_groups/admin.py index 76ed323..54a8f8b 100644 --- a/backend/user_groups/admin.py +++ b/backend/user_groups/admin.py @@ -7,9 +7,7 @@ from unfold.contrib.filters.admin import RangeDateFilter @admin.register(UserGroup) class UserGroupAdmin(ModelAdmin): list_filter_submit = True - list_filter = (( - "date_created", RangeDateFilter - ),) + list_filter = (("date_created", RangeDateFilter),) - list_display = ['id', 'name'] - search_fields = ['id', 'name'] + list_display = ["id", "name"] + search_fields = ["id", "name"] diff --git a/backend/user_groups/migrations/0001_initial.py b/backend/user_groups/migrations/0001_initial.py index d0d63ea..9ace028 100644 --- a/backend/user_groups/migrations/0001_initial.py +++ b/backend/user_groups/migrations/0001_initial.py @@ -8,16 +8,28 @@ class Migration(migrations.Migration): initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='UserGroup', + name="UserGroup", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=128)), - ('date_created', models.DateTimeField(default=django.utils.timezone.now, editable=False)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=128)), + ( + "date_created", + models.DateTimeField( + default=django.utils.timezone.now, editable=False + ), + ), ], ), ] diff --git a/backend/user_groups/migrations/0002_usergroup_managers_usergroup_members_usergroup_owner.py b/backend/user_groups/migrations/0002_usergroup_managers_usergroup_members_usergroup_owner.py index a77e36e..6cee9c2 100644 --- a/backend/user_groups/migrations/0002_usergroup_managers_usergroup_members_usergroup_owner.py +++ b/backend/user_groups/migrations/0002_usergroup_managers_usergroup_members_usergroup_owner.py @@ -8,24 +8,33 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('user_groups', '0001_initial'), + ("user_groups", "0001_initial"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.AddField( - model_name='usergroup', - name='managers', - field=models.ManyToManyField(related_name='usergroup_managers', to=settings.AUTH_USER_MODEL), + model_name="usergroup", + name="managers", + field=models.ManyToManyField( + related_name="usergroup_managers", to=settings.AUTH_USER_MODEL + ), ), migrations.AddField( - model_name='usergroup', - name='members', - field=models.ManyToManyField(related_name='usergroup_members', to=settings.AUTH_USER_MODEL), + model_name="usergroup", + name="members", + field=models.ManyToManyField( + related_name="usergroup_members", to=settings.AUTH_USER_MODEL + ), ), migrations.AddField( - model_name='usergroup', - name='owner', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='usergroup_owner', to=settings.AUTH_USER_MODEL), + model_name="usergroup", + name="owner", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="usergroup_owner", + to=settings.AUTH_USER_MODEL, + ), ), ] diff --git a/backend/user_groups/models.py b/backend/user_groups/models.py index e62d767..4ecce67 100644 --- a/backend/user_groups/models.py +++ b/backend/user_groups/models.py @@ -2,17 +2,24 @@ from django.db import models from django.utils.timezone import now from config.settings import STRIPE_SECRET_KEY import stripe + stripe.api_key = STRIPE_SECRET_KEY class UserGroup(models.Model): name = models.CharField(max_length=128, null=False) owner = models.ForeignKey( - 'accounts.CustomUser', on_delete=models.SET_NULL, null=True, related_name='usergroup_owner') + "accounts.CustomUser", + on_delete=models.SET_NULL, + null=True, + related_name="usergroup_owner", + ) managers = models.ManyToManyField( - 'accounts.CustomUser', related_name='usergroup_managers') + "accounts.CustomUser", related_name="usergroup_managers" + ) members = models.ManyToManyField( - 'accounts.CustomUser', related_name='usergroup_members') + "accounts.CustomUser", related_name="usergroup_members" + ) date_created = models.DateTimeField(default=now, editable=False) # Derived from email of owner, may be used for billing diff --git a/backend/user_groups/serializers.py b/backend/user_groups/serializers.py index a1d105f..b82a0ea 100644 --- a/backend/user_groups/serializers.py +++ b/backend/user_groups/serializers.py @@ -3,10 +3,9 @@ from .models import UserGroup class SimpleUserGroupSerializer(serializers.ModelSerializer): - date_created = serializers.DateTimeField( - format="%m-%d-%Y %I:%M %p", read_only=True) + date_created = serializers.DateTimeField(format="%m-%d-%Y %I:%M %p", read_only=True) class Meta: model = UserGroup - fields = ['id', 'name', 'date_created'] - read_only_fields = ['id', 'name', 'date_created'] + fields = ["id", "name", "date_created"] + read_only_fields = ["id", "name", "date_created"] diff --git a/backend/user_groups/signals.py b/backend/user_groups/signals.py index fc22c56..28e151e 100644 --- a/backend/user_groups/signals.py +++ b/backend/user_groups/signals.py @@ -8,15 +8,16 @@ from config.settings import STRIPE_SECRET_KEY, ROOT_DIR import os import json import stripe + stripe.api_key = STRIPE_SECRET_KEY @receiver(m2m_changed, sender=UserGroup.managers.through) def update_group_managers(sender, instance, action, **kwargs): # When adding new managers to a UserGroup, associate them with it - if action == 'post_add': + if action == "post_add": # Get the newly added managers - new_managers = kwargs.get('pk_set', set()) + new_managers = kwargs.get("pk_set", set()) for manager in new_managers: # Retrieve the member USER = CustomUser.objects.get(pk=manager) @@ -27,8 +28,8 @@ def update_group_managers(sender, instance, action, **kwargs): if USER not in instance.members.all(): instance.members.add(USER) # When removing managers from a UserGroup, remove their association with it - elif action == 'post_remove': - for manager in kwargs['pk_set']: + elif action == "post_remove": + for manager in kwargs["pk_set"]: # Retrieve the manager USER = CustomUser.objects.get(pk=manager) if USER not in instance.members.all(): @@ -39,9 +40,9 @@ def update_group_managers(sender, instance, action, **kwargs): @receiver(m2m_changed, sender=UserGroup.members.through) def update_group_members(sender, instance, action, **kwargs): # When adding new members to a UserGroup, associate them with it - if action == 'post_add': + if action == "post_add": # Get the newly added members - new_members = kwargs.get('pk_set', set()) + new_members = kwargs.get("pk_set", set()) for member in new_members: # Retrieve the member USER = CustomUser.objects.get(pk=member) @@ -50,10 +51,13 @@ def update_group_members(sender, instance, action, **kwargs): USER.user_group = instance USER.save() # When removing members from a UserGroup, remove their association with it - elif action == 'post_remove': - for client in kwargs['pk_set']: + elif action == "post_remove": + for client in kwargs["pk_set"]: USER = CustomUser.objects.get(pk=client) - if USER not in instance.members.all() and USER not in instance.managers.all(): + if ( + USER not in instance.members.all() + and USER not in instance.managers.all() + ): USER.user_group = None USER.save() # Update usage records @@ -66,42 +70,42 @@ def update_group_members(sender, instance, action, **kwargs): stripe.SubscriptionItem.create_usage_record( SUBSCRIPTION_ITEM.stripe_id, quantity=len(instance.members.all()), - action="set" + action="set", ) except: print( - f'Warning: Unable to update usage record for SubscriptionGroup ID:{instance.id}') + f"Warning: Unable to update usage record for SubscriptionGroup ID:{instance.id}" + ) @receiver(post_migrate) def create_groups(sender, **kwargs): if sender.name == "agencies": - with open(os.path.join(ROOT_DIR, 'seed_data.json'), "r") as f: + with open(os.path.join(ROOT_DIR, "seed_data.json"), "r") as f: seed_data = json.loads(f.read()) - for user_group in seed_data['user_groups']: - OWNER = CustomUser.objects.filter( - email=user_group['owner']).first() + for user_group in seed_data["user_groups"]: + OWNER = CustomUser.objects.filter(email=user_group["owner"]).first() USER_GROUP, CREATED = UserGroup.objects.get_or_create( owner=OWNER, - agency_name=user_group['name'], + agency_name=user_group["name"], ) if CREATED: print(f"Created UserGroup {USER_GROUP.agency_name}") # Add managers - USERS = CustomUser.objects.filter( - email__in=user_group['managers']) + USERS = CustomUser.objects.filter(email__in=user_group["managers"]) for USER in USERS: if USER not in USER_GROUP.managers.all(): print( - f"Adding User {USER.full_name} as manager to UserGroup {USER_GROUP.agency_name}") + f"Adding User {USER.full_name} as manager to UserGroup {USER_GROUP.agency_name}" + ) USER_GROUP.managers.add(USER) # Add members - USERS = CustomUser.objects.filter( - email__in=user_group['members']) + USERS = CustomUser.objects.filter(email__in=user_group["members"]) for USER in USERS: if USER not in USER_GROUP.members.all(): print( - f"Adding User {USER.full_name} as member to UserGroup {USER_GROUP.agency_name}") + f"Adding User {USER.full_name} as member to UserGroup {USER_GROUP.agency_name}" + ) USER_GROUP.clients.add(USER) USER_GROUP.save() diff --git a/backend/webdriver/apps.py b/backend/webdriver/apps.py index 2df962c..e029e8e 100644 --- a/backend/webdriver/apps.py +++ b/backend/webdriver/apps.py @@ -2,5 +2,5 @@ from django.apps import AppConfig class EmailsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'webdriver' + default_auto_field = "django.db.models.BigAutoField" + name = "webdriver" diff --git a/backend/webdriver/tasks.py b/backend/webdriver/tasks.py index 33b390a..6bf0381 100644 --- a/backend/webdriver/tasks.py +++ b/backend/webdriver/tasks.py @@ -1,11 +1,19 @@ from celery import shared_task -from webdriver.utils import setup_webdriver, selenium_action_template, google_search, get_element, get_elements +from webdriver.utils import ( + setup_webdriver, + selenium_action_template, + google_search, + get_element, + get_elements, +) from selenium.webdriver.common.by import By from search_results.tasks import create_search_result # Task template -@shared_task(autoretry_for=(Exception,), retry_kwargs={'max_retries': 3, 'countdown': 5}) +@shared_task( + autoretry_for=(Exception,), retry_kwargs={"max_retries": 3, "countdown": 5} +) def sample_selenium_task(): driver = setup_webdriver(use_proxy=False, use_saved_session=False) @@ -18,27 +26,29 @@ def sample_selenium_task(): driver.close() driver.quit() + # Sample task to scrape Google for search results based on a keyword -@shared_task(autoretry_for=(Exception,), retry_kwargs={'max_retries': 3, 'countdown': 5}) +@shared_task( + autoretry_for=(Exception,), retry_kwargs={"max_retries": 3, "countdown": 5} +) def simple_google_search(): - driver = setup_webdriver(driver_type="firefox", - use_proxy=False, use_saved_session=False) + driver = setup_webdriver( + driver_type="firefox", use_proxy=False, use_saved_session=False + ) driver.get(f"https://google.com/") google_search(driver, search_term="cat blog posts") # Count number of Google search results - search_items = get_elements( - driver, "xpath", '//*[@id="search"]/div[1]/div[1]/*') + search_items = get_elements(driver, "xpath", '//*[@id="search"]/div[1]/div[1]/*') for item in search_items: - title = item.find_element(By.TAG_NAME, 'h3').text - link = item.find_element(By.TAG_NAME, 'a').get_attribute('href') + title = item.find_element(By.TAG_NAME, "h3").text + link = item.find_element(By.TAG_NAME, "a").get_attribute("href") - create_search_result.apply_async( - kwargs={"title": title, "link": link}) + create_search_result.apply_async(kwargs={"title": title, "link": link}) driver.close() driver.quit() diff --git a/backend/webdriver/utils.py b/backend/webdriver/utils.py index c300fda..fdee0da 100644 --- a/backend/webdriver/utils.py +++ b/backend/webdriver/utils.py @@ -1,6 +1,7 @@ """ Settings file to hold constants and functions """ + from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from config.settings import get_secret @@ -18,24 +19,26 @@ import os import random -def take_snapshot(driver, filename='dump.png'): +def take_snapshot(driver, filename="dump.png"): # Set window size required_width = driver.execute_script( - 'return document.body.parentNode.scrollWidth') + "return document.body.parentNode.scrollWidth" + ) required_height = driver.execute_script( - 'return document.body.parentNode.scrollHeight') - driver.set_window_size( - required_width, required_height+(required_height*0.05)) + "return document.body.parentNode.scrollHeight" + ) + driver.set_window_size(required_width, required_height + (required_height * 0.05)) # Take the snapshot - driver.find_element(By.TAG_NAME, - 'body').screenshot('/dumps/'+filename) # avoids any scrollbars - print('Snapshot saved') + driver.find_element(By.TAG_NAME, "body").screenshot( + "/dumps/" + filename + ) # avoids any scrollbars + print("Snapshot saved") -def dump_html(driver, filename='dump.html'): +def dump_html(driver, filename="dump.html"): # Save the page source to error.html - with open(('/dumps/'+filename), 'w', encoding='utf-8') as file: + with open(("/dumps/" + filename), "w", encoding="utf-8") as file: file.write(driver.page_source) @@ -44,83 +47,83 @@ def setup_webdriver(driver_type="chrome", use_proxy=True, use_saved_session=Fals if not USE_PROXY: use_proxy = False if use_proxy: - print('Running driver with proxy enabled') + print("Running driver with proxy enabled") else: - print('Running driver with proxy disabled') + print("Running driver with proxy disabled") if use_saved_session: - print('Running with saved session') + print("Running with saved session") else: - print('Running without using saved session') + print("Running without using saved session") if driver_type == "chrome": - print('Using Chrome driver') + print("Using Chrome driver") opts = uc.ChromeOptions() if use_saved_session: if os.path.exists("/tmp_chrome_profile"): - print('Existing Chrome ephemeral profile found') + print("Existing Chrome ephemeral profile found") else: - print('No existing Chrome ephemeral profile found') + print("No existing Chrome ephemeral profile found") os.system("mkdir /tmp_chrome_profile") - if os.path.exists('/chrome'): - print('Copying Chrome Profile to ephemeral directory') + if os.path.exists("/chrome"): + print("Copying Chrome Profile to ephemeral directory") # Flush any non-essential cache directories from the existing profile as they may balloon in size overtime - os.system( - 'rm -rf "/chrome/Selenium Profile/Code Cache/*"') + os.system('rm -rf "/chrome/Selenium Profile/Code Cache/*"') # Create a copy of the Chrome Profile os.system("cp -r /chrome/* /tmp_chrome_profile") try: # Remove some items related to file locks - os.remove('/tmp_chrome_profile/SingletonLock') - os.remove('/tmp_chrome_profile/SingletonSocket') - os.remove('/tmp_chrome_profile/SingletonLock') + os.remove("/tmp_chrome_profile/SingletonLock") + os.remove("/tmp_chrome_profile/SingletonSocket") + os.remove("/tmp_chrome_profile/SingletonLock") except: pass else: - print('No existing Chrome Profile found. Creating one from scratch') + print("No existing Chrome Profile found. Creating one from scratch") if use_saved_session: # Specify the user data directory - opts.add_argument(f'--user-data-dir=/tmp_chrome_profile') - opts.add_argument('--profile-directory=Selenium Profile') + opts.add_argument(f"--user-data-dir=/tmp_chrome_profile") + opts.add_argument("--profile-directory=Selenium Profile") # Set proxy if use_proxy: opts.add_argument( - f'--proxy-server=socks5://{get_secret("PROXY_IP")}:{get_secret("PROXY_PORT_IP_AUTH")}') + f'--proxy-server=socks5://{get_secret("PROXY_IP")}:{get_secret("PROXY_PORT_IP_AUTH")}' + ) opts.add_argument("--disable-extensions") - opts.add_argument('--disable-application-cache') + opts.add_argument("--disable-application-cache") opts.add_argument("--disable-setuid-sandbox") - opts.add_argument('--disable-dev-shm-usage') + opts.add_argument("--disable-dev-shm-usage") opts.add_argument("--disable-gpu") opts.add_argument("--no-sandbox") opts.add_argument("--headless=new") driver = uc.Chrome(options=opts) elif driver_type == "firefox": - print('Using firefox driver') + print("Using firefox driver") opts = FirefoxOptions() if use_saved_session: if not os.path.exists("/firefox"): - print('No profile found') + print("No profile found") os.makedirs("/firefox") else: - print('Existing profile found') + print("Existing profile found") # Specify a profile if it exists opts.profile = "/firefox" # Set proxy if use_proxy: - opts.set_preference('network.proxy.type', 1) - opts.set_preference('network.proxy.socks', - get_secret('PROXY_IP')) - opts.set_preference('network.proxy.socks_port', - int(get_secret('PROXY_PORT_IP_AUTH'))) - opts.set_preference('network.proxy.socks_remote_dns', False) + opts.set_preference("network.proxy.type", 1) + opts.set_preference("network.proxy.socks", get_secret("PROXY_IP")) + opts.set_preference( + "network.proxy.socks_port", int(get_secret("PROXY_PORT_IP_AUTH")) + ) + opts.set_preference("network.proxy.socks_remote_dns", False) - opts.add_argument('--disable-dev-shm-usage') + opts.add_argument("--disable-dev-shm-usage") opts.add_argument("--headless") opts.add_argument("--disable-gpu") driver = webdriver.Firefox(options=opts) @@ -128,13 +131,15 @@ def setup_webdriver(driver_type="chrome", use_proxy=True, use_saved_session=Fals driver.maximize_window() # Check if proxy is working - driver.get('https://api.ipify.org/') + driver.get("https://api.ipify.org/") body = WebDriverWait(driver, 10).until( - EC.presence_of_element_located((By.TAG_NAME, "body"))) + EC.presence_of_element_located((By.TAG_NAME, "body")) + ) ip_address = body.text - print(f'External IP: {ip_address}') + print(f"External IP: {ip_address}") return driver + # These are wrapper function for quickly automating multiple steps in webscraping (logins, button presses, text inputs, etc.) # Depending on your use case, you may have to opt out of using this @@ -151,10 +156,11 @@ def get_element(driver, by, key, hidden_element=False, timeout=8): wait = WebDriverWait(driver, timeout=timeout) if not hidden_element: element = wait.until( - EC.element_to_be_clickable((by, key)) and EC.visibility_of_element_located((by, key))) + EC.element_to_be_clickable((by, key)) + and EC.visibility_of_element_located((by, key)) + ) else: - element = wait.until(EC.presence_of_element_located( - (by, key))) + element = wait.until(EC.presence_of_element_located((by, key))) return element except Exception: dump_html(driver) @@ -173,13 +179,12 @@ def get_elements(driver, by, key, hidden_element=False, timeout=8): wait = WebDriverWait(driver, timeout=timeout) if hidden_element: - elements = wait.until( - EC.presence_of_all_elements_located((by, key))) + elements = wait.until(EC.presence_of_all_elements_located((by, key))) else: visible_elements = wait.until( - EC.visibility_of_any_elements_located((by, key))) - elements = [ - element for element in visible_elements if element.is_enabled()] + EC.visibility_of_any_elements_located((by, key)) + ) + elements = [element for element in visible_elements if element.is_enabled()] return elements except Exception: @@ -193,17 +198,22 @@ def get_elements(driver, by, key, hidden_element=False, timeout=8): def execute_selenium_elements(driver, timeout, elements): try: for index, element in enumerate(elements): - print('Waiting...') + print("Waiting...") # Element may have a keyword specified, check if that exists before running any actions if "keyword" in element: # Skip a step if the keyword does not exist - if element['keyword'] not in driver.page_source: + if element["keyword"] not in driver.page_source: print( - f'Keyword {element["keyword"]} does not exist. Skipping step: {index+1} - {element["name"]}') + f'Keyword {element["keyword"]} does not exist. Skipping step: {index+1} - {element["name"]}' + ) continue - elif element['keyword'] in driver.page_source and element['type'] == 'skip': + elif ( + element["keyword"] in driver.page_source + and element["type"] == "skip" + ): print( - f'Keyword {element["keyword"]} does exists. Stopping at step: {index+1} - {element["name"]}') + f'Keyword {element["keyword"]} does exists. Stopping at step: {index+1} - {element["name"]}' + ) break print(f'Step: {index+1} - {element["name"]}') # Revert to default iframe action @@ -217,31 +227,47 @@ def execute_selenium_elements(driver, timeout, elements): else: values = element["input"] if type(values) is list: - raise Exception( - 'Invalid input value specified for "callback" type') + raise Exception('Invalid input value specified for "callback" type') else: # For single input values - driver.execute_script( - f'onRecaptcha("{values}");') + driver.execute_script(f'onRecaptcha("{values}");') continue try: # Try to get default element if "hidden" in element: site_element = get_element( - driver, element["default"]["type"], element["default"]["key"], hidden_element=True, timeout=timeout) + driver, + element["default"]["type"], + element["default"]["key"], + hidden_element=True, + timeout=timeout, + ) else: site_element = get_element( - driver, element["default"]["type"], element["default"]["key"], timeout=timeout) + driver, + element["default"]["type"], + element["default"]["key"], + timeout=timeout, + ) except Exception as e: - print(f'Failed to find primary element') + print(f"Failed to find primary element") # If that fails, try to get the failover one - print('Trying to find legacy element') + print("Trying to find legacy element") if "hidden" in element: site_element = get_element( - driver, element["failover"]["type"], element["failover"]["key"], hidden_element=True, timeout=timeout) + driver, + element["failover"]["type"], + element["failover"]["key"], + hidden_element=True, + timeout=timeout, + ) else: site_element = get_element( - driver, element["failover"]["type"], element["failover"]["key"], timeout=timeout) + driver, + element["failover"]["type"], + element["failover"]["key"], + timeout=timeout, + ) # Clicking an element if element["type"] == "click": site_element.click() @@ -272,11 +298,13 @@ def execute_selenium_elements(driver, timeout, elements): values = element["input"] if type(values) is list: raise Exception( - 'Invalid input value specified for "input_replace" type') + 'Invalid input value specified for "input_replace" type' + ) else: # For single input values driver.execute_script( - f'arguments[0].value = "{values}";', site_element) + f'arguments[0].value = "{values}";', site_element + ) except Exception as e: take_snapshot(driver) dump_html(driver) @@ -285,30 +313,33 @@ def execute_selenium_elements(driver, timeout, elements): raise Exception(e) -def solve_captcha(site_key, url, retry_attempts=3, version='v2', enterprise=False, use_proxy=True): +def solve_captcha( + site_key, url, retry_attempts=3, version="v2", enterprise=False, use_proxy=True +): # Manual proxy override set via $ENV if not USE_PROXY: use_proxy = False if CAPTCHA_TESTING: - print('Initializing CAPTCHA solver in dummy mode') + print("Initializing CAPTCHA solver in dummy mode") code = random.randint() print("CAPTCHA Successful") return code elif use_proxy: - print('Using CAPTCHA solver with proxy') + print("Using CAPTCHA solver with proxy") else: - print('Using CAPTCHA solver without proxy') + print("Using CAPTCHA solver without proxy") captcha_params = { "url": url, "sitekey": site_key, "version": version, "enterprise": 1 if enterprise else 0, - "proxy": { - 'type': 'socks5', - 'uri': get_secret('PROXY_USER_AUTH') - } if use_proxy else None + "proxy": ( + {"type": "socks5", "uri": get_secret("PROXY_USER_AUTH")} + if use_proxy + else None + ), } # Keep retrying until max attempts is reached @@ -316,12 +347,12 @@ def solve_captcha(site_key, url, retry_attempts=3, version='v2', enterprise=Fals # Solver uses 2CAPTCHA by default solver = TwoCaptcha(get_secret("CAPTCHA_API_KEY")) try: - print('Waiting for CAPTCHA code...') + print("Waiting for CAPTCHA code...") code = solver.recaptcha(**captcha_params)["code"] print("CAPTCHA Successful") return code except Exception as e: - print(f'CAPTCHA Failed! {e}') + print(f"CAPTCHA Failed! {e}") raise Exception(f"CAPTCHA API Failed!") @@ -339,13 +370,12 @@ def save_browser_session(driver): # Copy over the profile once we finish logging in if isinstance(driver, webdriver.Firefox): # Copy process for Firefox - print('Updating saved Firefox profile') + print("Updating saved Firefox profile") # Get the current profile directory from about:support page driver.get("about:support") - box = get_element( - driver, "id", "profile-dir-box", timeout=4) + box = get_element(driver, "id", "profile-dir-box", timeout=4) temp_profile_path = os.path.join(os.getcwd(), box.text) - profile_path = '/firefox' + profile_path = "/firefox" # Create the command copy_command = "cp -r " + temp_profile_path + "/* " + profile_path # Copy over the Firefox profile @@ -353,13 +383,13 @@ def save_browser_session(driver): print("Firefox profile saved") elif isinstance(driver, uc.Chrome): # Copy the Chrome profile - print('Updating non-ephemeral Chrome profile') + print("Updating non-ephemeral Chrome profile") # Flush Code Cache again to speed up copy - os.system( - 'rm -rf "/tmp_chrome_profile/SimpleDMCA Profile/Code Cache/*"') + os.system('rm -rf "/tmp_chrome_profile/SimpleDMCA Profile/Code Cache/*"') if os.system("cp -r /tmp_chrome_profile/* /chrome"): print("Chrome profile saved") + # Sample function # Call this within a Celery task # TODO: Modify as needed to your needs @@ -370,7 +400,7 @@ def selenium_action_template(driver): info = { "sample_field1": "sample_data", "sample_field2": "sample_data", - "captcha_code": lambda: solve_captcha('SITE_KEY', 'SITE_URL') + "captcha_code": lambda: solve_captcha("SITE_KEY", "SITE_URL"), } elements = [ @@ -382,13 +412,10 @@ def selenium_action_template(driver): "default": { # See get_element() for possible selector types "type": "xpath", - "key": '' + "key": "", }, # If a site implements canary design releases, you can place the ID for the element in the old design here - "failover": { - "type": "xpath", - "key": '' - } + "failover": {"type": "xpath", "key": ""}, }, ] @@ -398,8 +425,8 @@ def selenium_action_template(driver): # Fill in final fstring values in elements for element in elements: - if 'input' in element and '{' in element['input']: - a = element['input'].strip('{}') + if "input" in element and "{" in element["input"]: + a = element["input"].strip("{}") if a in info: value = info[a] # Check if the value is a callable (a lambda function) and call it if so @@ -411,11 +438,12 @@ def selenium_action_template(driver): # Use the stored value value = site_form_values[a] # Replace the placeholder with the actual value - element['input'] = str(value) + element["input"] = str(value) # Execute the selenium actions execute_selenium_elements(driver, 8, elements) + # Sample task for Google search @@ -429,40 +457,28 @@ def google_search(driver, search_term): "name": "Type in search term", "type": "input", "input": "{search_term}", - "default": { - "type": "xpath", - "key": '//*[@id="APjFqb"]' - }, - "failover": { - "type": "xpath", - "key": '//*[@id="APjFqb"]' - } + "default": {"type": "xpath", "key": '//*[@id="APjFqb"]'}, + "failover": {"type": "xpath", "key": '//*[@id="APjFqb"]'}, }, { "name": "Press enter", "type": "input_enter", - "default": { - "type": "xpath", - "key": '//*[@id="APjFqb"]' - }, - "failover": { - "type": "xpath", - "key": '//*[@id="APjFqb"]' - } + "default": {"type": "xpath", "key": '//*[@id="APjFqb"]'}, + "failover": {"type": "xpath", "key": '//*[@id="APjFqb"]'}, }, ] site_form_values = {} for element in elements: - if 'input' in element and '{' in element['input']: - a = element['input'].strip('{}') + if "input" in element and "{" in element["input"]: + a = element["input"].strip("{}") if a in info: value = info[a] if callable(value): if a not in site_form_values: site_form_values[a] = value() value = site_form_values[a] - element['input'] = str(value) + element["input"] = str(value) execute_selenium_elements(driver, 8, elements) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 03ea04d..940893c 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -5,7 +5,7 @@ services: build: context: . dockerfile: Dockerfile - image: drf_template:latest + image: drf_template ports: - "${BACKEND_PORT}:${BACKEND_PORT}" environment: @@ -23,7 +23,7 @@ services: env_file: .env environment: - RUN_TYPE=worker - image: drf_template:latest + image: drf_template volumes: - .:/code - ./chrome:/chrome @@ -42,7 +42,7 @@ services: env_file: .env environment: - RUN_TYPE=beat - image: drf_template:latest + image: drf_template volumes: - .:/code depends_on: @@ -58,7 +58,7 @@ services: env_file: .env environment: - RUN_TYPE=monitor - image: drf_template:latest + image: drf_template ports: - "${CELERY_FLOWER_PORT}:5555" volumes: