diff --git a/.gitignore b/.gitignore index b4bd214..18410bf 100644 --- a/.gitignore +++ b/.gitignore @@ -65,6 +65,11 @@ local_settings.py db.sqlite3 db.sqlite3-journal +equipment_tracker/db.sqlite3 +equipment_tracker/.env +equipment_tracker/static/* +equipment_tracker/static/ + # Flask stuff: instance/ .webassets-cache diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9f5f54e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +# Use the official Python 3.11 image +# FROM --platform=arm64 python:3.11.4-bookworm +# ARG BUILDPLATFORM +FROM python:3.11.4-bookworm + +ENV PYTHONBUFFERED 1 + +# Create directory +RUN mkdir /code + +# Set the working directory to /code +WORKDIR /code + +# Mirror the current directory to the working directory for hotreloading +ADD . /code/ + +# Install pipenv +RUN pip install --no-cache-dir -r requirements.txt + +# Make migrations +RUN python equipment_tracker/manage.py makemigrations + +# Run custom migrate +RUN python equipment_tracker/manage.py migrate + +# Generate DRF Spectacular Documentation +RUN python equipment_tracker/manage.py spectacular --color --file equipment_tracker/schema.yml + +# Expose port 8000 for the web server +EXPOSE 8000 diff --git a/ERD/Backend_ERD.drawio.png b/ERD/Backend_ERD.drawio.png new file mode 100644 index 0000000..669f28e Binary files /dev/null and b/ERD/Backend_ERD.drawio.png differ diff --git a/Pipfile b/Pipfile index 1ca75d8..bd1aa02 100644 --- a/Pipfile +++ b/Pipfile @@ -10,6 +10,12 @@ python-dotenv = "*" whitenoise = "*" djoser = "*" django-cors-headers = "*" +drf-spectacular = {version = "*", extras = ["sidecar"]} +django-extra-fields = "*" +pillow = "*" +psycopg2 = "*" +django-simple-history = "*" +django-unfold = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 4857364..383503d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "78c49b7899d981508de476af5e9aa819ee18a8c6ecaa040961788004559f5cac" + "sha256": "0dcfc730d5d2c4983b2e6db83e9eeb630ebe9a0146c582c00d4dc4f7e76d74b2" }, "pipfile-spec": 6, "requires": { @@ -24,13 +24,21 @@ "markers": "python_version >= '3.7'", "version": "==3.7.2" }, + "attrs": { + "hashes": [ + "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", + "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015" + ], + "markers": "python_version >= '3.7'", + "version": "==23.1.0" + }, "certifi": { "hashes": [ - "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", - "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" + "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", + "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" ], "markers": "python_version >= '3.6'", - "version": "==2023.7.22" + "version": "==2023.11.17" }, "cffi": { "hashes": [ @@ -92,128 +100,128 @@ }, "charset-normalizer": { "hashes": [ - "sha256:06cf46bdff72f58645434d467bf5228080801298fbba19fe268a01b4534467f5", - "sha256:0c8c61fb505c7dad1d251c284e712d4e0372cef3b067f7ddf82a7fa82e1e9a93", - "sha256:10b8dd31e10f32410751b3430996f9807fc4d1587ca69772e2aa940a82ab571a", - "sha256:1171ef1fc5ab4693c5d151ae0fdad7f7349920eabbaca6271f95969fa0756c2d", - "sha256:17a866d61259c7de1bdadef418a37755050ddb4b922df8b356503234fff7932c", - "sha256:1d6bfc32a68bc0933819cfdfe45f9abc3cae3877e1d90aac7259d57e6e0f85b1", - "sha256:1ec937546cad86d0dce5396748bf392bb7b62a9eeb8c66efac60e947697f0e58", - "sha256:223b4d54561c01048f657fa6ce41461d5ad8ff128b9678cfe8b2ecd951e3f8a2", - "sha256:2465aa50c9299d615d757c1c888bc6fef384b7c4aec81c05a0172b4400f98557", - "sha256:28f512b9a33235545fbbdac6a330a510b63be278a50071a336afc1b78781b147", - "sha256:2c092be3885a1b7899cd85ce24acedc1034199d6fca1483fa2c3a35c86e43041", - "sha256:2c4c99f98fc3a1835af8179dcc9013f93594d0670e2fa80c83aa36346ee763d2", - "sha256:31445f38053476a0c4e6d12b047b08ced81e2c7c712e5a1ad97bc913256f91b2", - "sha256:31bbaba7218904d2eabecf4feec0d07469284e952a27400f23b6628439439fa7", - "sha256:34d95638ff3613849f473afc33f65c401a89f3b9528d0d213c7037c398a51296", - "sha256:352a88c3df0d1fa886562384b86f9a9e27563d4704ee0e9d56ec6fcd270ea690", - "sha256:39b70a6f88eebe239fa775190796d55a33cfb6d36b9ffdd37843f7c4c1b5dc67", - "sha256:3c66df3f41abee950d6638adc7eac4730a306b022570f71dd0bd6ba53503ab57", - "sha256:3f70fd716855cd3b855316b226a1ac8bdb3caf4f7ea96edcccc6f484217c9597", - "sha256:3f9bc2ce123637a60ebe819f9fccc614da1bcc05798bbbaf2dd4ec91f3e08846", - "sha256:3fb765362688821404ad6cf86772fc54993ec11577cd5a92ac44b4c2ba52155b", - "sha256:45f053a0ece92c734d874861ffe6e3cc92150e32136dd59ab1fb070575189c97", - "sha256:46fb9970aa5eeca547d7aa0de5d4b124a288b42eaefac677bde805013c95725c", - "sha256:4cb50a0335382aac15c31b61d8531bc9bb657cfd848b1d7158009472189f3d62", - "sha256:4e12f8ee80aa35e746230a2af83e81bd6b52daa92a8afaef4fea4a2ce9b9f4fa", - "sha256:4f3100d86dcd03c03f7e9c3fdb23d92e32abbca07e7c13ebd7ddfbcb06f5991f", - "sha256:4f6e2a839f83a6a76854d12dbebde50e4b1afa63e27761549d006fa53e9aa80e", - "sha256:4f861d94c2a450b974b86093c6c027888627b8082f1299dfd5a4bae8e2292821", - "sha256:501adc5eb6cd5f40a6f77fbd90e5ab915c8fd6e8c614af2db5561e16c600d6f3", - "sha256:520b7a142d2524f999447b3a0cf95115df81c4f33003c51a6ab637cbda9d0bf4", - "sha256:548eefad783ed787b38cb6f9a574bd8664468cc76d1538215d510a3cd41406cb", - "sha256:555fe186da0068d3354cdf4bbcbc609b0ecae4d04c921cc13e209eece7720727", - "sha256:55602981b2dbf8184c098bc10287e8c245e351cd4fdcad050bd7199d5a8bf514", - "sha256:58e875eb7016fd014c0eea46c6fa92b87b62c0cb31b9feae25cbbe62c919f54d", - "sha256:5a3580a4fdc4ac05f9e53c57f965e3594b2f99796231380adb2baaab96e22761", - "sha256:5b70bab78accbc672f50e878a5b73ca692f45f5b5e25c8066d748c09405e6a55", - "sha256:5ceca5876032362ae73b83347be8b5dbd2d1faf3358deb38c9c88776779b2e2f", - "sha256:61f1e3fb621f5420523abb71f5771a204b33c21d31e7d9d86881b2cffe92c47c", - "sha256:633968254f8d421e70f91c6ebe71ed0ab140220469cf87a9857e21c16687c034", - "sha256:63a6f59e2d01310f754c270e4a257426fe5a591dc487f1983b3bbe793cf6bac6", - "sha256:63accd11149c0f9a99e3bc095bbdb5a464862d77a7e309ad5938fbc8721235ae", - "sha256:6db3cfb9b4fcecb4390db154e75b49578c87a3b9979b40cdf90d7e4b945656e1", - "sha256:71ef3b9be10070360f289aea4838c784f8b851be3ba58cf796262b57775c2f14", - "sha256:7ae8e5142dcc7a49168f4055255dbcced01dc1714a90a21f87448dc8d90617d1", - "sha256:7b6cefa579e1237ce198619b76eaa148b71894fb0d6bcf9024460f9bf30fd228", - "sha256:800561453acdecedaac137bf09cd719c7a440b6800ec182f077bb8e7025fb708", - "sha256:82ca51ff0fc5b641a2d4e1cc8c5ff108699b7a56d7f3ad6f6da9dbb6f0145b48", - "sha256:851cf693fb3aaef71031237cd68699dded198657ec1e76a76eb8be58c03a5d1f", - "sha256:854cc74367180beb327ab9d00f964f6d91da06450b0855cbbb09187bcdb02de5", - "sha256:87071618d3d8ec8b186d53cb6e66955ef2a0e4fa63ccd3709c0c90ac5a43520f", - "sha256:871d045d6ccc181fd863a3cd66ee8e395523ebfbc57f85f91f035f50cee8e3d4", - "sha256:8aee051c89e13565c6bd366813c386939f8e928af93c29fda4af86d25b73d8f8", - "sha256:8af5a8917b8af42295e86b64903156b4f110a30dca5f3b5aedea123fbd638bff", - "sha256:8ec8ef42c6cd5856a7613dcd1eaf21e5573b2185263d87d27c8edcae33b62a61", - "sha256:91e43805ccafa0a91831f9cd5443aa34528c0c3f2cc48c4cb3d9a7721053874b", - "sha256:9505dc359edb6a330efcd2be825fdb73ee3e628d9010597aa1aee5aa63442e97", - "sha256:985c7965f62f6f32bf432e2681173db41336a9c2611693247069288bcb0c7f8b", - "sha256:9a74041ba0bfa9bc9b9bb2cd3238a6ab3b7618e759b41bd15b5f6ad958d17605", - "sha256:9edbe6a5bf8b56a4a84533ba2b2f489d0046e755c29616ef8830f9e7d9cf5728", - "sha256:a15c1fe6d26e83fd2e5972425a772cca158eae58b05d4a25a4e474c221053e2d", - "sha256:a66bcdf19c1a523e41b8e9d53d0cedbfbac2e93c649a2e9502cb26c014d0980c", - "sha256:ae4070f741f8d809075ef697877fd350ecf0b7c5837ed68738607ee0a2c572cf", - "sha256:ae55d592b02c4349525b6ed8f74c692509e5adffa842e582c0f861751701a673", - "sha256:b578cbe580e3b41ad17b1c428f382c814b32a6ce90f2d8e39e2e635d49e498d1", - "sha256:b891a2f68e09c5ef989007fac11476ed33c5c9994449a4e2c3386529d703dc8b", - "sha256:baec8148d6b8bd5cee1ae138ba658c71f5b03e0d69d5907703e3e1df96db5e41", - "sha256:bb06098d019766ca16fc915ecaa455c1f1cd594204e7f840cd6258237b5079a8", - "sha256:bc791ec3fd0c4309a753f95bb6c749ef0d8ea3aea91f07ee1cf06b7b02118f2f", - "sha256:bd28b31730f0e982ace8663d108e01199098432a30a4c410d06fe08fdb9e93f4", - "sha256:be4d9c2770044a59715eb57c1144dedea7c5d5ae80c68fb9959515037cde2008", - "sha256:c0c72d34e7de5604df0fde3644cc079feee5e55464967d10b24b1de268deceb9", - "sha256:c0e842112fe3f1a4ffcf64b06dc4c61a88441c2f02f373367f7b4c1aa9be2ad5", - "sha256:c15070ebf11b8b7fd1bfff7217e9324963c82dbdf6182ff7050519e350e7ad9f", - "sha256:c2000c54c395d9e5e44c99dc7c20a64dc371f777faf8bae4919ad3e99ce5253e", - "sha256:c30187840d36d0ba2893bc3271a36a517a717f9fd383a98e2697ee890a37c273", - "sha256:cb7cd68814308aade9d0c93c5bd2ade9f9441666f8ba5aa9c2d4b389cb5e2a45", - "sha256:cd805513198304026bd379d1d516afbf6c3c13f4382134a2c526b8b854da1c2e", - "sha256:d0bf89afcbcf4d1bb2652f6580e5e55a840fdf87384f6063c4a4f0c95e378656", - "sha256:d9137a876020661972ca6eec0766d81aef8a5627df628b664b234b73396e727e", - "sha256:dbd95e300367aa0827496fe75a1766d198d34385a58f97683fe6e07f89ca3e3c", - "sha256:dced27917823df984fe0c80a5c4ad75cf58df0fbfae890bc08004cd3888922a2", - "sha256:de0b4caa1c8a21394e8ce971997614a17648f94e1cd0640fbd6b4d14cab13a72", - "sha256:debb633f3f7856f95ad957d9b9c781f8e2c6303ef21724ec94bea2ce2fcbd056", - "sha256:e372d7dfd154009142631de2d316adad3cc1c36c32a38b16a4751ba78da2a397", - "sha256:ecd26be9f112c4f96718290c10f4caea6cc798459a3a76636b817a0ed7874e42", - "sha256:edc0202099ea1d82844316604e17d2b175044f9bcb6b398aab781eba957224bd", - "sha256:f194cce575e59ffe442c10a360182a986535fd90b57f7debfaa5c845c409ecc3", - "sha256:f5fb672c396d826ca16a022ac04c9dce74e00a1c344f6ad1a0fdc1ba1f332213", - "sha256:f6a02a3c7950cafaadcd46a226ad9e12fc9744652cc69f9e5534f98b47f3bbcf", - "sha256:fe81b35c33772e56f4b6cf62cf4aedc1762ef7162a31e6ac7fe5e40d0149eb67" + "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" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.1" + "version": "==3.3.2" }, "cryptography": { "hashes": [ - "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67", - "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311", - "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8", - "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13", - "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143", - "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f", - "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829", - "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd", - "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397", - "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac", - "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d", - "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a", - "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839", - "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e", - "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6", - "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9", - "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860", - "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca", - "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91", - "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d", - "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714", - "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb", - "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f" + "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960", + "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a", + "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc", + "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a", + "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf", + "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1", + "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39", + "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406", + "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a", + "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a", + "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c", + "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be", + "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15", + "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2", + "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d", + "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157", + "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003", + "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248", + "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a", + "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec", + "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309", + "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7", + "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d" ], "markers": "python_version >= '3.7'", - "version": "==41.0.4" + "version": "==41.0.7" }, "defusedxml": { "hashes": [ @@ -225,19 +233,34 @@ }, "django": { "hashes": [ - "sha256:08f41f468b63335aea0d904c5729e0250300f6a1907bf293a65499496cdbc68f", - "sha256:a64d2487cdb00ad7461434320ccc38e60af9c404773a2f95ab0093b4453a3215" + "sha256:8e0f1c2c2786b5c0e39fe1afce24c926040fad47c8ea8ad30aaf1188df29fc41", + "sha256:e1d37c51ad26186de355cbcec16613ebdabfa9689bbade9c538835205a8abbe9" ], "index": "pypi", - "version": "==4.2.6" + "version": "==4.2.7" }, "django-cors-headers": { "hashes": [ - "sha256:25aabc94d4837678c1edf442c7f68a5f5fd151f6767b0e0b01c61a2179d02711", - "sha256:bd36c7aea0d070e462f3383f0dc9ef717e5fdc2b10a99c98c285f16da84ffba2" + "sha256:0b1fd19297e37417fc9f835d39e45c8c642938ddba1acce0c1753d3edef04f36", + "sha256:0bf65ef45e606aff1994d35503e6b677c0b26cafff6506f8fd7187f3be840207" ], "index": "pypi", - "version": "==4.3.0" + "version": "==4.3.1" + }, + "django-extra-fields": { + "hashes": [ + "sha256:2334e914b346c0a19a7765bf0ff7895c46cf35d5f40315a68418f44b7ddbb33b" + ], + "index": "pypi", + "version": "==3.0.2" + }, + "django-simple-history": { + "hashes": [ + "sha256:19bd1a87e1e2eba34dfd43eab1fcf2da5752221f343232f2372b2121c7e3b97d", + "sha256:992dcca3cddc0b67b470fc91f77292e2d2a6010d37c9eac3536e9d80e8754032" + ], + "index": "pypi", + "version": "==3.4.0" }, "django-templated-mail": { "hashes": [ @@ -246,6 +269,14 @@ ], "version": "==1.1.1" }, + "django-unfold": { + "hashes": [ + "sha256:1863e7aea57812a29ed7230fc5c6fa338082b1e32d480720b426a20b4f23b8bd", + "sha256:27b170f1bf9e0c349d20bde53e5b90d9d02a73a8f786def86121c9d6c56dbe41" + ], + "index": "pypi", + "version": "==0.17.1" + }, "djangorestframework": { "hashes": [ "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8", @@ -264,19 +295,61 @@ }, "djoser": { "hashes": [ - "sha256:4aa48502df870c8b5f07109ad4a749cc881c37bb5efa85cf5462ea695a0dca8c", - "sha256:7b24718cdc51b4294b0abcf6bf0ead11aa3ca83652e351dfb04b7b8b15afa3b0" + "sha256:9deb831a1c8781ceff325699e1407b4e1be8b4588e87071621d88ba31c09349f", + "sha256:efb91ad61e4d5b8d664db029b5947df9d34078289ef2680a1ab665e047144b74" ], "index": "pypi", - "version": "==2.2.0" + "version": "==2.2.2" + }, + "drf-spectacular": { + "extras": [ + "sidecar" + ], + "hashes": [ + "sha256:aee55330a774ba8a9cbdb125714d1c9ee05a8aafd3ce3be8bfd26527649aeb44", + "sha256:c0002a820b11771fdbf37853deb371947caf0159d1afeeffe7598e964bc1db94" + ], + "index": "pypi", + "version": "==0.26.5" + }, + "drf-spectacular-sidecar": { + "hashes": [ + "sha256:3d042a6772512f4d238f0385d3430acf5f669f595fd0be2641fe6bbfb4c7b376", + "sha256:546a83c173589715e530fad211af60cbcda2db54eb9e0935d44251639332af6d" + ], + "version": "==2023.10.1" }, "idna": { "hashes": [ - "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", - "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", + "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" ], "markers": "python_version >= '3.5'", - "version": "==3.4" + "version": "==3.6" + }, + "inflection": { + "hashes": [ + "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417", + "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2" + ], + "markers": "python_version >= '3.5'", + "version": "==0.5.1" + }, + "jsonschema": { + "hashes": [ + "sha256:4f614fd46d8d61258610998997743ec5492a648b33cf478c1ddc23ed4598a5fa", + "sha256:ed6231f0429ecf966f5bc8dfef245998220549cbbcf140f913b7464c52c3b6b3" + ], + "markers": "python_version >= '3.8'", + "version": "==4.20.0" + }, + "jsonschema-specifications": { + "hashes": [ + "sha256:9472fc4fea474cd74bea4a2b190daeccb5a9e4db2ea80efcf7a1b582fc9a81b8", + "sha256:e74ba7c0a65e8cb49dc26837d6cfe576557084a8b423ed16a420984228104f93" + ], + "markers": "python_version >= '3.8'", + "version": "==2023.11.2" }, "oauthlib": { "hashes": [ @@ -286,6 +359,85 @@ "markers": "python_version >= '3.6'", "version": "==3.2.2" }, + "pillow": { + "hashes": [ + "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d", + "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de", + "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616", + "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839", + "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099", + "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a", + "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219", + "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106", + "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b", + "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412", + "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b", + "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7", + "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2", + "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7", + "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14", + "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f", + "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27", + "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57", + "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262", + "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28", + "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610", + "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172", + "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273", + "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e", + "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d", + "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818", + "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f", + "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9", + "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01", + "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7", + "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651", + "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312", + "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80", + "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666", + "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061", + "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b", + "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992", + "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593", + "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4", + "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db", + "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba", + "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd", + "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e", + "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212", + "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb", + "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2", + "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34", + "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256", + "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f", + "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2", + "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38", + "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996", + "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a", + "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793" + ], + "index": "pypi", + "version": "==10.1.0" + }, + "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" + ], + "index": "pypi", + "version": "==2.9.9" + }, "pycparser": { "hashes": [ "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", @@ -323,6 +475,70 @@ ], "version": "==2023.3.post1" }, + "pyyaml": { + "hashes": [ + "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", + "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", + "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", + "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", + "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", + "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", + "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", + "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", + "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", + "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", + "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", + "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", + "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", + "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", + "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", + "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", + "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", + "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", + "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", + "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", + "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", + "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", + "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", + "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", + "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", + "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", + "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", + "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", + "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", + "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", + "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", + "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", + "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", + "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", + "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", + "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", + "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", + "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", + "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", + "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", + "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", + "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", + "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", + "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", + "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", + "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", + "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", + "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", + "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", + "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" + ], + "markers": "python_version >= '3.6'", + "version": "==6.0.1" + }, + "referencing": { + "hashes": [ + "sha256:81a1471c68c9d5e3831c30ad1dd9815c45b558e596653db751a2bfdd17b3b9ec", + "sha256:c19c4d006f1757e3dd75c4f784d38f8698d87b649c54f9ace14e5e8c9667c01d" + ], + "markers": "python_version >= '3.8'", + "version": "==0.31.1" + }, "requests": { "hashes": [ "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", @@ -339,6 +555,111 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.3.1" }, + "rpds-py": { + "hashes": [ + "sha256:06d218e4464d31301e943b65b2c6919318ea6f69703a351961e1baaf60347276", + "sha256:12ecf89bd54734c3c2c79898ae2021dca42750c7bcfb67f8fb3315453738ac8f", + "sha256:15253fff410873ebf3cfba1cc686a37711efcd9b8cb30ea21bb14a973e393f60", + "sha256:188435794405c7f0573311747c85a96b63c954a5f2111b1df8018979eca0f2f0", + "sha256:1ceebd0ae4f3e9b2b6b553b51971921853ae4eebf3f54086be0565d59291e53d", + "sha256:244e173bb6d8f3b2f0c4d7370a1aa341f35da3e57ffd1798e5b2917b91731fd3", + "sha256:25b28b3d33ec0a78e944aaaed7e5e2a94ac811bcd68b557ca48a0c30f87497d2", + "sha256:25ea41635d22b2eb6326f58e608550e55d01df51b8a580ea7e75396bafbb28e9", + "sha256:29d311e44dd16d2434d5506d57ef4d7036544fc3c25c14b6992ef41f541b10fb", + "sha256:2a1472956c5bcc49fb0252b965239bffe801acc9394f8b7c1014ae9258e4572b", + "sha256:2a7bef6977043673750a88da064fd513f89505111014b4e00fbdd13329cd4e9a", + "sha256:2ac26f50736324beb0282c819668328d53fc38543fa61eeea2c32ea8ea6eab8d", + "sha256:2e72f750048b32d39e87fc85c225c50b2a6715034848dbb196bf3348aa761fa1", + "sha256:31e220a040b89a01505128c2f8a59ee74732f666439a03e65ccbf3824cdddae7", + "sha256:35f53c76a712e323c779ca39b9a81b13f219a8e3bc15f106ed1e1462d56fcfe9", + "sha256:38d4f822ee2f338febcc85aaa2547eb5ba31ba6ff68d10b8ec988929d23bb6b4", + "sha256:38f9bf2ad754b4a45b8210a6c732fe876b8a14e14d5992a8c4b7c1ef78740f53", + "sha256:3a44c8440183b43167fd1a0819e8356692bf5db1ad14ce140dbd40a1485f2dea", + "sha256:3ab96754d23372009638a402a1ed12a27711598dd49d8316a22597141962fe66", + "sha256:3c55d7f2d817183d43220738270efd3ce4e7a7b7cbdaefa6d551ed3d6ed89190", + "sha256:46e1ed994a0920f350a4547a38471217eb86f57377e9314fbaaa329b71b7dfe3", + "sha256:4a5375c5fff13f209527cd886dc75394f040c7d1ecad0a2cb0627f13ebe78a12", + "sha256:4c2d26aa03d877c9730bf005621c92da263523a1e99247590abbbe252ccb7824", + "sha256:4c4e314d36d4f31236a545696a480aa04ea170a0b021e9a59ab1ed94d4c3ef27", + "sha256:4d0c10d803549427f427085ed7aebc39832f6e818a011dcd8785e9c6a1ba9b3e", + "sha256:4dcc5ee1d0275cb78d443fdebd0241e58772a354a6d518b1d7af1580bbd2c4e8", + "sha256:51967a67ea0d7b9b5cd86036878e2d82c0b6183616961c26d825b8c994d4f2c8", + "sha256:530190eb0cd778363bbb7596612ded0bb9fef662daa98e9d92a0419ab27ae914", + "sha256:5379e49d7e80dca9811b36894493d1c1ecb4c57de05c36f5d0dd09982af20211", + "sha256:5493569f861fb7b05af6d048d00d773c6162415ae521b7010197c98810a14cab", + "sha256:5a4c1058cdae6237d97af272b326e5f78ee7ee3bbffa6b24b09db4d828810468", + "sha256:5d75d6d220d55cdced2f32cc22f599475dbe881229aeddba6c79c2e9df35a2b3", + "sha256:5d97e9ae94fb96df1ee3cb09ca376c34e8a122f36927230f4c8a97f469994bff", + "sha256:5feae2f9aa7270e2c071f488fab256d768e88e01b958f123a690f1cc3061a09c", + "sha256:603d5868f7419081d616dab7ac3cfa285296735e7350f7b1e4f548f6f953ee7d", + "sha256:61d42d2b08430854485135504f672c14d4fc644dd243a9c17e7c4e0faf5ed07e", + "sha256:61dbc1e01dc0c5875da2f7ae36d6e918dc1b8d2ce04e871793976594aad8a57a", + "sha256:65cfed9c807c27dee76407e8bb29e6f4e391e436774bcc769a037ff25ad8646e", + "sha256:67a429520e97621a763cf9b3ba27574779c4e96e49a27ff8a1aa99ee70beb28a", + "sha256:6aadae3042f8e6db3376d9e91f194c606c9a45273c170621d46128f35aef7cd0", + "sha256:6ba8858933f0c1a979781272a5f65646fca8c18c93c99c6ddb5513ad96fa54b1", + "sha256:6bc568b05e02cd612be53900c88aaa55012e744930ba2eeb56279db4c6676eb3", + "sha256:729408136ef8d45a28ee9a7411917c9e3459cf266c7e23c2f7d4bb8ef9e0da42", + "sha256:751758d9dd04d548ec679224cc00e3591f5ebf1ff159ed0d4aba6a0746352452", + "sha256:76d59d4d451ba77f08cb4cd9268dec07be5bc65f73666302dbb5061989b17198", + "sha256:79bf58c08f0756adba691d480b5a20e4ad23f33e1ae121584cf3a21717c36dfa", + "sha256:7de12b69d95072394998c622cfd7e8cea8f560db5fca6a62a148f902a1029f8b", + "sha256:7f55cd9cf1564b7b03f238e4c017ca4794c05b01a783e9291065cb2858d86ce4", + "sha256:80e5acb81cb49fd9f2d5c08f8b74ffff14ee73b10ca88297ab4619e946bcb1e1", + "sha256:87a90f5545fd61f6964e65eebde4dc3fa8660bb7d87adb01d4cf17e0a2b484ad", + "sha256:881df98f0a8404d32b6de0fd33e91c1b90ed1516a80d4d6dc69d414b8850474c", + "sha256:8a776a29b77fe0cc28fedfd87277b0d0f7aa930174b7e504d764e0b43a05f381", + "sha256:8c2a61c0e4811012b0ba9f6cdcb4437865df5d29eab5d6018ba13cee1c3064a0", + "sha256:8fa6bd071ec6d90f6e7baa66ae25820d57a8ab1b0a3c6d3edf1834d4b26fafa2", + "sha256:96f2975fb14f39c5fe75203f33dd3010fe37d1c4e33177feef1107b5ced750e3", + "sha256:96fb0899bb2ab353f42e5374c8f0789f54e0a94ef2f02b9ac7149c56622eaf31", + "sha256:97163a1ab265a1073a6372eca9f4eeb9f8c6327457a0b22ddfc4a17dcd613e74", + "sha256:9c95a1a290f9acf7a8f2ebbdd183e99215d491beea52d61aa2a7a7d2c618ddc6", + "sha256:9d94d78418203904730585efa71002286ac4c8ac0689d0eb61e3c465f9e608ff", + "sha256:a6ba2cb7d676e9415b9e9ac7e2aae401dc1b1e666943d1f7bc66223d3d73467b", + "sha256:aa0379c1935c44053c98826bc99ac95f3a5355675a297ac9ce0dfad0ce2d50ca", + "sha256:ac96d67b37f28e4b6ecf507c3405f52a40658c0a806dffde624a8fcb0314d5fd", + "sha256:ade2ccb937060c299ab0dfb2dea3d2ddf7e098ed63ee3d651ebfc2c8d1e8632a", + "sha256:aefbdc934115d2f9278f153952003ac52cd2650e7313750390b334518c589568", + "sha256:b07501b720cf060c5856f7b5626e75b8e353b5f98b9b354a21eb4bfa47e421b1", + "sha256:b5267feb19070bef34b8dea27e2b504ebd9d31748e3ecacb3a4101da6fcb255c", + "sha256:b5f6328e8e2ae8238fc767703ab7b95785521c42bb2b8790984e3477d7fa71ad", + "sha256:b8996ffb60c69f677245f5abdbcc623e9442bcc91ed81b6cd6187129ad1fa3e7", + "sha256:b981a370f8f41c4024c170b42fbe9e691ae2dbc19d1d99151a69e2c84a0d194d", + "sha256:b9d121be0217787a7d59a5c6195b0842d3f701007333426e5154bf72346aa658", + "sha256:bcef4f2d3dc603150421de85c916da19471f24d838c3c62a4f04c1eb511642c1", + "sha256:bed0252c85e21cf73d2d033643c945b460d6a02fc4a7d644e3b2d6f5f2956c64", + "sha256:bfdfbe6a36bc3059fff845d64c42f2644cf875c65f5005db54f90cdfdf1df815", + "sha256:c0095b8aa3e432e32d372e9a7737e65b58d5ed23b9620fea7cb81f17672f1fa1", + "sha256:c1f41d32a2ddc5a94df4b829b395916a4b7f103350fa76ba6de625fcb9e773ac", + "sha256:c45008ca79bad237cbc03c72bc5205e8c6f66403773929b1b50f7d84ef9e4d07", + "sha256:c82bbf7e03748417c3a88c1b0b291288ce3e4887a795a3addaa7a1cfd9e7153e", + "sha256:c918621ee0a3d1fe61c313f2489464f2ae3d13633e60f520a8002a5e910982ee", + "sha256:d204957169f0b3511fb95395a9da7d4490fb361763a9f8b32b345a7fe119cb45", + "sha256:d329896c40d9e1e5c7715c98529e4a188a1f2df51212fd65102b32465612b5dc", + "sha256:d3a61e928feddc458a55110f42f626a2a20bea942ccedb6fb4cee70b4830ed41", + "sha256:d48db29bd47814671afdd76c7652aefacc25cf96aad6daefa82d738ee87461e2", + "sha256:d5593855b5b2b73dd8413c3fdfa5d95b99d657658f947ba2c4318591e745d083", + "sha256:d79c159adea0f1f4617f54aa156568ac69968f9ef4d1e5fefffc0a180830308e", + "sha256:db09b98c7540df69d4b47218da3fbd7cb466db0fb932e971c321f1c76f155266", + "sha256:ddf23960cb42b69bce13045d5bc66f18c7d53774c66c13f24cf1b9c144ba3141", + "sha256:e06cfea0ece444571d24c18ed465bc93afb8c8d8d74422eb7026662f3d3f779b", + "sha256:e7c564c58cf8f248fe859a4f0fe501b050663f3d7fbc342172f259124fb59933", + "sha256:e86593bf8637659e6a6ed58854b6c87ec4e9e45ee8a4adfd936831cef55c2d21", + "sha256:eaffbd8814bb1b5dc3ea156a4c5928081ba50419f9175f4fc95269e040eff8f0", + "sha256:ee353bb51f648924926ed05e0122b6a0b1ae709396a80eb583449d5d477fcdf7", + "sha256:ee6faebb265e28920a6f23a7d4c362414b3f4bb30607141d718b991669e49ddc", + "sha256:efe093acc43e869348f6f2224df7f452eab63a2c60a6c6cd6b50fd35c4e075ba", + "sha256:f03a1b3a4c03e3e0161642ac5367f08479ab29972ea0ffcd4fa18f729cd2be0a", + "sha256:f0d320e70b6b2300ff6029e234e79fe44e9dbbfc7b98597ba28e054bd6606a57", + "sha256:f252dfb4852a527987a9156cbcae3022a30f86c9d26f4f17b8c967d7580d65d2", + "sha256:f5f4424cb87a20b016bfdc157ff48757b89d2cc426256961643d443c6c277007", + "sha256:f8eae66a1304de7368932b42d801c67969fd090ddb1a7a24f27b435ed4bed68f", + "sha256:fdb82eb60d31b0c033a8e8ee9f3fc7dfbaa042211131c29da29aea8531b4f18f" + ], + "markers": "python_version >= '3.8'", + "version": "==0.13.2" + }, "social-auth-app-django": { "hashes": [ "sha256:09ac02a063cb313eed5e9ef2f9ac4477c8bf5bbd685925ff3aba43f9072f1bbb", @@ -349,11 +670,11 @@ }, "social-auth-core": { "hashes": [ - "sha256:9791d7c7aee2ac8517fe7a2ea2f942a8a5492b3a4ccb44a9b0dacc87d182f2aa", - "sha256:ea7a19c46b791b767e95f467881b53c5fd0d1efb40048d9ed3dbc46daa05c954" + "sha256:307a4ba64d4f3ec86e4389163eac1d8b8656ffe5ab2e964aeff043ab00b3a662", + "sha256:54d0c598bf6ea0ec12bbcf78bee035c7cd604b5d781d80b7997e9e033c3ac05d" ], - "markers": "python_version >= '3.6'", - "version": "==4.4.2" + "markers": "python_version >= '3.8'", + "version": "==4.5.1" }, "sqlparse": { "hashes": [ @@ -371,13 +692,21 @@ "markers": "sys_platform == 'win32'", "version": "==2023.3" }, + "uritemplate": { + "hashes": [ + "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0", + "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e" + ], + "markers": "python_version >= '3.6'", + "version": "==4.1.1" + }, "urllib3": { "hashes": [ - "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84", - "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e" + "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3", + "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54" ], - "markers": "python_version >= '3.7'", - "version": "==2.0.7" + "markers": "python_version >= '3.8'", + "version": "==2.1.0" }, "whitenoise": { "hashes": [ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..94e1e99 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +version: "3.9" + +services: + # Django App + django_backend: + build: + context: . + dockerfile: Dockerfile + image: equipmenttracker_backend:latest + ports: + - "8094:8000" # Expose port 8094 for the web server + environment: + - PYTHONBUFFERED=1 + command: + [ + "sh", + "-c", + "python equipment_tracker/manage.py spectacular --color --file equipment_tracker/schema.yml && python equipment_tracker/manage.py collectstatic --noinput && python equipment_tracker/manage.py makemigrations && python equipment_tracker/manage.py migrate && python equipment_tracker/manage.py runserver 0.0.0.0:8000", + ] + volumes: + - .:/code # For hotreloading +volumes: + equipment_tracker: diff --git a/equipment_tracker/.env b/equipment_tracker/.env deleted file mode 100644 index 7996e3d..0000000 --- a/equipment_tracker/.env +++ /dev/null @@ -1,20 +0,0 @@ -# Django -SECRET_KEY = 'django-insecure-aorh!j+^*hmepp%&(cna!8)yeo!is)zly-^x*41jdi=(tl#v40' - -# Superuser Credentials -DJANGO_ADMIN_USERNAME = 'admin' -DJANGO_ADMIN_EMAIL = 'admin@admin.com' -DJANGO_ADMIN_PASSWORD = 'admin*(9125' - -# Production Email Credentials -PROD_EMAIL_HOST = "smtp.gmail.com" -PROD_EMAIL_HOST_USER = '' -PROD_EMAIL_HOST_PASSWORD = '' -PROD_EMAIL_PORT = '587' -PROD_EMAIL_TLS = 'True' - -# Dev Email Credentials -DEV_EMAIL_HOST = 'sandbox.smtp.mailtrap.io' -DEV_EMAIL_HOST_USER = '' -DEV_EMAIL_HOST_PASSWORD = '' -DEV_EMAIL_PORT = '2525' diff --git a/equipment_tracker/accounts/__init__.py b/equipment_tracker/accounts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/equipment_tracker/accounts/__pycache__/__init__.cpython-311.pyc b/equipment_tracker/accounts/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..3e9616e Binary files /dev/null and b/equipment_tracker/accounts/__pycache__/__init__.cpython-311.pyc differ diff --git a/equipment_tracker/accounts/__pycache__/admin.cpython-311.pyc b/equipment_tracker/accounts/__pycache__/admin.cpython-311.pyc new file mode 100644 index 0000000..d594b06 Binary files /dev/null and b/equipment_tracker/accounts/__pycache__/admin.cpython-311.pyc differ diff --git a/equipment_tracker/accounts/__pycache__/apps.cpython-311.pyc b/equipment_tracker/accounts/__pycache__/apps.cpython-311.pyc new file mode 100644 index 0000000..a2ff15c Binary files /dev/null and b/equipment_tracker/accounts/__pycache__/apps.cpython-311.pyc differ diff --git a/equipment_tracker/accounts/__pycache__/models.cpython-311.pyc b/equipment_tracker/accounts/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000..2e08770 Binary files /dev/null and b/equipment_tracker/accounts/__pycache__/models.cpython-311.pyc differ diff --git a/equipment_tracker/accounts/__pycache__/permissions.cpython-311.pyc b/equipment_tracker/accounts/__pycache__/permissions.cpython-311.pyc new file mode 100644 index 0000000..2a5017a Binary files /dev/null and b/equipment_tracker/accounts/__pycache__/permissions.cpython-311.pyc differ diff --git a/equipment_tracker/accounts/__pycache__/serializers.cpython-311.pyc b/equipment_tracker/accounts/__pycache__/serializers.cpython-311.pyc new file mode 100644 index 0000000..6cad693 Binary files /dev/null and b/equipment_tracker/accounts/__pycache__/serializers.cpython-311.pyc differ diff --git a/equipment_tracker/accounts/__pycache__/urls.cpython-311.pyc b/equipment_tracker/accounts/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000..fd784f6 Binary files /dev/null and b/equipment_tracker/accounts/__pycache__/urls.cpython-311.pyc differ diff --git a/equipment_tracker/accounts/admin.py b/equipment_tracker/accounts/admin.py new file mode 100644 index 0000000..eda007b --- /dev/null +++ b/equipment_tracker/accounts/admin.py @@ -0,0 +1,15 @@ +from django import forms +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from .models import CustomUser + + +class CustomUserAdmin(UserAdmin): + model = CustomUser + list_display = UserAdmin.list_display + ('is_technician',) + fieldsets = UserAdmin.fieldsets + ( + (None, {'fields': ('is_technician',)}), + ) + + +admin.site.register(CustomUser, CustomUserAdmin) diff --git a/equipment_tracker/accounts/apps.py b/equipment_tracker/accounts/apps.py new file mode 100644 index 0000000..3e3c765 --- /dev/null +++ b/equipment_tracker/accounts/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'accounts' diff --git a/equipment_tracker/accounts/migrations/0001_initial.py b/equipment_tracker/accounts/migrations/0001_initial.py new file mode 100644 index 0000000..6479e54 --- /dev/null +++ b/equipment_tracker/accounts/migrations/0001_initial.py @@ -0,0 +1,47 @@ +# Generated by Django 4.2.7 on 2023-12-02 12:25 + +import accounts.models +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + 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')), + ('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')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('first_name', models.CharField(max_length=100)), + ('last_name', models.CharField(max_length=100)), + ('is_active', models.BooleanField(default=False)), + ('is_technician', models.BooleanField(default=False)), + ('avatar', models.ImageField(null=True, upload_to=accounts.models.CustomUser._get_upload_to)), + ('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_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, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/equipment_tracker/accounts/migrations/__init__.py b/equipment_tracker/accounts/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/equipment_tracker/accounts/migrations/__pycache__/0001_initial.cpython-311.pyc b/equipment_tracker/accounts/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..83fa4ae Binary files /dev/null and b/equipment_tracker/accounts/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/equipment_tracker/accounts/migrations/__pycache__/__init__.cpython-311.pyc b/equipment_tracker/accounts/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..6447e93 Binary files /dev/null and b/equipment_tracker/accounts/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/equipment_tracker/accounts/models.py b/equipment_tracker/accounts/models.py new file mode 100644 index 0000000..12816f8 --- /dev/null +++ b/equipment_tracker/accounts/models.py @@ -0,0 +1,83 @@ +from django.db import models +from django.contrib.auth.models import AbstractUser +from django.db import models +from django.db.models.signals import post_migrate +from django.dispatch import receiver +import os +from uuid import uuid4 + + +class CustomUser(AbstractUser): + # Function for avatar uploads + def _get_upload_to(instance, filename): + base_filename, file_extension = os.path.splitext(filename) + # Get the student ID number + ext = base_filename.split('.')[-1] + filename = '{}.{}'.format(uuid4().hex, ext) + + student_id = str(instance.student_id_number) + new_filename = f"{student_id}_{filename}_{file_extension}" + return os.path.join('avatars', new_filename) + + # Delete old avatar file if new one is uploaded + def save(self, *args, **kwargs): + try: + # is the object in the database yet? + this = CustomUser.objects.get(id=self.id) + if this.avatar != self.avatar: + this.avatar.delete(save=False) + except: + pass # when new photo then we do nothing, normal case + super(CustomUser, self).save(*args, **kwargs) + + first_name = models.CharField(max_length=100) + last_name = models.CharField(max_length=100) + # Email inherited from base user class + # Username inherited from base user class + # Password inherited from base user class + # is_admin inherited from base user class + is_active = models.BooleanField(default=False) + is_technician = models.BooleanField(default=False) + avatar = models.ImageField(upload_to=_get_upload_to, null=True) + + @property + def full_name(self): + return f"{self.first_name} {self.last_name}" + pass + + +@receiver(post_migrate) +def create_superuser(sender, **kwargs): + if sender.name == 'accounts': + User = CustomUser + username = os.getenv('DJANGO_ADMIN_USERNAME') + email = os.getenv('DJANGO_ADMIN_EMAIL') + password = os.getenv('DJANGO_ADMIN_PASSWORD') + first_name = 'Admin' + last_name = 'Admin' + + if not User.objects.filter(username=username).exists(): + # Create the superuser with is_active set to False + superuser = User.objects.create_superuser( + username=username, email=email, password=password, first_name=first_name, last_name=last_name, is_technician=True) + + # Activate the superuser + superuser.is_active = True + print('Created admin account') + superuser.save() + + username = 'test-user-technician' + email = os.getenv('DJANGO_ADMIN_EMAIL') + password = os.getenv('DJANGO_ADMIN_PASSWORD') + first_name = 'Test' + last_name = 'Technician' + + if not User.objects.filter(username=username).exists(): + # Create the superuser with is_active set to False + user = User.objects.create_user( + username=username, email=email, password=password, first_name=first_name, last_name=last_name, is_technician=True) + + # Activate the user + user.is_active = True + print('Created debug technician account') + user.save() diff --git a/equipment_tracker/accounts/permissions.py b/equipment_tracker/accounts/permissions.py new file mode 100644 index 0000000..8486605 --- /dev/null +++ b/equipment_tracker/accounts/permissions.py @@ -0,0 +1,11 @@ +from rest_framework.permissions import BasePermission + + +class IsTechnician(BasePermission): + message = "You must be a technician to perform this action." + + def has_permission(self, request, view): + return request.user.is_authenticated and request.user.is_technician + + def has_object_permission(self, request, view, obj): + return request.user.is_authenticated and request.user.is_technician diff --git a/equipment_tracker/accounts/serializers.py b/equipment_tracker/accounts/serializers.py new file mode 100644 index 0000000..d878cce --- /dev/null +++ b/equipment_tracker/accounts/serializers.py @@ -0,0 +1,52 @@ +from djoser.serializers import UserCreateSerializer as BaseUserRegistrationSerializer +from djoser.serializers import UserSerializer as BaseUserSerializer +from django.core import exceptions as django_exceptions +from rest_framework import serializers +from accounts.models import CustomUser +from rest_framework.settings import api_settings +from django.contrib.auth.password_validation import validate_password +from django.utils.encoding import smart_str +from drf_spectacular.utils import extend_schema_field +from drf_spectacular.types import OpenApiTypes +from drf_extra_fields.fields import Base64ImageField + +# There can be multiple subject instances with the same name, only differing in course, year level, and semester. We filter them here + + +class CustomUserSerializer(BaseUserSerializer): + avatar = Base64ImageField() + + class Meta(BaseUserSerializer.Meta): + model = CustomUser + fields = ('username', 'email', 'avatar', 'first_name', 'last_name',) + + +class UserRegistrationSerializer(serializers.ModelSerializer): + email = serializers.EmailField(required=True) + password = serializers.CharField( + write_only=True, style={'input_type': 'password', 'placeholder': 'Password'}) + + class Meta: + model = CustomUser # Use your custom user model here + fields = ('username', 'email', 'password', 'avatar', + 'first_name', 'last_name') + + def validate(self, attrs): + user = self.Meta.model(**attrs) + password = attrs.get("password") + try: + validate_password(password, user) + except django_exceptions.ValidationError as e: + serializer_error = serializers.as_serializer_error(e) + raise serializers.ValidationError( + {"password": serializer_error[api_settings.NON_FIELD_ERRORS_KEY]} + ) + + return super().validate(attrs) + + def create(self, validated_data): + user = self.Meta.model(**validated_data) + user.set_password(validated_data['password']) + user.save() + + return user diff --git a/equipment_tracker/accounts/tests.py b/equipment_tracker/accounts/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/equipment_tracker/accounts/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/equipment_tracker/accounts/urls.py b/equipment_tracker/accounts/urls.py new file mode 100644 index 0000000..22c121e --- /dev/null +++ b/equipment_tracker/accounts/urls.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path('', include('djoser.urls')), + path('', include('djoser.urls.jwt')), +] diff --git a/equipment_tracker/accounts/views.py b/equipment_tracker/accounts/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/equipment_tracker/accounts/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/equipment_tracker/api/__pycache__/__init__.cpython-311.pyc b/equipment_tracker/api/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..1b3c24c Binary files /dev/null and b/equipment_tracker/api/__pycache__/__init__.cpython-311.pyc differ diff --git a/equipment_tracker/api/__pycache__/urls.cpython-311.pyc b/equipment_tracker/api/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000..25ad270 Binary files /dev/null and b/equipment_tracker/api/__pycache__/urls.cpython-311.pyc differ diff --git a/equipment_tracker/api/urls.py b/equipment_tracker/api/urls.py index 130dc54..03edd51 100644 --- a/equipment_tracker/api/urls.py +++ b/equipment_tracker/api/urls.py @@ -1,5 +1,6 @@ from django.urls import path, include urlpatterns = [ - path('accounts/', include('djoser.urls')) + path('accounts/', include('accounts.urls')), + path('equipments/', include('equipments.urls')) ] diff --git a/equipment_tracker/config/__pycache__/__init__.cpython-311.pyc b/equipment_tracker/config/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..a880ab5 Binary files /dev/null and b/equipment_tracker/config/__pycache__/__init__.cpython-311.pyc differ diff --git a/equipment_tracker/config/__pycache__/settings.cpython-311.pyc b/equipment_tracker/config/__pycache__/settings.cpython-311.pyc new file mode 100644 index 0000000..d8c3599 Binary files /dev/null and b/equipment_tracker/config/__pycache__/settings.cpython-311.pyc differ diff --git a/equipment_tracker/config/__pycache__/urls.cpython-311.pyc b/equipment_tracker/config/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000..791acf4 Binary files /dev/null and b/equipment_tracker/config/__pycache__/urls.cpython-311.pyc differ diff --git a/equipment_tracker/config/__pycache__/wsgi.cpython-311.pyc b/equipment_tracker/config/__pycache__/wsgi.cpython-311.pyc new file mode 100644 index 0000000..6d48467 Binary files /dev/null and b/equipment_tracker/config/__pycache__/wsgi.cpython-311.pyc differ diff --git a/equipment_tracker/config/settings.py b/equipment_tracker/config/settings.py index 7954525..d7c8953 100644 --- a/equipment_tracker/config/settings.py +++ b/equipment_tracker/config/settings.py @@ -28,11 +28,11 @@ BASE_DIR = Path(__file__).resolve().parent.parent SECRET_KEY = str(os.getenv('SECRET_KEY')) # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True -FRONTEND_DEBUG = True +DEBUG = False ALLOWED_HOSTS = ['*'] -# CSRF_TRUSTED_ORIGINS = [] To-do: Specify URL to web frontend +CSRF_TRUSTED_ORIGINS = [ + "https://equipment-tracker-backend.keannu1.duckdns.org"] # Email credentials EMAIL_HOST = '' @@ -56,6 +56,8 @@ else: # Application definition INSTALLED_APPS = [ + 'unfold', + 'unfold.contrib.simple_history', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -64,8 +66,14 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', 'rest_framework', 'rest_framework_simplejwt', + 'simple_history', 'djoser', - 'corsheaders' + 'corsheaders', + 'drf_spectacular', + 'drf_spectacular_sidecar', + 'accounts', + 'equipments', + 'equipment_groups', ] MIDDLEWARE = [ @@ -124,6 +132,19 @@ REST_FRAMEWORK = { 'user': '1440/min' }, + 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', +} + +# DRF-Spectacular +SPECTACULAR_SETTINGS = { + 'TITLE': 'CITC Equipment Tracker Backend', + 'DESCRIPTION': 'An IT Elective 4 Project', + 'VERSION': '1.0.0', + 'SERVE_INCLUDE_SCHEMA': False, + 'SWAGGER_UI_DIST': 'SIDECAR', + 'SWAGGER_UI_FAVICON_HREF': 'SIDECAR', + 'REDOC_DIST': 'SIDECAR', + # OTHER SETTINGS } WSGI_APPLICATION = 'config.wsgi.application' @@ -139,11 +160,19 @@ DATABASES = { } } +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'], + 'SERIALIZERS': { + 'user': 'accounts.serializers.CustomUserSerializer', + 'current_user': 'accounts.serializers.CustomUserSerializer', + 'user_create': 'accounts.serializers.UserRegistrationSerializer', + }, } # Password validation @@ -182,11 +211,7 @@ USE_TZ = True DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' -DOMAIN = '' -if (FRONTEND_DEBUG): - DOMAIN = 'exp' -else: - DOMAIN = 'citctracker' +DOMAIN = 'equipment-tracker-frontend.keannu1.duckdns.org/#' SITE_NAME = 'CITC Equipment Tracker' diff --git a/equipment_tracker/config/urls.py b/equipment_tracker/config/urls.py index cad3e39..8c0ff9e 100644 --- a/equipment_tracker/config/urls.py +++ b/equipment_tracker/config/urls.py @@ -16,8 +16,14 @@ Including another URLconf """ from django.contrib import admin from django.urls import path, include +from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView urlpatterns = [ path('admin/', admin.site.urls), - path('api/v1/', include('api.urls')) + path('api/v1/', include('api.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'), ] diff --git a/equipment_tracker/db.sqlite3 b/equipment_tracker/db.sqlite3 deleted file mode 100644 index 2cd4147..0000000 Binary files a/equipment_tracker/db.sqlite3 and /dev/null differ diff --git a/equipment_tracker/equipment_groups/__init__.py b/equipment_tracker/equipment_groups/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/equipment_tracker/equipment_groups/__pycache__/__init__.cpython-311.pyc b/equipment_tracker/equipment_groups/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..683ba00 Binary files /dev/null and b/equipment_tracker/equipment_groups/__pycache__/__init__.cpython-311.pyc differ diff --git a/equipment_tracker/equipment_groups/__pycache__/admin.cpython-311.pyc b/equipment_tracker/equipment_groups/__pycache__/admin.cpython-311.pyc new file mode 100644 index 0000000..38a8337 Binary files /dev/null and b/equipment_tracker/equipment_groups/__pycache__/admin.cpython-311.pyc differ diff --git a/equipment_tracker/equipment_groups/__pycache__/apps.cpython-311.pyc b/equipment_tracker/equipment_groups/__pycache__/apps.cpython-311.pyc new file mode 100644 index 0000000..3522790 Binary files /dev/null and b/equipment_tracker/equipment_groups/__pycache__/apps.cpython-311.pyc differ diff --git a/equipment_tracker/equipment_groups/__pycache__/models.cpython-311.pyc b/equipment_tracker/equipment_groups/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000..53f2c92 Binary files /dev/null and b/equipment_tracker/equipment_groups/__pycache__/models.cpython-311.pyc differ diff --git a/equipment_tracker/equipment_groups/admin.py b/equipment_tracker/equipment_groups/admin.py new file mode 100644 index 0000000..28ed500 --- /dev/null +++ b/equipment_tracker/equipment_groups/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin +from .models import EquipmentGroup +from simple_history.admin import SimpleHistoryAdmin + + +@admin.register(EquipmentGroup) +class EquipmentGroupAdmin(SimpleHistoryAdmin): + readonly_fields = ['status', 'date_added', 'last_updated'] + list_display = ('name', 'status', 'date_added', 'last_updated') diff --git a/equipment_tracker/equipment_groups/apps.py b/equipment_tracker/equipment_groups/apps.py new file mode 100644 index 0000000..3992bc6 --- /dev/null +++ b/equipment_tracker/equipment_groups/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EquipmentGroupsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'equipment_groups' diff --git a/equipment_tracker/equipment_groups/migrations/0001_initial.py b/equipment_tracker/equipment_groups/migrations/0001_initial.py new file mode 100644 index 0000000..b4c6f84 --- /dev/null +++ b/equipment_tracker/equipment_groups/migrations/0001_initial.py @@ -0,0 +1,53 @@ +# Generated by Django 4.2.7 on 2023-12-02 12:25 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import simple_history.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('equipments', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='HistoricalEquipmentGroup', + fields=[ + ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('name', models.CharField(max_length=200)), + ('remarks', models.TextField(max_length=512)), + ('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)), + ('last_updated', models.DateTimeField(blank=True, editable=False)), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField(db_index=True)), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'historical equipment group', + 'verbose_name_plural': 'historical equipment groups', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': ('history_date', 'history_id'), + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + migrations.CreateModel( + name='EquipmentGroup', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200)), + ('remarks', models.TextField(max_length=512)), + ('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)), + ('last_updated', models.DateTimeField(auto_now=True)), + ('equipments', models.ManyToManyField(to='equipments.equipmentinstance')), + ], + ), + ] diff --git a/equipment_tracker/equipment_groups/migrations/__init__.py b/equipment_tracker/equipment_groups/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/equipment_tracker/equipment_groups/migrations/__pycache__/0001_initial.cpython-311.pyc b/equipment_tracker/equipment_groups/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..3bc5c6a Binary files /dev/null and b/equipment_tracker/equipment_groups/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/equipment_tracker/equipment_groups/migrations/__pycache__/__init__.cpython-311.pyc b/equipment_tracker/equipment_groups/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..84fffe7 Binary files /dev/null and b/equipment_tracker/equipment_groups/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/equipment_tracker/equipment_groups/models.py b/equipment_tracker/equipment_groups/models.py new file mode 100644 index 0000000..87cde52 --- /dev/null +++ b/equipment_tracker/equipment_groups/models.py @@ -0,0 +1,40 @@ +from django.db import models +from django.utils.timezone import now +from simple_history.models import HistoricalRecords +from django.db.models.signals import post_migrate +from django.dispatch import receiver +from equipments.models import EquipmentInstance + + +class EquipmentGroup(models.Model): + name = models.CharField(max_length=200) + remarks = models.TextField(max_length=512) + date_added = models.DateTimeField(default=now, editable=False) + last_updated = models.DateTimeField(auto_now=True, editable=False) + equipments = models.ManyToManyField(EquipmentInstance) + history = HistoricalRecords() + + @property + def status(self): + if self.equipments.filter(status='Broken').exists(): + return 'Broken' + elif self.equipments.filter(status='Under Maintenance').exists(): + return 'Under Maintenance' + elif self.equipments.filter(status='Decomissioned').exists(): + return 'Decomissioned' + else: + return 'Working' + + def __str__(self): + return self.name + + +@receiver(post_migrate) +def create_superuser(sender, **kwargs): + if sender.name == 'equipment_groups': + PC = EquipmentInstance.objects.filter(id=1).first().id + KEYBOARD = EquipmentInstance.objects.filter(id=2).first().id + MOUSE = EquipmentInstance.objects.filter(id=3).first().id + GROUP, CREATED = EquipmentGroup.objects.get_or_create( + name="HP All-In-One PC Set", remarks="First PC set of citc tracker!") + GROUP.equipments.set([PC, KEYBOARD, MOUSE]) diff --git a/equipment_tracker/equipment_groups/tests.py b/equipment_tracker/equipment_groups/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/equipment_tracker/equipment_groups/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/equipment_tracker/equipment_groups/views.py b/equipment_tracker/equipment_groups/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/equipment_tracker/equipment_groups/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/equipment_tracker/equipments/__init__.py b/equipment_tracker/equipments/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/equipment_tracker/equipments/__pycache__/__init__.cpython-311.pyc b/equipment_tracker/equipments/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..d160710 Binary files /dev/null and b/equipment_tracker/equipments/__pycache__/__init__.cpython-311.pyc differ diff --git a/equipment_tracker/equipments/__pycache__/admin.cpython-311.pyc b/equipment_tracker/equipments/__pycache__/admin.cpython-311.pyc new file mode 100644 index 0000000..c7b5ade Binary files /dev/null and b/equipment_tracker/equipments/__pycache__/admin.cpython-311.pyc differ diff --git a/equipment_tracker/equipments/__pycache__/apps.cpython-311.pyc b/equipment_tracker/equipments/__pycache__/apps.cpython-311.pyc new file mode 100644 index 0000000..c9b62fa Binary files /dev/null and b/equipment_tracker/equipments/__pycache__/apps.cpython-311.pyc differ diff --git a/equipment_tracker/equipments/__pycache__/models.cpython-311.pyc b/equipment_tracker/equipments/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000..97c1c87 Binary files /dev/null and b/equipment_tracker/equipments/__pycache__/models.cpython-311.pyc differ diff --git a/equipment_tracker/equipments/__pycache__/serializers.cpython-311.pyc b/equipment_tracker/equipments/__pycache__/serializers.cpython-311.pyc new file mode 100644 index 0000000..7dca595 Binary files /dev/null and b/equipment_tracker/equipments/__pycache__/serializers.cpython-311.pyc differ diff --git a/equipment_tracker/equipments/__pycache__/urls.cpython-311.pyc b/equipment_tracker/equipments/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000..35140b8 Binary files /dev/null and b/equipment_tracker/equipments/__pycache__/urls.cpython-311.pyc differ diff --git a/equipment_tracker/equipments/__pycache__/views.cpython-311.pyc b/equipment_tracker/equipments/__pycache__/views.cpython-311.pyc new file mode 100644 index 0000000..9d53abc Binary files /dev/null and b/equipment_tracker/equipments/__pycache__/views.cpython-311.pyc differ diff --git a/equipment_tracker/equipments/admin.py b/equipment_tracker/equipments/admin.py new file mode 100644 index 0000000..561282d --- /dev/null +++ b/equipment_tracker/equipments/admin.py @@ -0,0 +1,16 @@ +from django.contrib import admin +from simple_history.admin import SimpleHistoryAdmin +from .models import Equipment, EquipmentInstance + + +@admin.register(Equipment) +class EquipmentAdmin(SimpleHistoryAdmin): + readonly_fields = ('date_added', 'last_updated') + list_display = ('name', 'date_added', 'last_updated') + + +@admin.register(EquipmentInstance) +class EquipmentInstanceAdmin(SimpleHistoryAdmin): + readonly_fields = ('date_added', 'last_updated') + list_display = ('equipment', 'status', 'remarks', + 'date_added', 'last_updated') diff --git a/equipment_tracker/equipments/apps.py b/equipment_tracker/equipments/apps.py new file mode 100644 index 0000000..0b795db --- /dev/null +++ b/equipment_tracker/equipments/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EquipmentsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'equipments' diff --git a/equipment_tracker/equipments/migrations/0001_initial.py b/equipment_tracker/equipments/migrations/0001_initial.py new file mode 100644 index 0000000..9288eb1 --- /dev/null +++ b/equipment_tracker/equipments/migrations/0001_initial.py @@ -0,0 +1,87 @@ +# Generated by Django 4.2.7 on 2023-12-02 12:25 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import simple_history.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Equipment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=40)), + ('description', models.TextField(max_length=512)), + ('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)), + ('last_updated', models.DateTimeField(auto_now=True)), + ('category', models.CharField(choices=[('PC', 'PC'), ('NETWORKING', 'Networking'), ('CCTV', 'CCTV'), ('FURNITURE', 'Furniture'), ('PERIPHERALS', 'Peripherals'), ('MISC', 'Miscellaneous')], default='MISC', max_length=20)), + ], + ), + migrations.CreateModel( + name='HistoricalEquipmentInstance', + fields=[ + ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('status', models.CharField(choices=[('WORKING', 'Working'), ('BROKEN', 'Broken'), ('MAINTENANCE', 'Under Maintenance'), ('DECOMISSIONED', 'Decomissioned')], default='PENDING', max_length=20)), + ('remarks', models.TextField(max_length=512)), + ('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)), + ('last_updated', models.DateTimeField(blank=True, editable=False)), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField(db_index=True)), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('equipment', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='equipments.equipment')), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'historical equipment instance', + 'verbose_name_plural': 'historical equipment instances', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': ('history_date', 'history_id'), + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + migrations.CreateModel( + name='HistoricalEquipment', + fields=[ + ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('name', models.CharField(max_length=40)), + ('description', models.TextField(max_length=512)), + ('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)), + ('last_updated', models.DateTimeField(blank=True, editable=False)), + ('category', models.CharField(choices=[('PC', 'PC'), ('NETWORKING', 'Networking'), ('CCTV', 'CCTV'), ('FURNITURE', 'Furniture'), ('PERIPHERALS', 'Peripherals'), ('MISC', 'Miscellaneous')], default='MISC', max_length=20)), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField(db_index=True)), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'historical equipment', + 'verbose_name_plural': 'historical equipments', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': ('history_date', 'history_id'), + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + migrations.CreateModel( + name='EquipmentInstance', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('status', models.CharField(choices=[('WORKING', 'Working'), ('BROKEN', 'Broken'), ('MAINTENANCE', 'Under Maintenance'), ('DECOMISSIONED', 'Decomissioned')], default='PENDING', max_length=20)), + ('remarks', models.TextField(max_length=512)), + ('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)), + ('last_updated', models.DateTimeField(auto_now=True)), + ('equipment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipments.equipment')), + ], + ), + ] diff --git a/equipment_tracker/equipments/migrations/0002_alter_equipment_description_and_more.py b/equipment_tracker/equipments/migrations/0002_alter_equipment_description_and_more.py new file mode 100644 index 0000000..dfb2bc7 --- /dev/null +++ b/equipment_tracker/equipments/migrations/0002_alter_equipment_description_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.7 on 2023-12-02 13:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('equipments', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='equipment', + name='description', + field=models.TextField(max_length=512, null=True), + ), + migrations.AlterField( + model_name='equipmentinstance', + name='remarks', + field=models.TextField(max_length=512, null=True), + ), + migrations.AlterField( + model_name='historicalequipment', + name='description', + field=models.TextField(max_length=512, null=True), + ), + migrations.AlterField( + model_name='historicalequipmentinstance', + name='remarks', + field=models.TextField(max_length=512, null=True), + ), + ] diff --git a/equipment_tracker/equipments/migrations/__init__.py b/equipment_tracker/equipments/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/equipment_tracker/equipments/migrations/__pycache__/0001_initial.cpython-311.pyc b/equipment_tracker/equipments/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..0ff86ba Binary files /dev/null and b/equipment_tracker/equipments/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/equipment_tracker/equipments/migrations/__pycache__/0002_alter_equipment_description_and_more.cpython-311.pyc b/equipment_tracker/equipments/migrations/__pycache__/0002_alter_equipment_description_and_more.cpython-311.pyc new file mode 100644 index 0000000..2f58f2a Binary files /dev/null and b/equipment_tracker/equipments/migrations/__pycache__/0002_alter_equipment_description_and_more.cpython-311.pyc differ diff --git a/equipment_tracker/equipments/migrations/__pycache__/__init__.cpython-311.pyc b/equipment_tracker/equipments/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..1f4cad8 Binary files /dev/null and b/equipment_tracker/equipments/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/equipment_tracker/equipments/models.py b/equipment_tracker/equipments/models.py new file mode 100644 index 0000000..b7476cf --- /dev/null +++ b/equipment_tracker/equipments/models.py @@ -0,0 +1,66 @@ +from django.db import models +from django.utils.timezone import now +from simple_history.models import HistoricalRecords +from django.db.models.signals import post_migrate +from django.dispatch import receiver +# Create your models here. + + +class Equipment(models.Model): + + CATEGORY_CHOICES = ( + ('PC', 'PC'), + ('NETWORKING', 'Networking'), + ('CCTV', 'CCTV'), + ('FURNITURE', 'Furniture'), + ('PERIPHERALS', 'Peripherals'), + ('MISC', 'Miscellaneous') + ) + + name = models.CharField(max_length=40) + description = models.TextField(max_length=512, null=True) + date_added = models.DateTimeField(default=now, editable=False) + last_updated = models.DateTimeField(auto_now=True, editable=False) + category = models.CharField( + max_length=20, choices=CATEGORY_CHOICES, default='MISC') + history = HistoricalRecords() + + def __str__(self): + return f'{self.name} ID:{self.id}' + + +class EquipmentInstance(models.Model): + STATUS_CHOICES = ( + ('WORKING', 'Working'), + ('BROKEN', 'Broken'), + ('MAINTENANCE', 'Under Maintenance'), + ('DECOMISSIONED', 'Decomissioned'), + ) + + equipment = models.ForeignKey(Equipment, on_delete=models.CASCADE) + status = models.CharField( + max_length=20, choices=STATUS_CHOICES, default='PENDING') + remarks = models.TextField(max_length=512, null=True) + date_added = models.DateTimeField(default=now, editable=False) + last_updated = models.DateTimeField(auto_now=True, editable=False) + history = HistoricalRecords() + + def __str__(self): + return f'{self.equipment.name} ID:{self.id}' + + +@receiver(post_migrate) +def create_superuser(sender, **kwargs): + if sender.name == 'equipments': + EQUIPMENT, CREATED = Equipment.objects.get_or_create( + name="HP All-in-One PC", description="I5 6500 8GB RAM 1TB HDD", category="PC") + EQUIPMENT_INSTANCE, CREATED = EquipmentInstance.objects.get_or_create( + equipment=EQUIPMENT, status="WORKING", remarks="First PC of citc equipment tracker!") + EQUIPMENT, CREATED = Equipment.objects.get_or_create( + name="HP Keyboard", description="Generic Membrane Keyboard", category="PERIPHERALS") + EQUIPMENT_INSTANCE, CREATED = EquipmentInstance.objects.get_or_create( + equipment=EQUIPMENT, status="WORKING", remarks="First keyboard of citc equipment tracker!") + EQUIPMENT, CREATED = Equipment.objects.get_or_create( + name="HP Mouse", description="Generic Mouse", category="PERIPHERALS") + EQUIPMENT_INSTANCE, CREATED = EquipmentInstance.objects.get_or_create( + equipment=EQUIPMENT, status="WORKING", remarks="First mouse of citc equipment tracker!") diff --git a/equipment_tracker/equipments/serializers.py b/equipment_tracker/equipments/serializers.py new file mode 100644 index 0000000..00e9a13 --- /dev/null +++ b/equipment_tracker/equipments/serializers.py @@ -0,0 +1,185 @@ +from rest_framework import serializers +from .models import Equipment, EquipmentInstance +from drf_spectacular.utils import extend_schema_field +from drf_spectacular.types import OpenApiTypes +from django.db.models import F +from accounts.models import CustomUser + +# -- Equipment Serializers + + +class EquipmentHistoricalRecordField(serializers.ListField): + child = serializers.DictField() + + def to_representation(self, data): + return super().to_representation(data.values('name', 'description', 'category', 'history_date', 'history_user').order_by('-history_date')) + + +class EquipmentSerializer(serializers.HyperlinkedModelSerializer): + date_added = serializers.DateTimeField( + format="%m-%d-%Y %I:%M%p", read_only=True) + last_updated = serializers.DateTimeField( + format="%m-%d-%Y %I:%M%p", read_only=True) + last_updated_by = serializers.SerializerMethodField() + name = serializers.CharField() + description = serializers.CharField() + + class Meta: + model = Equipment + fields = ('id', 'name', 'description', 'category', + 'last_updated', 'last_updated_by', 'date_added') + read_only_fields = ('id', 'last_updated', + 'last_updated_by', 'date_added') + + @extend_schema_field(OpenApiTypes.STR) + def get_history_user(self, obj): + return obj.history_user.username if obj.history_user else None + + @extend_schema_field(OpenApiTypes.STR) + def get_last_updated_by(self, obj): + return obj.history.first().history_user.username if obj.history.first().history_user else None + + +class EquipmentLogsSerializer(serializers.HyperlinkedModelSerializer): + history_date = serializers.DateTimeField( + format="%m-%d-%Y %I:%M%p", read_only=True) + history_user = serializers.SerializerMethodField() + + class Meta: + model = Equipment.history.model + fields = ('history_id', 'id', 'name', 'description', 'category', + 'history_date', 'history_user') + read_only_fields = ('history_id', 'id', 'name', 'description', + 'history_date', 'history_user') + + @extend_schema_field(OpenApiTypes.STR) + def get_history_user(self, obj): + return obj.history_user.username if obj.history_user else None + + +class EquipmentLogSerializer(serializers.HyperlinkedModelSerializer): + date_added = serializers.DateTimeField( + format="%m-%d-%Y %I:%M%p", read_only=True) + last_updated = serializers.DateTimeField( + format="%m-%d-%Y %I:%M%p", read_only=True) + last_updated_by = serializers.SerializerMethodField() + name = serializers.CharField() + description = serializers.CharField(required=False) + history = EquipmentHistoricalRecordField() + + class Meta: + model = Equipment + fields = ('id', 'name', 'description', 'category', + 'last_updated', 'date_added', 'last_updated_by', 'history') + read_only_fields = ('id', 'last_updated', + 'date_added', 'last_updated_by', 'history') + + @extend_schema_field(OpenApiTypes.STR) + def get_last_updated_by(self, obj): + return obj.history.first().history_user.username if obj.history.first().history_user else None + +# -- Equipment Instance Serializers + + +class EquipmentInstanceHistoricalRecordField(serializers.ListField): + child = serializers.DictField() + + def to_representation(self, data): + data = data.annotate(equipment_name=F('equipment__name')) + data = data.annotate(category=F('equipment__category')) + return super().to_representation(data.values('equipment', 'equipment_name', 'category', 'status', 'remarks', 'history_date', 'history_user').order_by('-history_date')) + + +class EquipmentInstanceSerializer(serializers.HyperlinkedModelSerializer): + equipment = serializers.SlugRelatedField( + slug_field='id', queryset=Equipment.objects.all()) + equipment_name = serializers.CharField( + source='equipment.name', read_only=True) + category = serializers.CharField( + source='equipment.category', read_only=True) + status = serializers.CharField() + remarks = serializers.CharField(required=False) + date_added = serializers.DateTimeField( + format="%m-%d-%Y %I:%M%p", read_only=True) + last_updated = serializers.DateTimeField( + format="%m-%d-%Y %I:%M%p", read_only=True) + last_updated_by = serializers.SerializerMethodField() + status = serializers.ChoiceField(choices=EquipmentInstance.STATUS_CHOICES) + + # Forbid user from changing equipment field once the instance is already created + def update(self, instance, validated_data): + # Ignore any changes to 'equipment' + validated_data.pop('equipment', None) + return super().update(instance, validated_data) + + class Meta: + model = EquipmentInstance + fields = ('id', 'equipment', 'equipment_name', 'category', 'status', 'remarks', + 'last_updated', 'last_updated_by', 'date_added') + read_only_fields = ('id', 'last_updated', 'equipment_name', 'category', + 'last_updated_by', 'date_added', 'equipment_name') + + @extend_schema_field(OpenApiTypes.STR) + def get_history_user(self, obj): + return obj.history_user.username if obj.history_user else None + + @extend_schema_field(OpenApiTypes.STR) + def get_last_updated_by(self, obj): + return obj.history.first().history_user.username if obj.history.first().history_user else None + + +class EquipmentInstanceLogsSerializer(serializers.HyperlinkedModelSerializer): + history_date = serializers.DateTimeField( + format="%m-%d-%Y %I:%M%p", read_only=True) + history_user = serializers.SerializerMethodField() + equipment = serializers.SlugRelatedField( + slug_field='id', queryset=Equipment.objects.all()) + equipment_name = serializers.CharField( + source='equipment.name', read_only=True) + category = serializers.CharField( + source='equipment.category', read_only=True) + + class Meta: + model = EquipmentInstance.history.model + fields = ('history_id', 'id', 'equipment', 'equipment_name', 'category', 'status', 'remarks', + 'history_date', 'history_user') + read_only_fields = ('history_id', 'id', 'equipment', 'equipment_name', 'category', 'status', 'remarks', + 'history_date', 'history_user', 'equipment_name') + + @extend_schema_field(OpenApiTypes.STR) + def get_history_user(self, obj): + return obj.history_user.username if obj.history_user else None + + +class EquipmentInstanceLogSerializer(serializers.HyperlinkedModelSerializer): + equipment = serializers.SlugRelatedField( + slug_field='id', queryset=Equipment.objects.all()) + equipment_name = serializers.CharField( + source='equipment.name', read_only=True) + status = serializers.CharField() + remarks = serializers.CharField() + date_added = serializers.DateTimeField( + format="%m-%d-%Y %I:%M%p", read_only=True) + last_updated = serializers.DateTimeField( + format="%m-%d-%Y %I:%M%p", read_only=True) + last_updated_by = serializers.SerializerMethodField() + history = EquipmentInstanceHistoricalRecordField() + category = serializers.CharField( + source='equipment.category', read_only=True) + + # Forbid user from changing equipment field once the instance is already created + def update(self, instance, validated_data): + # Ignore any changes to 'equipment' + validated_data.pop('equipment', None) + return super().update(instance, validated_data) + + class Meta: + model = EquipmentInstance + fields = ('id', 'equipment', 'equipment_name', 'category', 'status', 'remarks', + 'last_updated', 'date_added', 'last_updated_by', 'history') + read_only_fields = ('id', 'last_updated', 'equipment_name', 'category', + 'date_added', 'last_updated_by', 'history', 'equipment_name') + + @extend_schema_field(OpenApiTypes.STR) + def get_last_updated_by(self, obj): + return obj.history.first().history_user.username if obj.history.first().history_user else None diff --git a/equipment_tracker/equipments/tests.py b/equipment_tracker/equipments/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/equipment_tracker/equipments/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/equipment_tracker/equipments/urls.py b/equipment_tracker/equipments/urls.py new file mode 100644 index 0000000..9547051 --- /dev/null +++ b/equipment_tracker/equipments/urls.py @@ -0,0 +1,29 @@ +from django.urls import include, path +from rest_framework import routers +from . import views + +router = routers.DefaultRouter() +# For viewing all equipments +router.register(r'equipments', views.EquipmentViewSet) +router.register(r'equipment_instances', views.EquipmentInstanceViewSet) + +# Wire up our API using automatic URL routing. +# Additionally, we include login URLs for the browsable API. +urlpatterns = [ + path('', include(router.urls)), + # Logs for all equipments + path('equipments/logs', views.EquipmentsLogsViewSet.as_view()), + # Logs for each equipment + path('equipments//logs/', + views.EquipmentLogViewSet.as_view({'get': 'list'})), + # Last changed equipment + path('equipments/latest', views.LastUpdatedEquipmentViewSet.as_view()), + # Logs for all equipment instances + path('equipment_instances/logs', views.EquipmentInstancesLogsViewSet.as_view()), + # Logs for each equipment instance + path('equipment_instances//logs/', + views.EquipmentInstanceLogViewSet.as_view({'get': 'list'})), + # Last changed equipment instance + path('equipment_instances/latest', + views.LastUpdatedEquipmentInstanceViewSet.as_view()) +] diff --git a/equipment_tracker/equipments/views.py b/equipment_tracker/equipments/views.py new file mode 100644 index 0000000..700f6b1 --- /dev/null +++ b/equipment_tracker/equipments/views.py @@ -0,0 +1,90 @@ +from rest_framework.permissions import IsAuthenticated +from rest_framework import viewsets, generics +from .models import Equipment, EquipmentInstance +from . import serializers +from config.settings import DEBUG +from accounts.permissions import IsTechnician + +# -- Equipment Viewsets + + +class EquipmentViewSet(viewsets.ModelViewSet): + if (not DEBUG): + permission_classes = [IsAuthenticated, IsTechnician] + serializer_class = serializers.EquipmentSerializer + queryset = Equipment.objects.all().order_by('id') + +# For viewing all logs for all equipments + + +class EquipmentsLogsViewSet(generics.ListAPIView): + if (not DEBUG): + permission_classes = [IsAuthenticated, IsTechnician] + serializer_class = serializers.EquipmentLogsSerializer + queryset = Equipment.history.all().order_by('-history_date') + +# For viewing logs per individual equipment + + +class EquipmentLogViewSet(viewsets.ReadOnlyModelViewSet): + if (not DEBUG): + permission_classes = [IsAuthenticated, IsTechnician] + serializer_class = serializers.EquipmentLogSerializer + + def get_queryset(self): + equipment_id = self.kwargs['equipment_id'] + return Equipment.objects.filter(id=equipment_id) + +# Last changed equipment + + +class LastUpdatedEquipmentViewSet(generics.ListAPIView): + if (not DEBUG): + permission_classes = [IsAuthenticated, IsTechnician] + serializer_class = serializers.EquipmentSerializer + queryset = Equipment.objects.all().order_by('-date_added') + + def get_queryset(self): + return super().get_queryset()[:1] + +# -- Equipment Instance Viewsets + + +class EquipmentInstanceViewSet(viewsets.ModelViewSet): + if (not DEBUG): + permission_classes = [IsAuthenticated, IsTechnician] + serializer_class = serializers.EquipmentInstanceSerializer + queryset = EquipmentInstance.objects.all().order_by('id') + +# For viewing all equipment instance logs + + +class EquipmentInstancesLogsViewSet(generics.ListAPIView): + if (not DEBUG): + permission_classes = [IsAuthenticated, IsTechnician] + serializer_class = serializers.EquipmentInstanceLogsSerializer + queryset = EquipmentInstance.history.all().order_by('-history_date') + +# For viewing logs per individual equipment instance + + +class EquipmentInstanceLogViewSet(viewsets.ReadOnlyModelViewSet): + if (not DEBUG): + permission_classes = [IsAuthenticated, IsTechnician] + serializer_class = serializers.EquipmentInstanceLogSerializer + + def get_queryset(self): + equipment_id = self.kwargs['equipment_id'] + return EquipmentInstance.objects.filter(id=equipment_id) + +# Last changed equipment instance + + +class LastUpdatedEquipmentInstanceViewSet(generics.ListAPIView): + if (not DEBUG): + permission_classes = [IsAuthenticated, IsTechnician] + serializer_class = serializers.EquipmentInstanceSerializer + queryset = EquipmentInstance.objects.all().order_by('-date_added') + + def get_queryset(self): + return super().get_queryset()[:1] diff --git a/equipment_tracker/schema.yml b/equipment_tracker/schema.yml new file mode 100644 index 0000000..e042267 --- /dev/null +++ b/equipment_tracker/schema.yml @@ -0,0 +1,1416 @@ +openapi: 3.0.3 +info: + title: CITC Equipment Tracker Backend + version: 1.0.0 + description: An IT Elective 4 Project +paths: + /api/v1/accounts/jwt/create/: + post: + operationId: api_v1_accounts_jwt_create_create + description: |- + Takes a set of user credentials and returns an access and refresh JSON web + token pair to prove the authentication of those credentials. + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TokenObtainPair' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TokenObtainPair' + multipart/form-data: + schema: + $ref: '#/components/schemas/TokenObtainPair' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TokenObtainPair' + description: '' + /api/v1/accounts/jwt/refresh/: + post: + operationId: api_v1_accounts_jwt_refresh_create + description: |- + Takes a refresh type JSON web token and returns an access type JSON web + token if the refresh token is valid. + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TokenRefresh' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TokenRefresh' + multipart/form-data: + schema: + $ref: '#/components/schemas/TokenRefresh' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TokenRefresh' + description: '' + /api/v1/accounts/jwt/verify/: + post: + operationId: api_v1_accounts_jwt_verify_create + description: |- + Takes a token and indicates if it is valid. This view provides no + information about a token's fitness for a particular use. + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TokenVerify' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TokenVerify' + multipart/form-data: + schema: + $ref: '#/components/schemas/TokenVerify' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TokenVerify' + description: '' + /api/v1/accounts/users/: + get: + operationId: api_v1_accounts_users_list + tags: + - api + security: + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/CustomUser' + description: '' + post: + operationId: api_v1_accounts_users_create + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserRegistration' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserRegistration' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserRegistration' + required: true + security: + - jwtAuth: [] + - {} + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/UserRegistration' + description: '' + /api/v1/accounts/users/{id}/: + get: + operationId: api_v1_accounts_users_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user. + required: true + tags: + - api + security: + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CustomUser' + description: '' + put: + operationId: api_v1_accounts_users_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user. + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CustomUser' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CustomUser' + multipart/form-data: + schema: + $ref: '#/components/schemas/CustomUser' + required: true + security: + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CustomUser' + description: '' + patch: + operationId: api_v1_accounts_users_partial_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user. + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedCustomUser' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedCustomUser' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedCustomUser' + security: + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CustomUser' + description: '' + delete: + operationId: api_v1_accounts_users_destroy + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user. + required: true + tags: + - api + security: + - jwtAuth: [] + responses: + '204': + description: No response body + /api/v1/accounts/users/activation/: + post: + operationId: api_v1_accounts_users_activation_create + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Activation' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Activation' + multipart/form-data: + schema: + $ref: '#/components/schemas/Activation' + required: true + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Activation' + description: '' + /api/v1/accounts/users/me/: + get: + operationId: api_v1_accounts_users_me_retrieve + tags: + - api + security: + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CustomUser' + description: '' + put: + operationId: api_v1_accounts_users_me_update + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CustomUser' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CustomUser' + multipart/form-data: + schema: + $ref: '#/components/schemas/CustomUser' + required: true + security: + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CustomUser' + description: '' + patch: + operationId: api_v1_accounts_users_me_partial_update + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedCustomUser' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedCustomUser' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedCustomUser' + security: + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CustomUser' + description: '' + delete: + operationId: api_v1_accounts_users_me_destroy + tags: + - api + security: + - jwtAuth: [] + responses: + '204': + description: No response body + /api/v1/accounts/users/resend_activation/: + post: + operationId: api_v1_accounts_users_resend_activation_create + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SendEmailReset' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/SendEmailReset' + multipart/form-data: + schema: + $ref: '#/components/schemas/SendEmailReset' + required: true + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/SendEmailReset' + description: '' + /api/v1/accounts/users/reset_password/: + post: + operationId: api_v1_accounts_users_reset_password_create + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SendEmailReset' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/SendEmailReset' + multipart/form-data: + schema: + $ref: '#/components/schemas/SendEmailReset' + required: true + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/SendEmailReset' + description: '' + /api/v1/accounts/users/reset_password_confirm/: + post: + operationId: api_v1_accounts_users_reset_password_confirm_create + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PasswordResetConfirm' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PasswordResetConfirm' + multipart/form-data: + schema: + $ref: '#/components/schemas/PasswordResetConfirm' + required: true + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PasswordResetConfirm' + description: '' + /api/v1/accounts/users/reset_username/: + post: + operationId: api_v1_accounts_users_reset_username_create + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SendEmailReset' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/SendEmailReset' + multipart/form-data: + schema: + $ref: '#/components/schemas/SendEmailReset' + required: true + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/SendEmailReset' + description: '' + /api/v1/accounts/users/reset_username_confirm/: + post: + operationId: api_v1_accounts_users_reset_username_confirm_create + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UsernameResetConfirm' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UsernameResetConfirm' + multipart/form-data: + schema: + $ref: '#/components/schemas/UsernameResetConfirm' + required: true + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UsernameResetConfirm' + description: '' + /api/v1/accounts/users/set_password/: + post: + operationId: api_v1_accounts_users_set_password_create + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SetPassword' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/SetPassword' + multipart/form-data: + schema: + $ref: '#/components/schemas/SetPassword' + required: true + security: + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/SetPassword' + description: '' + /api/v1/accounts/users/set_username/: + post: + operationId: api_v1_accounts_users_set_username_create + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SetUsername' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/SetUsername' + multipart/form-data: + schema: + $ref: '#/components/schemas/SetUsername' + required: true + security: + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/SetUsername' + description: '' + /api/v1/equipments/equipment_instances/: + get: + operationId: api_v1_equipments_equipment_instances_list + tags: + - api + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/EquipmentInstance' + description: '' + post: + operationId: api_v1_equipments_equipment_instances_create + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/EquipmentInstance' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/EquipmentInstance' + multipart/form-data: + schema: + $ref: '#/components/schemas/EquipmentInstance' + required: true + security: + - jwtAuth: [] + - {} + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/EquipmentInstance' + description: '' + /api/v1/equipments/equipment_instances/{equipment_id}/logs/: + get: + operationId: api_v1_equipments_equipment_instances_logs_list_2 + parameters: + - in: path + name: equipment_id + schema: + type: integer + required: true + tags: + - api + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/EquipmentInstanceLog' + description: '' + /api/v1/equipments/equipment_instances/{id}/: + get: + operationId: api_v1_equipments_equipment_instances_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this equipment instance. + required: true + tags: + - api + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/EquipmentInstance' + description: '' + put: + operationId: api_v1_equipments_equipment_instances_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this equipment instance. + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/EquipmentInstance' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/EquipmentInstance' + multipart/form-data: + schema: + $ref: '#/components/schemas/EquipmentInstance' + required: true + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/EquipmentInstance' + description: '' + patch: + operationId: api_v1_equipments_equipment_instances_partial_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this equipment instance. + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedEquipmentInstance' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedEquipmentInstance' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedEquipmentInstance' + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/EquipmentInstance' + description: '' + delete: + operationId: api_v1_equipments_equipment_instances_destroy + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this equipment instance. + required: true + tags: + - api + security: + - jwtAuth: [] + - {} + responses: + '204': + description: No response body + /api/v1/equipments/equipment_instances/latest: + get: + operationId: api_v1_equipments_equipment_instances_latest_list + tags: + - api + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/EquipmentInstance' + description: '' + /api/v1/equipments/equipment_instances/logs: + get: + operationId: api_v1_equipments_equipment_instances_logs_list + tags: + - api + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/EquipmentInstanceLogs' + description: '' + /api/v1/equipments/equipments/: + get: + operationId: api_v1_equipments_equipments_list + tags: + - api + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Equipment' + description: '' + post: + operationId: api_v1_equipments_equipments_create + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Equipment' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Equipment' + multipart/form-data: + schema: + $ref: '#/components/schemas/Equipment' + required: true + security: + - jwtAuth: [] + - {} + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Equipment' + description: '' + /api/v1/equipments/equipments/{equipment_id}/logs/: + get: + operationId: api_v1_equipments_equipments_logs_list_2 + parameters: + - in: path + name: equipment_id + schema: + type: integer + required: true + tags: + - api + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/EquipmentLog' + description: '' + /api/v1/equipments/equipments/{id}/: + get: + operationId: api_v1_equipments_equipments_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this equipment. + required: true + tags: + - api + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Equipment' + description: '' + put: + operationId: api_v1_equipments_equipments_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this equipment. + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Equipment' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Equipment' + multipart/form-data: + schema: + $ref: '#/components/schemas/Equipment' + required: true + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Equipment' + description: '' + patch: + operationId: api_v1_equipments_equipments_partial_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this equipment. + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedEquipment' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedEquipment' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedEquipment' + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Equipment' + description: '' + delete: + operationId: api_v1_equipments_equipments_destroy + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this equipment. + required: true + tags: + - api + security: + - jwtAuth: [] + - {} + responses: + '204': + description: No response body + /api/v1/equipments/equipments/latest: + get: + operationId: api_v1_equipments_equipments_latest_list + tags: + - api + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Equipment' + description: '' + /api/v1/equipments/equipments/logs: + get: + operationId: api_v1_equipments_equipments_logs_list + tags: + - api + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/EquipmentLogs' + description: '' +components: + schemas: + Activation: + type: object + properties: + uid: + type: string + token: + type: string + required: + - token + - uid + CategoryEnum: + enum: + - PC + - NETWORKING + - CCTV + - FURNITURE + - PERIPHERALS + - MISC + type: string + description: |- + * `PC` - PC + * `NETWORKING` - Networking + * `CCTV` - CCTV + * `FURNITURE` - Furniture + * `PERIPHERALS` - Peripherals + * `MISC` - Miscellaneous + CustomUser: + type: object + properties: + username: + type: string + readOnly: true + description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_ + only. + email: + type: string + format: email + title: Email address + maxLength: 254 + avatar: + type: string + format: uri + first_name: + type: string + maxLength: 100 + last_name: + type: string + maxLength: 100 + required: + - avatar + - first_name + - last_name + - username + Equipment: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + description: + type: string + category: + $ref: '#/components/schemas/CategoryEnum' + last_updated: + type: string + format: date-time + readOnly: true + last_updated_by: + type: string + readOnly: true + date_added: + type: string + format: date-time + readOnly: true + required: + - date_added + - description + - id + - last_updated + - last_updated_by + - name + EquipmentInstance: + type: object + properties: + id: + type: integer + readOnly: true + equipment: + type: integer + equipment_name: + type: string + readOnly: true + category: + type: string + readOnly: true + status: + $ref: '#/components/schemas/StatusEnum' + remarks: + type: string + last_updated: + type: string + format: date-time + readOnly: true + last_updated_by: + type: string + readOnly: true + date_added: + type: string + format: date-time + readOnly: true + required: + - category + - date_added + - equipment + - equipment_name + - id + - last_updated + - last_updated_by + - status + EquipmentInstanceLog: + type: object + properties: + id: + type: integer + readOnly: true + equipment: + type: integer + equipment_name: + type: string + readOnly: true + category: + type: string + readOnly: true + status: + type: string + remarks: + type: string + last_updated: + type: string + format: date-time + readOnly: true + date_added: + type: string + format: date-time + readOnly: true + last_updated_by: + type: string + readOnly: true + history: + type: array + items: + type: object + additionalProperties: {} + required: + - category + - date_added + - equipment + - equipment_name + - history + - id + - last_updated + - last_updated_by + - remarks + - status + EquipmentInstanceLogs: + type: object + properties: + history_id: + type: integer + readOnly: true + id: + type: integer + readOnly: true + equipment: + type: integer + equipment_name: + type: string + readOnly: true + category: + type: string + readOnly: true + status: + allOf: + - $ref: '#/components/schemas/StatusEnum' + readOnly: true + remarks: + type: string + readOnly: true + nullable: true + history_date: + type: string + format: date-time + readOnly: true + history_user: + type: string + readOnly: true + required: + - category + - equipment + - equipment_name + - history_date + - history_id + - history_user + - id + - remarks + - status + EquipmentLog: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + description: + type: string + category: + $ref: '#/components/schemas/CategoryEnum' + last_updated: + type: string + format: date-time + readOnly: true + date_added: + type: string + format: date-time + readOnly: true + last_updated_by: + type: string + readOnly: true + history: + type: array + items: + type: object + additionalProperties: {} + required: + - date_added + - history + - id + - last_updated + - last_updated_by + - name + EquipmentLogs: + type: object + properties: + history_id: + type: integer + readOnly: true + name: + type: string + readOnly: true + description: + type: string + readOnly: true + nullable: true + category: + $ref: '#/components/schemas/CategoryEnum' + history_date: + type: string + format: date-time + readOnly: true + history_user: + type: string + readOnly: true + required: + - description + - history_date + - history_id + - history_user + - name + PasswordResetConfirm: + type: object + properties: + uid: + type: string + token: + type: string + new_password: + type: string + required: + - new_password + - token + - uid + PatchedCustomUser: + type: object + properties: + username: + type: string + readOnly: true + description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_ + only. + email: + type: string + format: email + title: Email address + maxLength: 254 + avatar: + type: string + format: uri + first_name: + type: string + maxLength: 100 + last_name: + type: string + maxLength: 100 + PatchedEquipment: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + description: + type: string + category: + $ref: '#/components/schemas/CategoryEnum' + last_updated: + type: string + format: date-time + readOnly: true + last_updated_by: + type: string + readOnly: true + date_added: + type: string + format: date-time + readOnly: true + PatchedEquipmentInstance: + type: object + properties: + id: + type: integer + readOnly: true + equipment: + type: integer + equipment_name: + type: string + readOnly: true + category: + type: string + readOnly: true + status: + $ref: '#/components/schemas/StatusEnum' + remarks: + type: string + last_updated: + type: string + format: date-time + readOnly: true + last_updated_by: + type: string + readOnly: true + date_added: + type: string + format: date-time + readOnly: true + SendEmailReset: + type: object + properties: + email: + type: string + format: email + required: + - email + SetPassword: + type: object + properties: + new_password: + type: string + current_password: + type: string + required: + - current_password + - new_password + SetUsername: + type: object + properties: + current_password: + type: string + new_username: + type: string + title: Username + description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_ + only. + pattern: ^[\w.@+-]+$ + maxLength: 150 + required: + - current_password + - new_username + StatusEnum: + enum: + - WORKING + - BROKEN + - MAINTENANCE + - DECOMISSIONED + type: string + description: |- + * `WORKING` - Working + * `BROKEN` - Broken + * `MAINTENANCE` - Under Maintenance + * `DECOMISSIONED` - Decomissioned + TokenObtainPair: + type: object + properties: + username: + type: string + writeOnly: true + password: + type: string + writeOnly: true + access: + type: string + readOnly: true + refresh: + type: string + readOnly: true + required: + - access + - password + - refresh + - username + TokenRefresh: + type: object + properties: + access: + type: string + readOnly: true + refresh: + type: string + writeOnly: true + required: + - access + - refresh + TokenVerify: + type: object + properties: + token: + type: string + writeOnly: true + required: + - token + UserRegistration: + type: object + properties: + username: + type: string + description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_ + only. + pattern: ^[\w.@+-]+$ + maxLength: 150 + email: + type: string + format: email + password: + type: string + writeOnly: true + avatar: + type: string + format: uri + nullable: true + first_name: + type: string + maxLength: 100 + last_name: + type: string + maxLength: 100 + required: + - email + - first_name + - last_name + - password + - username + UsernameResetConfirm: + type: object + properties: + new_username: + type: string + title: Username + description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_ + only. + pattern: ^[\w.@+-]+$ + maxLength: 150 + required: + - new_username + securitySchemes: + jwtAuth: + type: http + scheme: bearer + bearerFormat: JWT diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7e55fe0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,43 @@ +-i https://pypi.org/simple +asgiref==3.7.2; python_version >= '3.7' +attrs==23.1.0; python_version >= '3.7' +certifi==2023.11.17; python_version >= '3.6' +cffi==1.16.0; python_version >= '3.8' +charset-normalizer==3.3.2; python_full_version >= '3.7.0' +cryptography==41.0.7; python_version >= '3.7' +defusedxml==0.8.0rc2; python_version >= '3.6' +django==4.2.7 +django-cors-headers==4.3.1 +django-extra-fields==3.0.2 +django-simple-history==3.4.0 +django-templated-mail==1.1.1 +django-unfold==0.17.1 +djangorestframework==3.14.0 +djangorestframework-simplejwt==5.3.0; python_version >= '3.7' +djoser==2.2.2 +drf-spectacular[sidecar]==0.26.5 +drf-spectacular-sidecar==2023.10.1 +idna==3.6; python_version >= '3.5' +inflection==0.5.1; python_version >= '3.5' +jsonschema==4.20.0; python_version >= '3.8' +jsonschema-specifications==2023.11.2; python_version >= '3.8' +oauthlib==3.2.2; python_version >= '3.6' +pillow==10.1.0 +psycopg2==2.9.9 +pycparser==2.21 +pyjwt==2.8.0; python_version >= '3.7' +python-dotenv==1.0.0 +python3-openid==3.2.0 +pytz==2023.3.post1 +pyyaml==6.0.1; python_version >= '3.6' +referencing==0.31.1; python_version >= '3.8' +requests==2.31.0; python_version >= '3.7' +requests-oauthlib==1.3.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +rpds-py==0.13.2; python_version >= '3.8' +social-auth-app-django==5.4.0; python_version >= '3.8' +social-auth-core==4.5.1; python_version >= '3.8' +sqlparse==0.4.4; python_version >= '3.5' +tzdata==2023.3; sys_platform == 'win32' +uritemplate==4.1.1; python_version >= '3.6' +urllib3==2.1.0; python_version >= '3.8' +whitenoise==6.6.0