Merge pull request #16 from lemeow125/master

merge to prod
This commit is contained in:
Keannu Bernasol 2023-12-04 14:01:41 +08:00 committed by GitHub
commit c4038c8625
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
74 changed files with 2890 additions and 170 deletions

5
.gitignore vendored
View file

@ -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

30
Dockerfile Normal file
View file

@ -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

BIN
ERD/Backend_ERD.drawio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View file

@ -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]

607
Pipfile.lock generated
View file

@ -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": [

23
docker-compose.yml Normal file
View file

@ -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:

View file

@ -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'

View file

View file

@ -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)

View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class AccountsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'accounts'

View file

@ -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()),
],
),
]

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View file

@ -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')),
]

View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

Binary file not shown.

View file

@ -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'))
]

View file

@ -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'

View file

@ -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'),
]

Binary file not shown.

View file

@ -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')

View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class EquipmentGroupsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'equipment_groups'

View file

@ -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')),
],
),
]

View file

@ -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])

View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View file

View file

@ -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')

View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class EquipmentsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'equipments'

View file

@ -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')),
],
),
]

View file

@ -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),
),
]

View file

@ -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!")

View file

@ -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

View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View file

@ -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/<int:equipment_id>/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/<int:equipment_id>/logs/',
views.EquipmentInstanceLogViewSet.as_view({'get': 'list'})),
# Last changed equipment instance
path('equipment_instances/latest',
views.LastUpdatedEquipmentInstanceViewSet.as_view())
]

View file

@ -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]

1416
equipment_tracker/schema.yml Normal file

File diff suppressed because it is too large Load diff

43
requirements.txt Normal file
View file

@ -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