diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 92560bf..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,6 +0,0 @@ -## CSoC Task 5 Submission - -I have completed the following tasks - -- [ ] Basic endpoints -- [ ] Collaborator feature diff --git a/Pipfile b/Pipfile index a6563bd..173a205 100644 --- a/Pipfile +++ b/Pipfile @@ -12,6 +12,8 @@ django-cors-headers = "*" django-heroku = "*" gunicorn = "*" drf-yasg = "*" +pyjwt = "*" +pipenv = "==2021.5.29" [requires] python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index be9f23f..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,323 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "90a78ef190530d701fb120f1af60525e7c1eeab94face596902fdd03cc19a9db" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.8" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "asgiref": { - "hashes": [ - "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17", - "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0" - ], - "version": "==3.3.1" - }, - "certifi": { - "hashes": [ - "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", - "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" - ], - "version": "==2020.12.5" - }, - "chardet": { - "hashes": [ - "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", - "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" - ], - "version": "==4.0.0" - }, - "coreapi": { - "hashes": [ - "sha256:46145fcc1f7017c076a2ef684969b641d18a2991051fddec9458ad3f78ffc1cb", - "sha256:bf39d118d6d3e171f10df9ede5666f63ad80bba9a29a8ec17726a66cf52ee6f3" - ], - "version": "==2.3.3" - }, - "coreschema": { - "hashes": [ - "sha256:5e6ef7bf38c1525d5e55a895934ab4273548629f16aed5c0a6caa74ebf45551f", - "sha256:9503506007d482ab0867ba14724b93c18a33b22b6d19fb419ef2d239dd4a1607" - ], - "version": "==0.0.4" - }, - "dj-database-url": { - "hashes": [ - "sha256:4aeaeb1f573c74835b0686a2b46b85990571159ffc21aa57ecd4d1e1cb334163", - "sha256:851785365761ebe4994a921b433062309eb882fedd318e1b0fcecc607ed02da9" - ], - "version": "==0.5.0" - }, - "django": { - "hashes": [ - "sha256:5052b34b34b3425233c682e0e11d658fd6efd587d11335a0203d827224ada8f2", - "sha256:e1630333248c9b3d4e38f02093a26f1e07b271ca896d73097457996e0fae12e8" - ], - "index": "pypi", - "version": "==3.0.7" - }, - "django-cors-headers": { - "hashes": [ - "sha256:a5960addecc04527ab26617e51b8ed42f0adab4594b24bb0f3c33e2bd3857c3f", - "sha256:a785b5f446f6635810776d9f5f5d23e6a2a2f728ea982648370afaf0dfdf2627" - ], - "index": "pypi", - "version": "==3.2.1" - }, - "django-heroku": { - "hashes": [ - "sha256:2bc690aab89eedbe01311752320a9a12e7548e3b0ed102681acc5736a41a4762", - "sha256:6af4bc3ae4a9b55eaad6dbe5164918982d2762661aebc9f83d9fa49f6009514e" - ], - "index": "pypi", - "version": "==0.3.1" - }, - "djangorestframework": { - "hashes": [ - "sha256:5cc724dc4b076463497837269107e1995b1fbc917468d1b92d188fd1af9ea789", - "sha256:a5967b68a04e0d97d10f4df228e30f5a2d82ba63b9d03e1759f84993b7bf1b53" - ], - "index": "pypi", - "version": "==3.11.2" - }, - "drf-yasg": { - "hashes": [ - "sha256:5572e9d5baab9f6b49318169df9789f7399d0e3c7bdac8fdb8dfccf1d5d2b1ca", - "sha256:7d7af27ad16e18507e9392b2afd6b218fbffc432ec8dbea053099a2241e184ff" - ], - "index": "pypi", - "version": "==1.17.1" - }, - "gunicorn": { - "hashes": [ - "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626", - "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c" - ], - "index": "pypi", - "version": "==20.0.4" - }, - "idna": { - "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" - ], - "version": "==2.10" - }, - "inflection": { - "hashes": [ - "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417", - "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2" - ], - "version": "==0.5.1" - }, - "itypes": { - "hashes": [ - "sha256:03da6872ca89d29aef62773672b2d408f490f80db48b23079a4b194c86dd04c6", - "sha256:af886f129dea4a2a1e3d36595a2d139589e4dd287f5cab0b40e799ee81570ff1" - ], - "version": "==1.2.0" - }, - "jinja2": { - "hashes": [ - "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419", - "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6" - ], - "version": "==2.11.3" - }, - "markupsafe": { - "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", - "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f", - "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014", - "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85", - "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850", - "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1", - "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", - "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5", - "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c", - "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", - "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be", - "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621" - ], - "version": "==1.1.1" - }, - "packaging": { - "hashes": [ - "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", - "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" - ], - "version": "==20.9" - }, - "psycopg2": { - "hashes": [ - "sha256:00195b5f6832dbf2876b8bf77f12bdce648224c89c880719c745b90515233301", - "sha256:068115e13c70dc5982dfc00c5d70437fe37c014c808acce119b5448361c03725", - "sha256:26e7fd115a6db75267b325de0fba089b911a4a12ebd3d0b5e7acb7028bc46821", - "sha256:2c93d4d16933fea5bbacbe1aaf8fa8c1348740b2e50b3735d1b0bf8154cbf0f3", - "sha256:56007a226b8e95aa980ada7abdea6b40b75ce62a433bd27cec7a8178d57f4051", - "sha256:56fee7f818d032f802b8eed81ef0c1232b8b42390df189cab9cfa87573fe52c5", - "sha256:6a3d9efb6f36f1fe6aa8dbb5af55e067db802502c55a9defa47c5a1dad41df84", - "sha256:a49833abfdede8985ba3f3ec641f771cca215479f41523e99dace96d5b8cce2a", - "sha256:ad2fe8a37be669082e61fb001c185ffb58867fdbb3e7a6b0b0d2ffe232353a3e", - "sha256:b8cae8b2f022efa1f011cc753adb9cbadfa5a184431d09b273fb49b4167561ad", - "sha256:d160744652e81c80627a909a0e808f3c6653a40af435744de037e3172cf277f5", - "sha256:d5062ae50b222da28253059880a871dc87e099c25cb68acf613d9d227413d6f7", - "sha256:f22ea9b67aea4f4a1718300908a2fb62b3e4276cf00bd829a97ab5894af42ea3", - "sha256:f974c96fca34ae9e4f49839ba6b78addf0346777b46c4da27a7bf54f48d3057d", - "sha256:fb23f6c71107c37fd667cb4ea363ddeb936b348bbd6449278eb92c189699f543" - ], - "version": "==2.8.6" - }, - "pyparsing": { - "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" - ], - "version": "==2.4.7" - }, - "pytz": { - "hashes": [ - "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", - "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798" - ], - "version": "==2021.1" - }, - "requests": { - "hashes": [ - "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", - "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" - ], - "version": "==2.25.1" - }, - "ruamel.yaml": { - "hashes": [ - "sha256:64b06e7873eb8e1125525ecef7345447d786368cadca92a7cd9b59eae62e95a3", - "sha256:bb48c514222702878759a05af96f4b7ecdba9b33cd4efcf25c86b882cef3a942" - ], - "version": "==0.16.13" - }, - "ruamel.yaml.clib": { - "hashes": [ - "sha256:058a1cc3df2a8aecc12f983a48bda99315cebf55a3b3a5463e37bb599b05727b", - "sha256:1236df55e0f73cd138c0eca074ee086136c3f16a97c2ac719032c050f7e0622f", - "sha256:1f8c0a4577c0e6c99d208de5c4d3fd8aceed9574bb154d7a2b21c16bb924154c", - "sha256:2602e91bd5c1b874d6f93d3086f9830f3e907c543c7672cf293a97c3fabdcd91", - "sha256:28116f204103cb3a108dfd37668f20abe6e3cafd0d3fd40dba126c732457b3cc", - "sha256:2d24bd98af676f4990c4d715bcdc2a60b19c56a3fb3a763164d2d8ca0e806ba7", - "sha256:2fd336a5c6415c82e2deb40d08c222087febe0aebe520f4d21910629018ab0f3", - "sha256:30dca9bbcbb1cc858717438218d11eafb78666759e5094dd767468c0d577a7e7", - "sha256:44c7b0498c39f27795224438f1a6be6c5352f82cb887bc33d962c3a3acc00df6", - "sha256:464e66a04e740d754170be5e740657a3b3b6d2bcc567f0c3437879a6e6087ff6", - "sha256:46d6d20815064e8bb023ea8628cfb7402c0f0e83de2c2227a88097e239a7dffd", - "sha256:4df5019e7783d14b79217ad9c56edf1ba7485d614ad5a385d1b3c768635c81c0", - "sha256:4e52c96ca66de04be42ea2278012a2342d89f5e82b4512fb6fb7134e377e2e62", - "sha256:5254af7d8bdf4d5484c089f929cb7f5bafa59b4f01d4f48adda4be41e6d29f99", - "sha256:52ae5739e4b5d6317b52f5b040b1b6639e8af68a5b8fd606a8b08658fbd0cab5", - "sha256:53b9dd1abd70e257a6e32f934ebc482dac5edb8c93e23deb663eac724c30b026", - "sha256:6c0a5dc52fc74eb87c67374a4e554d4761fd42a4d01390b7e868b30d21f4b8bb", - "sha256:73b3d43e04cc4b228fa6fa5d796409ece6fcb53a6c270eb2048109cbcbc3b9c2", - "sha256:74161d827407f4db9072011adcfb825b5258a5ccb3d2cd518dd6c9edea9e30f1", - "sha256:75f0ee6839532e52a3a53f80ce64925ed4aed697dd3fa890c4c918f3304bd4f4", - "sha256:839dd72545ef7ba78fd2aa1a5dd07b33696adf3e68fae7f31327161c1093001b", - "sha256:8be05be57dc5c7b4a0b24edcaa2f7275866d9c907725226cdde46da09367d923", - "sha256:8e8fd0a22c9d92af3a34f91e8a2594eeb35cba90ab643c5e0e643567dc8be43e", - "sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c", - "sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988", - "sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f", - "sha256:b4b0d31f2052b3f9f9b5327024dc629a253a83d8649d4734ca7f35b60ec3e9e5", - "sha256:c6ac7e45367b1317e56f1461719c853fd6825226f45b835df7436bb04031fd8a", - "sha256:daf21aa33ee9b351f66deed30a3d450ab55c14242cfdfcd377798e2c0d25c9f1", - "sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2", - "sha256:f6061a31880c1ed6b6ce341215336e2f3d0c1deccd84957b6fa8ca474b41e89f" - ], - "markers": "platform_python_implementation == 'CPython' and python_version < '3.10'", - "version": "==0.2.2" - }, - "six": { - "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" - ], - "version": "==1.15.0" - }, - "sqlparse": { - "hashes": [ - "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", - "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" - ], - "version": "==0.4.1" - }, - "uritemplate": { - "hashes": [ - "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f", - "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae" - ], - "version": "==3.0.1" - }, - "urllib3": { - "hashes": [ - "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df", - "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937" - ], - "version": "==1.26.4" - }, - "whitenoise": { - "hashes": [ - "sha256:05ce0be39ad85740a78750c86a93485c40f08ad8c62a6006de0233765996e5c7", - "sha256:05d00198c777028d72d8b0bbd234db605ef6d60e9410125124002518a48e515d" - ], - "version": "==5.2.0" - } - }, - "develop": {} -} diff --git a/README.md b/README.md index ae12ddf..c8640b6 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ -# Todo App API - CSoC Dev Task 5 - +# Todo App API- ## Introduction -Welcome to the Task 5 of CSOC Dev. In this assignment, you will be working on the Django REST Framework. You will be implementing the TODO API server . Also, you will be adding some additional functionality to the API. +Todo API server made with Django Rest framework and heroku postgresql. ### Setting up the project @@ -14,202 +13,6 @@ Welcome to the Task 5 of CSOC Dev. In this assignment, you will be working on th - Run `python manage.py migrate` to apply migrations. - Start the development server using `python manage.py runserver` -### Working -We are including the description of the API at the end of this readme for your reference. - -You'll have to complete two subtasks: - -- **Complete all the basic endpoints (API Description is included below)**: - - Auth - Login, Register, Get Profile and Todo - Get Detail, Get List, Create, Edit and Delete. In this subtask, only the creator of the Todo will have the access to View, Edit or Delete the Todo. As an example, we have created the endpoint `/todo/create/` for you. However, it just returns an empty `200 RESPONSE` on success. You will have to modify the endpoint to return the created Todo details too in the response body, along with a `200 RESPONSE`. The necessary details for this subtask are mentioned in the comments too. - -- **Implement collaborator feature**: - - In this subtask, you will have to implement a feature where the creator of a todo can add or remove the collaborators to a todo. Specifically, for every todo, the owner can add one or more collaborator to a todo and can remove one or more collaborators from a todo. So, every todo will have a set of collaborators (may be empty too). The collaborator of a todo will have the access to Edit or Delete the todo, but he won't be able to add or delete the collaborators to the todo. Only the creator of the todo will have the permission to add or remove collaborators. - - For this subtask, you may need to fiddle around with the models, create some views and serializers and make the necessary endpoints. We suggest you to create the two endpoints `/todo/{id}/add-collaborators/` and `/todo/{id}/remove-collaborators/` for adding and removing collaborators, respectively. Also, you will need to modify the GET Todo endpoint to display all the Todo - the one which the user has created, and the one for which the user is collaborating. Make sure to distinguish between the two of them, and we leave your imagination on how to do this. - -Make sure to do proper validation of the requests, and grant proper permissions to a user. - -## Tasks -- ### Complete all the basic endpoints (100 points) -- ### Collaborator feature (100 points) - -## Deadline -You'll have a week to complete this task. - -## Submission -* Follow the instructions to setup and run this project. -* Complete the task by making the required changes in the files. -* When done, commit your work locally and push it to your origin (forked repository). -* Make a pull request to our repository, stating the tasks which you have completed. -* Let us review your pull request. - -## API Description (Only for Subtask 1) - -### Auth System -For this API, you will have to use Token-based Authorization. We have already created the serializer and the functions required to create token for a user, for your reference. All the requests made to the API (except the Login and Register endpoints) shall need an **Authorization header** with a valid token and the prefix **Token**. Make sure to add proper permissions in the views to implement this. - -The authorization header shall have the following format: -`Authorization: Token ` - -In order to obtain a valid token it's necessary to send a request `POST /auth/login/` with **username** and **password**. To register a new user it's necessary to make a request `POST /auth/register/` with name, email, username and password. - -### End Points - -**Auth** - -- `POST /auth/login/` - - Takes the username and password as input, validates them and returns the **Token**, if the credentials are valid. - - Request Body (Sample): - ``` - { - "username": "string", - "password": "string" - } - ``` - Response Body (Sample): - ``` - { - "token": "string" - } - ``` - Response Code: `200` - -- `POST /auth/register/` - - Register a user in Django by taking the name, email, username and password as input. - - Request Body (Sample): - ``` - { - "name": "string", - "email": "user@example.com", - "username": "string", - "password": "string" - } - ``` - Response Body (Sample): - ``` - { - "token": "string" - } - ``` - Response Code: `200` - -- `POST /auth/profile/` - - Retrieve the id, name, email and username of the logged in user. Requires token in the Authorization header. - - Response Body (Sample): - ``` - { - "id": 1, - "name": "string", - "email": "user@example.com", - "username": "string" - } - ``` - Response Code: `200` - - -**Todo** - -- `GET /todo/` - - Get all the Todos of the logged in user. Requires token in the Authorization header. - - Response Body (Sample): - ``` - [ - { - "id": 1, - "title": "string" - }, - { - "id": 2, - "title": "string" - } - ] - ``` - Response Code: `200` - -- `POST /todo/create/` - - Create a Todo entry for the logged in user. Requires token in the Authorization header. - - Request Body (Sample): - ``` - { - "title": "string" - } - ``` - Response Body (Sample): - ``` - { - "id": 1, - "title": "string" - } - ``` - Response Code: `200` - -- `GET /todo/{id}/` - - Get the Todo of the logged in user with given id. Requires token in the Authorization header. - - Response Body (Sample): - ``` - { - "id": 1, - "title": "string" - } - ``` - Response Code: `200` - -- `PUT /todo/{id}/` - - Change the title of the Todo with given id, and get the new title as response. Requires token in the Authorization header. - - Request Body (Sample): - ``` - { - "title": "string" - } - ``` - Response Body (Sample): - ``` - { - "id": 1, - "title": "string" - } - ``` - -- `PATCH /todo/{id}/` - - Change the title of the Todo with given id, and get the new title as response. Requires token in the Authorization header. - - Request Body (Sample): - ``` - { - "title": "string" - } - ``` - Response Body (Sample): - ``` - { - "id": 1, - "title": "string" - } - ``` - -- `DELETE /todo/{id}/` - - Delete the Todo with given id. Requires token in the Authorization header. - - Response Code: `204` - ### Testing the API The API can be tested by running the Django server locally, going to the following url: [http://127.0.0.1:8000/](http://127.0.0.1:8000/), clicking the "Try it out" button after selecting the endpoint and finally executing it along with the Response Body (if required). diff --git a/api/admin.py b/api/admin.py index 8df206b..35a2479 100644 --- a/api/admin.py +++ b/api/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin -from .models import Todo +from .models import * -admin.site.register(Todo) \ No newline at end of file +admin.site.register(Todo) +admin.site.register(Collaboration) \ No newline at end of file diff --git a/api/migrations/0001_initial.py b/api/migrations/0001_initial.py index a21d3c2..485b732 100644 --- a/api/migrations/0001_initial.py +++ b/api/migrations/0001_initial.py @@ -1,6 +1,8 @@ -# Generated by Django 3.0.5 on 2020-04-21 08:58 +# Generated by Django 3.2.6 on 2021-08-16 18:36 +from django.conf import settings from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): @@ -8,6 +10,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ @@ -16,6 +19,15 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('title', models.CharField(max_length=255)), + ('creator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='collab', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('todo', models.ManyToManyField(null=True, to='api.Todo')), + ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), ] diff --git a/api/migrations/0002_rename_todo_collab_todos.py b/api/migrations/0002_rename_todo_collab_todos.py new file mode 100644 index 0000000..9122958 --- /dev/null +++ b/api/migrations/0002_rename_todo_collab_todos.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-08-18 17:16 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0001_initial'), + ] + + operations = [ + migrations.RenameField( + model_name='collab', + old_name='todo', + new_name='todos', + ), + ] diff --git a/api/migrations/0002_todo_creator.py b/api/migrations/0002_todo_creator.py deleted file mode 100644 index a77254c..0000000 --- a/api/migrations/0002_todo_creator.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.0.5 on 2020-04-21 10:00 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('api', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='todo', - name='creator', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - preserve_default=False, - ), - ] diff --git a/api/migrations/0003_auto_20210819_1124.py b/api/migrations/0003_auto_20210819_1124.py new file mode 100644 index 0000000..7b0f23e --- /dev/null +++ b/api/migrations/0003_auto_20210819_1124.py @@ -0,0 +1,27 @@ +# Generated by Django 3.2.6 on 2021-08-19 05:54 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('api', '0002_rename_todo_collab_todos'), + ] + + operations = [ + migrations.CreateModel( + name='Collaboration', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('collaborators', models.ManyToManyField(null=True, to=settings.AUTH_USER_MODEL)), + ('todo', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='api.todo')), + ], + ), + migrations.DeleteModel( + name='collab', + ), + ] diff --git a/api/migrations/0004_rename_collaborators_collaboration_user.py b/api/migrations/0004_rename_collaborators_collaboration_user.py new file mode 100644 index 0000000..4d78d6b --- /dev/null +++ b/api/migrations/0004_rename_collaborators_collaboration_user.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-08-19 05:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0003_auto_20210819_1124'), + ] + + operations = [ + migrations.RenameField( + model_name='collaboration', + old_name='collaborators', + new_name='user', + ), + ] diff --git a/api/migrations/0005_rename_user_collaboration_collaborator.py b/api/migrations/0005_rename_user_collaboration_collaborator.py new file mode 100644 index 0000000..d6f025f --- /dev/null +++ b/api/migrations/0005_rename_user_collaboration_collaborator.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-08-19 06:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0004_rename_collaborators_collaboration_user'), + ] + + operations = [ + migrations.RenameField( + model_name='collaboration', + old_name='user', + new_name='collaborator', + ), + ] diff --git a/api/migrations/0006_alter_collaboration_todo.py b/api/migrations/0006_alter_collaboration_todo.py new file mode 100644 index 0000000..12c32e5 --- /dev/null +++ b/api/migrations/0006_alter_collaboration_todo.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.6 on 2021-08-20 12:15 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0005_rename_user_collaboration_collaborator'), + ] + + operations = [ + migrations.AlterField( + model_name='collaboration', + name='todo', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='api.todo'), + ), + ] diff --git a/api/migrations/0007_alter_collaboration_todo.py b/api/migrations/0007_alter_collaboration_todo.py new file mode 100644 index 0000000..a944553 --- /dev/null +++ b/api/migrations/0007_alter_collaboration_todo.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.6 on 2021-08-20 12:17 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0006_alter_collaboration_todo'), + ] + + operations = [ + migrations.AlterField( + model_name='collaboration', + name='todo', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='api.todo'), + ), + ] diff --git a/api/migrations/0008_rename_collaborator_collaboration_collaborators.py b/api/migrations/0008_rename_collaborator_collaboration_collaborators.py new file mode 100644 index 0000000..e33a522 --- /dev/null +++ b/api/migrations/0008_rename_collaborator_collaboration_collaborators.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-08-21 16:09 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0007_alter_collaboration_todo'), + ] + + operations = [ + migrations.RenameField( + model_name='collaboration', + old_name='collaborator', + new_name='collaborators', + ), + ] diff --git a/api/models.py b/api/models.py index ab3ca0a..01a9fde 100644 --- a/api/models.py +++ b/api/models.py @@ -1,10 +1,19 @@ from django.db import models +from datetime import datetime from django.contrib.auth.models import User - class Todo(models.Model): - creator = models.ForeignKey(User, on_delete=models.CASCADE) +#it is a todo created by the creator(user) + creator = models.ForeignKey(User, on_delete=models.CASCADE, null = True) title = models.CharField(max_length=255) def __str__(self): - return self.title \ No newline at end of file + return self.title + +class Collaboration(models.Model): +#a single todo can have multiple collaborators + collaborators = models.ManyToManyField(User, null=True) + todo = models.ForeignKey(Todo, on_delete=models.CASCADE, null = True) + + def __str__(self): + return self.todo.title diff --git a/api/serializers.py b/api/serializers.py index ffd9d3a..a4e7f82 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -1,29 +1,39 @@ from rest_framework import serializers -from .models import Todo - - -""" -TODO: -Create the appropriate Serializer class(es) for implementing -Todo GET (List and Detail), PUT, PATCH and DELETE. -""" - +from .models import * +from django.contrib.auth.models import User +from authentication.serializers import * class TodoCreateSerializer(serializers.ModelSerializer): - """ - TODO: - Currently, the /todo/create/ endpoint returns only 200 status code, - after successful Todo creation. - - Modify the below code (if required), so that this endpoint would - also return the serialized Todo data (id etc.), alongwith 200 status code. - """ +#saving the user who created the todo def save(self, **kwargs): data = self.validated_data user = self.context['request'].user title = data['title'] todo = Todo.objects.create(creator=user, title=title) - + todo.save() + class Meta: model = Todo fields = ('id', 'title',) + +class TodoViewSerializer(serializers.ModelSerializer): + creator = serializers.CharField(source = 'creator.username', required = False, read_only = True) + creator_id = serializers.IntegerField(source = 'creator.id', required = False, read_only = True) + class Meta: + model = Todo + fields = ('id', 'title','creator', 'creator_id') + +class CollaborationUpdateSerializer(serializers.ModelSerializer): + class Meta: + model = Collaboration + fields = ('todo', 'collaborators') + + +class CollaborationSerializer(serializers.ModelSerializer): + todo = serializers.CharField(source = 'todo.title', required = False, read_only = True) + todo_id = serializers.IntegerField(source = 'todo.id', required = False, read_only = True) + collaboration_id = serializers.IntegerField(source = 'id', required = False, read_only = True) + class Meta: + model = Collaboration + fields = ('collaboration_id','todo','todo_id','collaborators',) + diff --git a/api/urls.py b/api/urls.py index 72f4978..7079c15 100644 --- a/api/urls.py +++ b/api/urls.py @@ -1,12 +1,12 @@ -from django.urls import path -from .views import TodoCreateView +from django.db import router +from django.urls import path, include +from .views import * +from rest_framework import routers -""" -TODO: -Add the urlpatterns of the endpoints, required for implementing -Todo GET (List and Detail), PUT, PATCH and DELETE. -""" +router = routers.SimpleRouter() +router.register(r'collaborations', CollaborationListViewSet, basename='collaboration') +router.register(r'todo', TodoViewSet, basename='todo') urlpatterns = [ - path('todo/create/', TodoCreateView.as_view()), + path(r'', include(router.urls)), ] \ No newline at end of file diff --git a/api/views.py b/api/views.py index d676c64..96b011b 100644 --- a/api/views.py +++ b/api/views.py @@ -1,35 +1,59 @@ -from rest_framework import generics +from django.db.models import query +from django.shortcuts import get_object_or_404 +from rest_framework import generics, viewsets +from rest_framework.generics import * from rest_framework import permissions from rest_framework import status +from rest_framework.decorators import api_view +from .serializers import * +from rest_framework.views import APIView from rest_framework.response import Response -from .serializers import TodoCreateSerializer -from .models import Todo - - -""" -TODO: -Create the appropriate View classes for implementing -Todo GET (List and Detail), PUT, PATCH and DELETE. -""" - - -class TodoCreateView(generics.GenericAPIView): - """ - TODO: - Currently, the /todo/create/ endpoint returns only 200 status code, - after successful Todo creation. - - Modify the below code (if required), so that this endpoint would - also return the serialized Todo data (id etc.), alongwith 200 status code. - """ - permission_classes = (permissions.IsAuthenticated, ) - serializer_class = TodoCreateSerializer - - def post(self, request): - """ - Creates a Todo entry for the logged in user. - """ - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save() - return Response(status=status.HTTP_200_OK) +from .models import * +from django.http import Http404 + + +class TodoViewSet(viewsets.ModelViewSet): + permission_classes = (permissions.IsAuthenticated,) + serializer_class = TodoViewSerializer + # http_method_names = ['get', 'put', 'patch', 'delete'] + + def create(self, request, *args, **kwargs): + return super().create(request, *args, **kwargs) + + def get_queryset(self): + return Todo.objects.all() + + +class CollaborationListViewSet(viewsets.ModelViewSet): + permission_classes = (permissions.IsAuthenticated,) + serializer_class = CollaborationSerializer + + def get_queryset(self): + return Collaboration.objects.all() + +# class TodoCreateView(generics.GenericAPIView): +# permission_classes = (permissions.IsAuthenticated, ) +# serializer_class = TodoCreateSerializer + +# def post(self, request): +# serializer = self.get_serializer(data=request.data) +# serializer.is_valid(raise_exception=True) +# data = serializer.save() +# return Response(data, status=status.HTTP_201_CREATED) + +# class CollaborationUpdateViewSet(viewsets.ModelViewSet): +# permission_classes = (permissions.IsAuthenticated,) +# serializer_class = CollaborationUpdateSerializer +# http_method_names = ['put', 'patch'] + +# def get_queryset(self): +# return Collaboration.objects.all() + + +#super method in python +#one viewset all functionalities +#writing tests + + +#django queryset api +#mysql and postgresql \ No newline at end of file diff --git a/authentication/serializers.py b/authentication/serializers.py index 47264af..2642ba0 100644 --- a/authentication/serializers.py +++ b/authentication/serializers.py @@ -1,23 +1,71 @@ from rest_framework import serializers from django.contrib.auth import authenticate from django.contrib.auth.models import User +from rest_framework.authtoken.models import Token +from api.serializers import * +def create_auth_token(user): + token, created = Token.objects.get_or_create(user=user) + return token + class TokenSerializer(serializers.Serializer): token = serializers.CharField(max_length=500) - class LoginSerializer(serializers.Serializer): - # TODO: Implement login functionality - pass + username = serializers.CharField() + password = serializers.CharField() + + def validate(self, data): + username = data['username'] + password = data['password'] + user = authenticate(username=username, password=password) + if not user: + raise serializers.ValidationError("Invalid Credentials!") + return data + + def get_token(self): + username = self.validated_data['username'] + user = User.objects.get(username=username) + return TokenSerializer({ + 'token': create_auth_token(user), + }) class RegisterSerializer(serializers.Serializer): - # TODO: Implement register functionality - pass + + password = serializers.CharField(max_length=128, min_length=8, write_only=True) + email=serializers.EmailField(required=True) + first_name=serializers.CharField(required=True) + last_name=serializers.CharField(required=True) + username = serializers.CharField(required=True) + + def validate_username(self, username): + if User.objects.filter(username=username).exists(): + raise serializers.ValidationError("Username already exists!") + return username + + def validate_eamil(self, email): + if User.objects.filter(email=email).exists(): + raise serializers.ValidationError("Username already exists!") + return email + + def register(self): + data=self.validated_data + username=data['username'] + email=data['email'] + password=data['password'] + user=User.objects.create_user(username=username, email=email, password=password) + user.first_name=data['first_name'] + user.last_name=data['last_name'] + user.save() + + return TokenSerializer({ + 'token': create_auth_token(user) + }) class UserSerializer(serializers.ModelSerializer): - # TODO: Implement the functionality to display user details - pass - \ No newline at end of file + class Meta: + model = User + fields = ['username', 'first_name','last_name', 'email', 'password'] \ No newline at end of file diff --git a/authentication/views.py b/authentication/views.py index d748434..73ceac6 100644 --- a/authentication/views.py +++ b/authentication/views.py @@ -1,42 +1,43 @@ -from rest_framework import permissions +from rest_framework import permissions, serializers from rest_framework import generics +from rest_framework.generics import * from rest_framework import status +from rest_framework import response from rest_framework.response import Response +from django.conf import settings from rest_framework.authtoken.models import Token +from django.contrib import auth from .serializers import ( LoginSerializer, RegisterSerializer, UserSerializer, TokenSerializer) -def create_auth_token(user): - """ - Returns the token required for authentication for a user. - """ - token, _ = Token.objects.get_or_create(user=user) - return token class LoginView(generics.GenericAPIView): - """ - TODO: - Implement login functionality, taking username and password - as input, and returning the Token. - """ - pass + serializer_class = LoginSerializer + + def post(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + response = serializer.get_token() + return Response(response.data, status=status.HTTP_200_OK) + class RegisterView(generics.GenericAPIView): - """ - TODO: - Implement register functionality, registering the user by - taking his details, and returning the Token. - """ - pass + serializer_class = RegisterSerializer + + def post(self, request): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + response = serializer.register() + return Response(response.data, status=status.HTTP_200_OK) class UserProfileView(generics.RetrieveAPIView): - """ - TODO: - Implement the functionality to retrieve the details - of the logged in user. - """ - pass \ No newline at end of file + permission_classes = (permissions.IsAuthenticated,) + serializer_class = UserSerializer + + def get(self, request): + serializer = self.get_serializer(request.user) + return Response(serializer.data) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ca9500d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,36 @@ +asgiref==3.4.1 +backports.entry-points-selectable==1.1.0 +certifi==2021.5.30 +charset-normalizer==2.0.4 +coreapi==2.3.3 +coreschema==0.0.4 +distlib==0.3.2 +dj-database-url==0.5.0 +Django==3.2.6 +django-cors-headers==3.8.0 +django-heroku==0.0.0 +djangorestframework==3.12.4 +drf-yasg==1.20.0 +filelock==3.0.12 +gunicorn==20.1.0 +idna==3.2 +inflection==0.5.1 +itypes==1.2.0 +Jinja2==3.0.1 +MarkupSafe==2.0.1 +packaging==21.0 +pipenv==2021.5.29 +platformdirs==2.2.0 +PyJWT==2.1.0 +pyparsing==2.4.7 +pytz==2021.1 +requests==2.26.0 +ruamel.yaml==0.17.10 +ruamel.yaml.clib==0.2.6 +six==1.16.0 +sqlparse==0.4.1 +uritemplate==3.0.1 +urllib3==1.26.6 +virtualenv==20.7.2 +virtualenv-clone==0.5.6 +whitenoise==5.3.0 diff --git a/todo/settings.py b/todo/settings.py index 8359271..564a9d4 100644 --- a/todo/settings.py +++ b/todo/settings.py @@ -45,7 +45,8 @@ 'rest_framework.authtoken', 'drf_yasg', 'corsheaders', - 'api' + 'api', + 'authentication' ] MIDDLEWARE = [ @@ -98,6 +99,9 @@ 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } +import dj_database_url +db_from_env = dj_database_url.config(conn_max_age=600) +DATABASES['default'].update(db_from_env) # Password validation @@ -124,6 +128,9 @@ LANGUAGE_CODE = 'en-us' +DATETIME_FORMAT = '%d-%m-%Y %H:%M:%S' + + TIME_ZONE = 'Asia/Kolkata' USE_I18N = True