this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix all linting issues in care (#2483)

Fix all linting issues in care (#2483)

---------

Co-authored-by: Aakash Singh <mail@singhaakash.dev>

authored by

Vignesh Hari
Aakash Singh
and committed by
GitHub
663bf305 7478b9a5

+1602 -2048
+5 -18
.github/workflows/linter.yml
··· 7 7 - staging 8 8 merge_group: 9 9 10 - permissions: { } 11 - 12 10 jobs: 13 11 lint: 14 - name: Lint Code Base 15 12 runs-on: ubuntu-latest 16 - 17 13 steps: 18 - - name: Checkout Code 19 - uses: actions/checkout@v4 20 - 21 - - name: Ruff check 22 - uses: chartboost/ruff-action@491342200cdd1cf4d5132a30ddc546b3b5bc531b 14 + - uses: actions/checkout@v4 23 15 with: 24 - version: 0.6.7 25 - args: "check" 26 - changed-files: "true" 27 - 28 - - name: Ruff format 29 - uses: chartboost/ruff-action@491342200cdd1cf4d5132a30ddc546b3b5bc531b 16 + fetch-depth: 0 17 + - uses: actions/setup-python@v3 18 + - uses: pre-commit/action@v3.0.1 30 19 with: 31 - version: 0.6.7 32 - args: "format" 33 - changed-files: "true" 20 + extra_args: --color=always --from-ref ${{ github.event.pull_request.base.sha }} --to-ref ${{ github.event.pull_request.head.sha }}
-3
Pipfile
··· 44 44 whitenoise = "==6.7.0" 45 45 46 46 [dev-packages] 47 - black = "==24.8.0" 48 47 boto3-stubs = { extras = ["s3", "boto3"], version = "==1.35.25" } 49 48 coverage = "==7.6.1" 50 49 debugpy = "==1.8.5" ··· 53 52 django-silk = "==5.2.0" 54 53 djangorestframework-stubs = "==3.15.1" 55 54 factory-boy = "==3.3.1" 56 - flake8 = "==7.1.1" 57 55 freezegun = "==1.5.1" 58 56 ipython = "==8.27.0" 59 - isort = "==5.13.2" 60 57 mypy = "==1.11.2" 61 58 pre-commit = "==3.8.0" 62 59 requests-mock = "==1.12.1"
+109 -198
Pipfile.lock
··· 1 1 { 2 2 "_meta": { 3 3 "hash": { 4 - "sha256": "41ec4e61d7dcec07332f048933e1c9699217a4b73422a8630121eedd2f6cffa4" 4 + "sha256": "f249cd5eb0e4b4e2f285fe4b6d0a9b3a125e26ab0521692f3ad0ab5e332fc450" 5 5 }, 6 6 "pipfile-spec": 6, 7 7 "requires": { ··· 470 470 "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a", 471 471 "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289" 472 472 ], 473 - "index": "pypi", 474 473 "markers": "python_version >= '3.7'", 475 474 "version": "==43.0.1" 476 475 }, ··· 1616 1615 }, 1617 1616 "tzdata": { 1618 1617 "hashes": [ 1619 - "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd", 1620 - "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252" 1618 + "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", 1619 + "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd" 1621 1620 ], 1622 1621 "markers": "python_version >= '2'", 1623 - "version": "==2024.1" 1622 + "version": "==2024.2" 1624 1623 }, 1625 1624 "unicodecsv": { 1626 1625 "hashes": [ ··· 1670 1669 }, 1671 1670 "yarl": { 1672 1671 "hashes": [ 1673 - "sha256:01a8697ec24f17c349c4f655763c4db70eebc56a5f82995e5e26e837c6eb0e49", 1674 - "sha256:02da8759b47d964f9173c8675710720b468aa1c1693be0c9c64abb9d8d9a4867", 1675 - "sha256:04293941646647b3bfb1719d1d11ff1028e9c30199509a844da3c0f5919dc520", 1676 - "sha256:067b961853c8e62725ff2893226fef3d0da060656a9827f3f520fb1d19b2b68a", 1677 - "sha256:077da604852be488c9a05a524068cdae1e972b7dc02438161c32420fb4ec5e14", 1678 - "sha256:09696438cb43ea6f9492ef237761b043f9179f455f405279e609f2bc9100212a", 1679 - "sha256:0b8486f322d8f6a38539136a22c55f94d269addb24db5cb6f61adc61eabc9d93", 1680 - "sha256:0ea9682124fc062e3d931c6911934a678cb28453f957ddccf51f568c2f2b5e05", 1681 - "sha256:0f351fa31234699d6084ff98283cb1e852270fe9e250a3b3bf7804eb493bd937", 1682 - "sha256:14438dfc5015661f75f85bc5adad0743678eefee266ff0c9a8e32969d5d69f74", 1683 - "sha256:15061ce6584ece023457fb8b7a7a69ec40bf7114d781a8c4f5dcd68e28b5c53b", 1684 - "sha256:15439f3c5c72686b6c3ff235279630d08936ace67d0fe5c8d5bbc3ef06f5a420", 1685 - "sha256:17b5a386d0d36fb828e2fb3ef08c8829c1ebf977eef88e5367d1c8c94b454639", 1686 - "sha256:18ac56c9dd70941ecad42b5a906820824ca72ff84ad6fa18db33c2537ae2e089", 1687 - "sha256:1bb2d9e212fb7449b8fb73bc461b51eaa17cc8430b4a87d87be7b25052d92f53", 1688 - "sha256:1e969fa4c1e0b1a391f3fcbcb9ec31e84440253325b534519be0d28f4b6b533e", 1689 - "sha256:1fa2e7a406fbd45b61b4433e3aa254a2c3e14c4b3186f6e952d08a730807fa0c", 1690 - "sha256:2164cd9725092761fed26f299e3f276bb4b537ca58e6ff6b252eae9631b5c96e", 1691 - "sha256:21a7c12321436b066c11ec19c7e3cb9aec18884fe0d5b25d03d756a9e654edfe", 1692 - "sha256:238a21849dd7554cb4d25a14ffbfa0ef380bb7ba201f45b144a14454a72ffa5a", 1693 - "sha256:250e888fa62d73e721f3041e3a9abf427788a1934b426b45e1b92f62c1f68366", 1694 - "sha256:25861303e0be76b60fddc1250ec5986c42f0a5c0c50ff57cc30b1be199c00e63", 1695 - "sha256:267b24f891e74eccbdff42241c5fb4f974de2d6271dcc7d7e0c9ae1079a560d9", 1696 - "sha256:27fcb271a41b746bd0e2a92182df507e1c204759f460ff784ca614e12dd85145", 1697 - "sha256:2909fa3a7d249ef64eeb2faa04b7957e34fefb6ec9966506312349ed8a7e77bf", 1698 - "sha256:3257978c870728a52dcce8c2902bf01f6c53b65094b457bf87b2644ee6238ddc", 1699 - "sha256:327c724b01b8641a1bf1ab3b232fb638706e50f76c0b5bf16051ab65c868fac5", 1700 - "sha256:3de5292f9f0ee285e6bd168b2a77b2a00d74cbcfa420ed078456d3023d2f6dff", 1701 - "sha256:3fce4da3703ee6048ad4138fe74619c50874afe98b1ad87b2698ef95bf92c96d", 1702 - "sha256:3ff6b1617aa39279fe18a76c8d165469c48b159931d9b48239065767ee455b2b", 1703 - "sha256:400cd42185f92de559d29eeb529e71d80dfbd2f45c36844914a4a34297ca6f00", 1704 - "sha256:4179522dc0305c3fc9782549175c8e8849252fefeb077c92a73889ccbcd508ad", 1705 - "sha256:4307d9a3417eea87715c9736d050c83e8c1904e9b7aada6ce61b46361b733d92", 1706 - "sha256:476e20c433b356e16e9a141449f25161e6b69984fb4cdbd7cd4bd54c17844998", 1707 - "sha256:489fa8bde4f1244ad6c5f6d11bb33e09cf0d1d0367edb197619c3e3fc06f3d91", 1708 - "sha256:48a28bed68ab8fb7e380775f0029a079f08a17799cb3387a65d14ace16c12e2b", 1709 - "sha256:48dfd117ab93f0129084577a07287376cc69c08138694396f305636e229caa1a", 1710 - "sha256:4973eac1e2ff63cf187073cd4e1f1148dcd119314ab79b88e1b3fad74a18c9d5", 1711 - "sha256:498442e3af2a860a663baa14fbf23fb04b0dd758039c0e7c8f91cb9279799bff", 1712 - "sha256:501c503eed2bb306638ccb60c174f856cc3246c861829ff40eaa80e2f0330367", 1713 - "sha256:504cf0d4c5e4579a51261d6091267f9fd997ef58558c4ffa7a3e1460bd2336fa", 1714 - "sha256:61a5f2c14d0a1adfdd82258f756b23a550c13ba4c86c84106be4c111a3a4e413", 1715 - "sha256:637c7ddb585a62d4469f843dac221f23eec3cbad31693b23abbc2c366ad41ff4", 1716 - "sha256:66b63c504d2ca43bf7221a1f72fbe981ff56ecb39004c70a94485d13e37ebf45", 1717 - "sha256:67459cf8cf31da0e2cbdb4b040507e535d25cfbb1604ca76396a3a66b8ba37a6", 1718 - "sha256:688654f8507464745ab563b041d1fb7dab5d9912ca6b06e61d1c4708366832f5", 1719 - "sha256:6907daa4b9d7a688063ed098c472f96e8181733c525e03e866fb5db480a424df", 1720 - "sha256:69721b8effdb588cb055cc22f7c5105ca6fdaa5aeb3ea09021d517882c4a904c", 1721 - "sha256:6d23754b9939cbab02c63434776df1170e43b09c6a517585c7ce2b3d449b7318", 1722 - "sha256:7175a87ab8f7fbde37160a15e58e138ba3b2b0e05492d7351314a250d61b1591", 1723 - "sha256:72bf26f66456baa0584eff63e44545c9f0eaed9b73cb6601b647c91f14c11f38", 1724 - "sha256:74db2ef03b442276d25951749a803ddb6e270d02dda1d1c556f6ae595a0d76a8", 1725 - "sha256:750f656832d7d3cb0c76be137ee79405cc17e792f31e0a01eee390e383b2936e", 1726 - "sha256:75e0ae31fb5ccab6eda09ba1494e87eb226dcbd2372dae96b87800e1dcc98804", 1727 - "sha256:768ecc550096b028754ea28bf90fde071c379c62c43afa574edc6f33ee5daaec", 1728 - "sha256:7d51324a04fc4b0e097ff8a153e9276c2593106a811704025bbc1d6916f45ca6", 1729 - "sha256:7e975a2211952a8a083d1b9d9ba26472981ae338e720b419eb50535de3c02870", 1730 - "sha256:8215f6f21394d1f46e222abeb06316e77ef328d628f593502d8fc2a9117bde83", 1731 - "sha256:8258c86f47e080a258993eed877d579c71da7bda26af86ce6c2d2d072c11320d", 1732 - "sha256:8418c053aeb236b20b0ab8fa6bacfc2feaaf7d4683dd96528610989c99723d5f", 1733 - "sha256:87f020d010ba80a247c4abc335fc13421037800ca20b42af5ae40e5fd75e7909", 1734 - "sha256:884eab2ce97cbaf89f264372eae58388862c33c4f551c15680dd80f53c89a269", 1735 - "sha256:8a336eaa7ee7e87cdece3cedb395c9657d227bfceb6781295cf56abcd3386a26", 1736 - "sha256:8aef1b64da41d18026632d99a06b3fefe1d08e85dd81d849fa7c96301ed22f1b", 1737 - "sha256:8aef97ba1dd2138112890ef848e17d8526fe80b21f743b4ee65947ea184f07a2", 1738 - "sha256:8ed653638ef669e0efc6fe2acb792275cb419bf9cb5c5049399f3556995f23c7", 1739 - "sha256:9361628f28f48dcf8b2f528420d4d68102f593f9c2e592bfc842f5fb337e44fd", 1740 - "sha256:946eedc12895873891aaceb39bceb484b4977f70373e0122da483f6c38faaa68", 1741 - "sha256:94d0caaa912bfcdc702a4204cd5e2bb01eb917fc4f5ea2315aa23962549561b0", 1742 - "sha256:964a428132227edff96d6f3cf261573cb0f1a60c9a764ce28cda9525f18f7786", 1743 - "sha256:999bfee0a5b7385a0af5ffb606393509cfde70ecca4f01c36985be6d33e336da", 1744 - "sha256:a08ea567c16f140af8ddc7cb58e27e9138a1386e3e6e53982abaa6f2377b38cc", 1745 - "sha256:a28b70c9e2213de425d9cba5ab2e7f7a1c8ca23a99c4b5159bf77b9c31251447", 1746 - "sha256:a34e1e30f1774fa35d37202bbeae62423e9a79d78d0874e5556a593479fdf239", 1747 - "sha256:a4264515f9117be204935cd230fb2a052dd3792789cc94c101c535d349b3dab0", 1748 - "sha256:a7915ea49b0c113641dc4d9338efa9bd66b6a9a485ffe75b9907e8573ca94b84", 1749 - "sha256:aac44097d838dda26526cffb63bdd8737a2dbdf5f2c68efb72ad83aec6673c7e", 1750 - "sha256:b91044952da03b6f95fdba398d7993dd983b64d3c31c358a4c89e3c19b6f7aef", 1751 - "sha256:ba444bdd4caa2a94456ef67a2f383710928820dd0117aae6650a4d17029fa25e", 1752 - "sha256:c2dc4250fe94d8cd864d66018f8344d4af50e3758e9d725e94fecfa27588ff82", 1753 - "sha256:c35f493b867912f6fda721a59cc7c4766d382040bdf1ddaeeaa7fa4d072f4675", 1754 - "sha256:c92261eb2ad367629dc437536463dc934030c9e7caca861cc51990fe6c565f26", 1755 - "sha256:ce928c9c6409c79e10f39604a7e214b3cb69552952fbda8d836c052832e6a979", 1756 - "sha256:d95b52fbef190ca87d8c42f49e314eace4fc52070f3dfa5f87a6594b0c1c6e46", 1757 - "sha256:dae7bd0daeb33aa3e79e72877d3d51052e8b19c9025ecf0374f542ea8ec120e4", 1758 - "sha256:e286580b6511aac7c3268a78cdb861ec739d3e5a2a53b4809faef6b49778eaff", 1759 - "sha256:e4b53f73077e839b3f89c992223f15b1d2ab314bdbdf502afdc7bb18e95eae27", 1760 - "sha256:e8f63904df26d1a66aabc141bfd258bf738b9bc7bc6bdef22713b4f5ef789a4c", 1761 - "sha256:f3a6d90cab0bdf07df8f176eae3a07127daafcf7457b997b2bf46776da2c7eb7", 1762 - "sha256:f41fa79114a1d2eddb5eea7b912d6160508f57440bd302ce96eaa384914cd265", 1763 - "sha256:f46f81501160c28d0c0b7333b4f7be8983dbbc161983b6fb814024d1b4952f79", 1764 - "sha256:f61db3b7e870914dbd9434b560075e0366771eecbe6d2b5561f5bc7485f39efd" 1672 + "sha256:0103c52f8dfe5d573c856322149ddcd6d28f51b4d4a3ee5c4b3c1b0a05c3d034", 1673 + "sha256:01549468858b87d36f967c97d02e6e54106f444aeb947ed76f8f71f85ed07cec", 1674 + "sha256:0274b1b7a9c9c32b7bf250583e673ff99fb9fccb389215841e2652d9982de740", 1675 + "sha256:0ac33d22b2604b020569a82d5f8a03ba637ba42cc1adf31f616af70baf81710b", 1676 + "sha256:0d0a5e87bc48d76dfcfc16295201e9812d5f33d55b4a0b7cad1025b92bf8b91b", 1677 + "sha256:10b690cd78cbaca2f96a7462f303fdd2b596d3978b49892e4b05a7567c591572", 1678 + "sha256:126309c0f52a2219b3d1048aca00766429a1346596b186d51d9fa5d2070b7b13", 1679 + "sha256:15871130439ad10abb25a4631120d60391aa762b85fcab971411e556247210a0", 1680 + "sha256:17d4dc4ff47893a06737b8788ed2ba2f5ac4e8bb40281c8603920f7d011d5bdd", 1681 + "sha256:18c2a7757561f05439c243f517dbbb174cadfae3a72dee4ae7c693f5b336570f", 1682 + "sha256:1d4017e78fb22bc797c089b746230ad78ecd3cdb215bc0bd61cb72b5867da57e", 1683 + "sha256:1f50a37aeeb5179d293465e522fd686080928c4d89e0ff215e1f963405ec4def", 1684 + "sha256:20d817c0893191b2ab0ba30b45b77761e8dfec30a029b7c7063055ca71157f84", 1685 + "sha256:22839d1d1eab9e4b427828a88a22beb86f67c14d8ff81175505f1cc8493f3500", 1686 + "sha256:22dda2799c8d39041d731e02bf7690f0ef34f1691d9ac9dfcb98dd1e94c8b058", 1687 + "sha256:2376d8cf506dffd0e5f2391025ae8675b09711016656590cb03b55894161fcfa", 1688 + "sha256:24197ba3114cc85ddd4091e19b2ddc62650f2e4a899e51b074dfd52d56cf8c72", 1689 + "sha256:24416bb5e221e29ddf8aac5b97e94e635ca2c5be44a1617ad6fe32556df44294", 1690 + "sha256:2631c9d7386bd2d4ce24ecc6ebf9ae90b3efd713d588d90504eaa77fec4dba01", 1691 + "sha256:28389a68981676bf74e2e199fe42f35d1aa27a9c98e3a03e6f58d2d3d054afe1", 1692 + "sha256:2aee7594d2c2221c717a8e394bbed4740029df4c0211ceb0f04815686e99c795", 1693 + "sha256:2e430ac432f969ef21770645743611c1618362309e3ad7cab45acd1ad1a540ff", 1694 + "sha256:2e912b282466444023610e4498e3795c10e7cfd641744524876239fcf01d538d", 1695 + "sha256:30ffc046ebddccb3c4cac72c1a3e1bc343492336f3ca86d24672e90ccc5e788a", 1696 + "sha256:319c206e83e46ec2421b25b300c8482b6fe8a018baca246be308c736d9dab267", 1697 + "sha256:326b8a079a9afcac0575971e56dabdf7abb2ea89a893e6949b77adfeb058b50e", 1698 + "sha256:36ee0115b9edca904153a66bb74a9ff1ce38caff015de94eadfb9ba8e6ecd317", 1699 + "sha256:3e26e64f42bce5ddf9002092b2c37b13071c2e6413d5c05f9fa9de58ed2f7749", 1700 + "sha256:4ea99e64b2ad2635e0f0597b63f5ea6c374791ff2fa81cdd4bad8ed9f047f56f", 1701 + "sha256:501a1576716032cc6d48c7c47bcdc42d682273415a8f2908e7e72cb4625801f3", 1702 + "sha256:54c8cee662b5f8c30ad7eedfc26123f845f007798e4ff1001d9528fe959fd23c", 1703 + "sha256:595bbcdbfc4a9c6989d7489dca8510cba053ff46b16c84ffd95ac8e90711d419", 1704 + "sha256:5b860055199aec8d6fe4dcee3c5196ce506ca198a50aab0059ffd26e8e815828", 1705 + "sha256:5c667b383529520b8dd6bd496fc318678320cb2a6062fdfe6d3618da6b8790f6", 1706 + "sha256:5fb475a4cdde582c9528bb412b98f899680492daaba318231e96f1a0a1bb0d53", 1707 + "sha256:607d12f0901f6419a8adceb139847c42c83864b85371f58270e42753f9780fa6", 1708 + "sha256:64c5b0f2b937fe40d0967516eee5504b23cb247b8b7ffeba7213a467d9646fdc", 1709 + "sha256:664380c7ed524a280b6a2d5d9126389c3e96cd6e88986cdb42ca72baa27421d6", 1710 + "sha256:6af871f70cfd5b528bd322c65793b5fd5659858cdfaa35fbe563fb99b667ed1f", 1711 + "sha256:6c89894cc6f6ddd993813e79244b36b215c14f65f9e4f1660b1f2ba9e5594b95", 1712 + "sha256:6dee0496d5f1a8f57f0f28a16f81a2033fc057a2cf9cd710742d11828f8c80e2", 1713 + "sha256:6e9a9f50892153bad5046c2a6df153224aa6f0573a5a8ab44fc54a1e886f6e21", 1714 + "sha256:712ba8722c0699daf186de089ddc4677651eb9875ed7447b2ad50697522cbdd9", 1715 + "sha256:717f185086bb9d817d4537dd18d5df5d657598cd00e6fc22e4d54d84de266c1d", 1716 + "sha256:71978ba778948760cff528235c951ea0ef7a4f9c84ac5a49975f8540f76c3f73", 1717 + "sha256:71af3766bb46738d12cc288d9b8de7ef6f79c31fd62757e2b8a505fe3680b27f", 1718 + "sha256:73a183042ae0918c82ce2df38c3db2409b0eeae88e3afdfc80fb67471a95b33b", 1719 + "sha256:7564525a4673fde53dee7d4c307a961c0951918f0b8c7f09b2c9e02067cf6504", 1720 + "sha256:76a59d1b63de859398bc7764c860a769499511463c1232155061fe0147f13e01", 1721 + "sha256:7e9905fc2dc1319e4c39837b906a024cf71b1261cc66b0cd89678f779c0c61f5", 1722 + "sha256:8112f640a4f7e7bf59f7cabf0d47a29b8977528c521d73a64d5cc9e99e48a174", 1723 + "sha256:835010cc17d0020e7931d39e487d72c8e01c98e669b6896a8b8c9aa8ca69a949", 1724 + "sha256:838dde2cb570cfbb4cab8a876a0974e8b90973ea40b3ac27a79b8a74c8a2db15", 1725 + "sha256:8d31dd0245d88cf7239e96e8f2a99f815b06e458a5854150f8e6f0e61618d41b", 1726 + "sha256:96b34830bd6825ca0220bf005ea99ac83eb9ce51301ddb882dcf613ae6cd95fb", 1727 + "sha256:96c8ff1e1dd680e38af0887927cab407a4e51d84a5f02ae3d6eb87233036c763", 1728 + "sha256:9a7ee79183f0b17dcede8b6723e7da2ded529cf159a878214be9a5d3098f5b1e", 1729 + "sha256:a3e2aff8b822ab0e0bdbed9f50494b3a35629c4b9488ae391659973a37a9f53f", 1730 + "sha256:a4f3ab9eb8ab2d585ece959c48d234f7b39ac0ca1954a34d8b8e58a52064bdb3", 1731 + "sha256:a8b54949267bd5704324397efe9fbb6aa306466dee067550964e994d309db5f1", 1732 + "sha256:a96198d5d26f40557d986c1253bfe0e02d18c9d9b93cf389daf1a3c9f7c755fa", 1733 + "sha256:aebbd47df77190ada603157f0b3670d578c110c31746ecc5875c394fdcc59a99", 1734 + "sha256:af1107299cef049ad00a93df4809517be432283a0847bcae48343ebe5ea340dc", 1735 + "sha256:b63465b53baeaf2122a337d4ab57d6bbdd09fcadceb17a974cfa8a0300ad9c67", 1736 + "sha256:ba1c779b45a399cc25f511c681016626f69e51e45b9d350d7581998722825af9", 1737 + "sha256:bce00f3b1f7f644faae89677ca68645ed5365f1c7f874fdd5ebf730a69640d38", 1738 + "sha256:bfdf419bf5d3644f94cd7052954fc233522f5a1b371fc0b00219ebd9c14d5798", 1739 + "sha256:c1caa5763d1770216596e0a71b5567f27aac28c95992110212c108ec74589a48", 1740 + "sha256:c3e4e1f7b08d1ec6b685ccd3e2d762219c550164fbf524498532e39f9413436e", 1741 + "sha256:c85ab016e96a975afbdb9d49ca90f3bca9920ef27c64300843fe91c3d59d8d20", 1742 + "sha256:c924deab8105f86980983eced740433fb7554a7f66db73991affa4eda99d5402", 1743 + "sha256:d4f818f6371970d6a5d1e42878389bbfb69dcde631e4bbac5ec1cb11158565ca", 1744 + "sha256:d920401941cb898ef089422e889759dd403309eb370d0e54f1bdf6ca07fef603", 1745 + "sha256:da045bd1147d12bd43fb032296640a7cc17a7f2eaba67495988362e99db24fd2", 1746 + "sha256:dc3192a81ecd5ff954cecd690327badd5a84d00b877e1573f7c9097ce13e5bfb", 1747 + "sha256:ddae504cfb556fe220efae65e35be63cd11e3c314b202723fc2119ce19f0ca2e", 1748 + "sha256:de4544b1fb29cf14870c4e2b8a897c0242449f5dcebd3e0366aa0aa3cf58a23a", 1749 + "sha256:dea360778e0668a7ad25d7727d03364de8a45bfd5d808f81253516b9f2217765", 1750 + "sha256:e2254fe137c4a360b0a13173a56444f756252c9283ba4d267ca8e9081cd140ea", 1751 + "sha256:e64f0421892a207d3780903085c1b04efeb53b16803b23d947de5a7261b71355", 1752 + "sha256:e97a29b37830ba1262d8dfd48ddb5b28ad4d3ebecc5d93a9c7591d98641ec737", 1753 + "sha256:eacbcf30efaca7dc5cb264228ffecdb95fdb1e715b1ec937c0ce6b734161e0c8", 1754 + "sha256:eee5ff934b0c9f4537ff9596169d56cab1890918004791a7a06b879b3ba2a7ef", 1755 + "sha256:eff6bac402719c14e17efe845d6b98593c56c843aca6def72080fbede755fd1f", 1756 + "sha256:f10954b233d4df5cc3137ffa5ced97f8894152df817e5d149bf05a0ef2ab8134", 1757 + "sha256:f23bb1a7a6e8e8b612a164fdd08e683bcc16c76f928d6dbb7bdbee2374fbfee6", 1758 + "sha256:f494c01b28645c431239863cb17af8b8d15b93b0d697a0320d5dd34cd9d7c2fa", 1759 + "sha256:f6a071d2c3d39b4104f94fc08ab349e9b19b951ad4b8e3b6d7ea92d6ef7ccaf8", 1760 + "sha256:f736f54565f8dd7e3ab664fef2bc461d7593a389a7f28d4904af8d55a91bd55f", 1761 + "sha256:f8981a94a27ac520a398302afb74ae2c0be1c3d2d215c75c582186a006c9e7b0", 1762 + "sha256:fd24996e12e1ba7c397c44be75ca299da14cde34d74bc5508cce233676cc68d0", 1763 + "sha256:ff54340fc1129e8e181827e2234af3ff659b4f17d9bbe77f43bc19e6577fadec" 1765 1764 ], 1766 1765 "markers": "python_version >= '3.8'", 1767 - "version": "==1.11.1" 1766 + "version": "==1.12.1" 1768 1767 } 1769 1768 }, 1770 1769 "develop": { ··· 1791 1790 "markers": "python_version >= '3.8'", 1792 1791 "version": "==2.3.1" 1793 1792 }, 1794 - "black": { 1795 - "hashes": [ 1796 - "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6", 1797 - "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e", 1798 - "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f", 1799 - "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018", 1800 - "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e", 1801 - "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd", 1802 - "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4", 1803 - "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed", 1804 - "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2", 1805 - "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42", 1806 - "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af", 1807 - "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb", 1808 - "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368", 1809 - "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb", 1810 - "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af", 1811 - "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed", 1812 - "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47", 1813 - "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2", 1814 - "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a", 1815 - "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c", 1816 - "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920", 1817 - "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1" 1818 - ], 1819 - "index": "pypi", 1820 - "markers": "python_version >= '3.8'", 1821 - "version": "==24.8.0" 1822 - }, 1823 1793 "boto3": { 1824 1794 "hashes": [ 1825 - "sha256:97fcc1a14cbc759e4ba9535ced703a99fcf652c9c4b8dfcd06f292c80551684b", 1826 - "sha256:be7807f30f26d6c0057e45cfd09dad5968e664488bf4f9138d0bb7a0f6d8ed40" 1795 + "sha256:5df4e2cbe3409db07d3a0d8d63d5220ce3202a78206ad87afdbb41519b26ce45", 1796 + "sha256:b1cfad301184cdd44dfd4805187ccab12de8dd28dd12a11a5cfdace17918c6de" 1827 1797 ], 1828 1798 "index": "pypi", 1829 1799 "markers": "python_version >= '3.8'", 1830 - "version": "==1.35.24" 1800 + "version": "==1.35.25" 1831 1801 }, 1832 1802 "boto3-stubs": { 1833 1803 "extras": [ ··· 1838 1808 "sha256:55dc1e9b9a6c8456d18bd6747ecf30283d84da4c05d321e2233413b009e2a711", 1839 1809 "sha256:cece5d8ed36a5c587bfdcb97a1262678023f1a43c0aad54eeab9f389aefa99ec" 1840 1810 ], 1841 - "index": "pypi", 1842 1811 "markers": "python_version >= '3.8'", 1843 1812 "version": "==1.35.25" 1844 1813 }, 1845 1814 "botocore": { 1846 1815 "hashes": [ 1847 - "sha256:1e59b0f14f4890c4f70bd6a58a634b9464bed1c4c6171f87c8795d974ade614b", 1848 - "sha256:eb9ccc068255cc3d24c36693fda6aec7786db05ae6c2b13bcba66dce6a13e2e3" 1816 + "sha256:76c5706b2c6533000603ae8683a297c887abbbaf6ee31e1b2e2863b74b2989bc", 1817 + "sha256:e58d60260abf10ccc4417967923117c9902a6a0cff9fddb6ea7ff42dc1bd4630" 1849 1818 ], 1850 1819 "markers": "python_version >= '3.8'", 1851 - "version": "==1.35.24" 1820 + "version": "==1.35.25" 1852 1821 }, 1853 1822 "botocore-stubs": { 1854 1823 "hashes": [ ··· 1969 1938 ], 1970 1939 "markers": "python_full_version >= '3.7.0'", 1971 1940 "version": "==3.3.2" 1972 - }, 1973 - "click": { 1974 - "hashes": [ 1975 - "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", 1976 - "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" 1977 - ], 1978 - "markers": "python_version >= '3.7'", 1979 - "version": "==8.1.7" 1980 1941 }, 1981 1942 "coverage": { 1982 1943 "hashes": [ ··· 2138 2099 }, 2139 2100 "django-stubs": { 2140 2101 "hashes": [ 2141 - "sha256:78e3764488fdfd2695f12502136548ec22f8d4b1780541a835042b8238d11514", 2142 - "sha256:c2502f5ecbae50c68f9a86d52b5b2447d8648fd205036dad0ccb41e19a445927" 2102 + "sha256:86128c228b65e6c9a85e5dc56eb1c6f41125917dae0e21e6cfecdf1b27e630c5", 2103 + "sha256:b98d49a80aa4adf1433a97407102d068de26c739c405431d93faad96dd282c40" 2143 2104 ], 2144 2105 "markers": "python_version >= '3.8'", 2145 - "version": "==5.0.4" 2106 + "version": "==5.1.0" 2146 2107 }, 2147 2108 "django-stubs-ext": { 2148 2109 "hashes": [ 2149 - "sha256:85da065224204774208be29c7d02b4482d5a69218a728465c2fbe41725fdc819", 2150 - "sha256:910cbaff3d1e8e806a5c27d5ddd4088535aae8371ea921b7fd680fdfa5f14e30" 2110 + "sha256:a455fc222c90b30b29ad8c53319559f5b54a99b4197205ddbb385aede03b395d", 2111 + "sha256:ed7d51c0b731651879fc75f331fb0806d98b67bfab464e96e2724db6b46ef926" 2151 2112 ], 2152 2113 "markers": "python_version >= '3.8'", 2153 - "version": "==5.0.4" 2114 + "version": "==5.1.0" 2154 2115 }, 2155 2116 "djangorestframework-stubs": { 2156 2117 "hashes": [ ··· 2194 2155 "markers": "python_version >= '3.8'", 2195 2156 "version": "==3.16.1" 2196 2157 }, 2197 - "flake8": { 2198 - "hashes": [ 2199 - "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38", 2200 - "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213" 2201 - ], 2202 - "index": "pypi", 2203 - "markers": "python_full_version >= '3.8.1'", 2204 - "version": "==7.1.1" 2205 - }, 2206 2158 "freezegun": { 2207 2159 "hashes": [ 2208 2160 "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9", ··· 2245 2197 "markers": "python_version >= '3.10'", 2246 2198 "version": "==8.27.0" 2247 2199 }, 2248 - "isort": { 2249 - "hashes": [ 2250 - "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", 2251 - "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6" 2252 - ], 2253 - "index": "pypi", 2254 - "markers": "python_full_version >= '3.8.0'", 2255 - "version": "==5.13.2" 2256 - }, 2257 2200 "jedi": { 2258 2201 "hashes": [ 2259 2202 "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd", ··· 2344 2287 "markers": "python_version >= '3.8'", 2345 2288 "version": "==0.1.7" 2346 2289 }, 2347 - "mccabe": { 2348 - "hashes": [ 2349 - "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", 2350 - "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" 2351 - ], 2352 - "markers": "python_version >= '3.6'", 2353 - "version": "==0.7.0" 2354 - }, 2355 2290 "mypy": { 2356 2291 "hashes": [ 2357 2292 "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36", ··· 2409 2344 "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", 2410 2345 "version": "==1.9.1" 2411 2346 }, 2412 - "packaging": { 2413 - "hashes": [ 2414 - "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", 2415 - "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" 2416 - ], 2417 - "markers": "python_version >= '3.8'", 2418 - "version": "==24.1" 2419 - }, 2420 2347 "parso": { 2421 2348 "hashes": [ 2422 2349 "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", ··· 2424 2351 ], 2425 2352 "markers": "python_version >= '3.6'", 2426 2353 "version": "==0.8.4" 2427 - }, 2428 - "pathspec": { 2429 - "hashes": [ 2430 - "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", 2431 - "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" 2432 - ], 2433 - "markers": "python_version >= '3.8'", 2434 - "version": "==0.12.1" 2435 2354 }, 2436 2355 "pexpect": { 2437 2356 "hashes": [ ··· 2487 2406 ], 2488 2407 "markers": "python_version >= '3.8'", 2489 2408 "version": "==2.12.1" 2490 - }, 2491 - "pyflakes": { 2492 - "hashes": [ 2493 - "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", 2494 - "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a" 2495 - ], 2496 - "markers": "python_version >= '3.8'", 2497 - "version": "==3.2.0" 2498 2409 }, 2499 2410 "pygments": { 2500 2411 "hashes": [
+1 -1
care/abdm/api/serializers/abha_number.py
··· 4 4 from care.abdm.models import AbhaNumber 5 5 from care.facility.api.serializers.patient import PatientDetailSerializer 6 6 from care.facility.models import PatientRegistration 7 - from care.utils.serializer.external_id_field import ExternalIdSerializerField 7 + from care.utils.serializers.fields import ExternalIdSerializerField 8 8 9 9 10 10 class AbhaNumberSerializer(serializers.ModelSerializer):
+10 -11
care/audit_log/helpers.py
··· 1 + # ruff: noqa: SLF001 1 2 import re 2 3 from fnmatch import fnmatch 3 4 from functools import lru_cache ··· 14 15 def instance_finder(v): 15 16 return isinstance( 16 17 v, 17 - (list, dict, set), 18 + list | dict | set, 18 19 ) 19 20 20 21 ··· 41 42 42 43 def _make_search(item): 43 44 splits = item.split(":") 44 - if len(splits) == 2: 45 + if len(splits) == 2: # noqa: PLR2004 45 46 return Search(type=splits[0], value=splits[1]) 46 - else: 47 - return Search(type="plain", value=splits[0]) 47 + return Search(type="plain", value=splits[0]) 48 48 49 49 50 50 def candidate_in_scope( ··· 62 62 search_candidate = candidate 63 63 if is_application: 64 64 splits = candidate.split(".") 65 - if len(splits) == 2: 65 + if len(splits) == 2: # noqa: PLR2004 66 66 app_label, model_name = splits 67 67 search_candidate = app_label 68 68 ··· 91 91 ): 92 92 return True 93 93 94 - if candidate_in_scope( 95 - model_name, settings.AUDIT_LOG["models"]["exclude"]["models"] 96 - ): 97 - return True 98 - 99 - return False 94 + return bool( 95 + candidate_in_scope( 96 + model_name, settings.AUDIT_LOG["models"]["exclude"]["models"] 97 + ) 98 + ) 100 99 101 100 102 101 class MetaDataContainer(dict):
+9 -9
care/audit_log/middleware.py
··· 15 15 response: HttpResponse | None 16 16 exception: Exception | None 17 17 18 + 18 19 logger = logging.getLogger(__name__) 19 20 20 21 ··· 50 51 if not dal_request_id: 51 52 dal_request_id = ( 52 53 f"{request.method.lower()}::" 53 - f"{md5(request.path.lower().encode('utf-8')).hexdigest()}::" 54 + f"{md5(request.path.lower().encode('utf-8')).hexdigest()}::" # noqa: S324 54 55 f"{uuid.uuid4().hex}" 55 56 ) 56 57 request.dal_request_id = dal_request_id ··· 69 70 environ = RequestInformation(*AuditLogMiddleware.thread.__dal__) 70 71 if isinstance(environ.request.user, AnonymousUser): 71 72 return None 72 - else: 73 - return environ.request.user 73 + return environ.request.user 74 74 75 75 @staticmethod 76 76 def get_current_request(): ··· 85 85 response: HttpResponse = self.get_response(request) 86 86 self.save(request, response) 87 87 88 - if request.user: 89 - current_user_str = f"{request.user.id}|{request.user}" 90 - else: 91 - current_user_str = None 88 + current_user_str = f"{request.user.id}|{request.user}" if request.user else None 92 89 93 90 logger.info( 94 - f"{request.method} {request.path} {response.status_code} " 95 - f"User:[{current_user_str}]" 91 + "%s %s %s User:[%s]", 92 + request.method, 93 + request.path, 94 + response.status_code, 95 + current_user_str, 96 96 ) 97 97 return response 98 98
+17 -8
care/audit_log/receivers.py
··· 1 + # ruff: noqa: SLF001 1 2 import json 2 3 import logging 3 4 from typing import NamedTuple ··· 22 23 23 24 logger = logging.getLogger(__name__) 24 25 26 + 25 27 class Event(NamedTuple): 26 28 model: str 27 29 actor: AbstractUser ··· 42 44 43 45 model_name = get_model_name(instance) 44 46 if exclude_model(model_name): 45 - logger.debug(f"{model_name} ignored as per settings") 47 + logger.debug("%s ignored as per settings", model_name) 46 48 return 47 49 48 50 get_or_create_meta(instance) ··· 61 63 changes = {} 62 64 63 65 if operation not in {Operation.INSERT, Operation.DELETE}: 64 - old, new = remove_non_member_fields(pre.__dict__), remove_non_member_fields( 65 - instance.__dict__ 66 + old, new = ( 67 + remove_non_member_fields(pre.__dict__), 68 + remove_non_member_fields(instance.__dict__), 66 69 ) 67 70 68 71 try: ··· 107 110 model_name = get_model_name(instance) 108 111 109 112 if not event and operation != Operation.DELETE: 110 - logger.debug(f"Event not received for {operation}. Ignoring.") 113 + logger.debug("Event not received for %s. Ignoring.", operation) 111 114 return 112 115 113 116 try: ··· 118 121 else: 119 122 changes = json.dumps(event.changes if event else {}, cls=LogJsonEncoder) 120 123 except Exception: 121 - logger.warning(f"Failed to log {event}", exc_info=True) 124 + logger.warning("Failed to log %s", event, exc_info=True) 122 125 return 123 126 124 127 logger.info( 125 - f"AUDIT_LOG::{request_id}|{actor}|{operation.value}|{model_name}|ID:{instance.pk}|{changes}" 128 + "AUDIT_LOG::%s|%s|%s|%s|ID:%s|%s", 129 + request_id, 130 + actor, 131 + operation.value, 132 + model_name, 133 + instance.pk, 134 + changes, 126 135 ) 127 136 128 137 ··· 137 146 138 147 model_name = get_model_name(instance) 139 148 if exclude_model(model_name): 140 - logger.debug(f"Ignoring {model_name}.") 149 + logger.debug("Ignoring %s.", model_name) 141 150 return 142 151 143 152 operation = Operation.INSERT if created else Operation.UPDATE ··· 158 167 159 168 model_name = get_model_name(instance) 160 169 if exclude_model(model_name): 161 - logger.debug(f"Ignoring {model_name}.") 170 + logger.debug("Ignoring %s.", model_name) 162 171 return 163 172 164 173 event = instance._meta.dal.event
+3 -19
care/facility/admin.py
··· 55 55 56 56 def lookups(self, request, model_admin): 57 57 district = Facility.objects.values_list("district__name", flat=True) 58 - return list(map(lambda x: (x, x), set(district))) 58 + return [(x, x) for x in set(district)] 59 59 60 60 def queryset(self, request, queryset): 61 61 if self.value() is None: ··· 63 63 return queryset.filter(district__name=self.value()) 64 64 65 65 66 - # class LocalBodyFilter(SimpleListFilter): 67 - # """Local body filter""" 68 - 69 - # title = "Local body" 70 - # parameter_name = "local_body" 71 - 72 - # def lookups(self, request, model_admin): 73 - # local_body = Facility.objects.values_list("local_body__name", flat=True) 74 - # return list(map(lambda x: (x, x), set(local_body))) 75 - 76 - # def queryset(self, request, queryset): 77 - # if self.value() is None: 78 - # return queryset 79 - # return queryset.filter(local_body__name=self.value()) 80 - 81 - 82 66 class StateFilter(SimpleListFilter): 83 67 """State filter""" 84 68 ··· 87 71 88 72 def lookups(self, request, model_admin): 89 73 state = Facility.objects.values_list("state__name", flat=True) 90 - return list(map(lambda x: (x, x), set(state))) 74 + return [(x, x) for x in set(state)] 91 75 92 76 def queryset(self, request, queryset): 93 77 if self.value() is None: ··· 222 206 ) 223 207 224 208 class Meta: 225 - fields = "__all__" 209 + fields = ("flag", "facility") 226 210 model = FacilityFlag 227 211 228 212 form = FacilityFeatureFlagForm
+5 -7
care/facility/api/serializers/ambulance.py
··· 10 10 class AmbulanceDriverSerializer(serializers.ModelSerializer): 11 11 class Meta: 12 12 model = AmbulanceDriver 13 - exclude = TIMESTAMP_FIELDS + ("ambulance",) 13 + exclude = (*TIMESTAMP_FIELDS, "ambulance") 14 14 15 15 16 16 class AmbulanceSerializer(serializers.ModelSerializer): ··· 36 36 def validate(self, obj): 37 37 validated = super().validate(obj) 38 38 if not validated.get("price_per_km") and not validated.get("has_free_service"): 39 - raise ValidationError( 40 - "The ambulance must provide a price or be marked as free" 41 - ) 39 + msg = "The ambulance must provide a price or be marked as free" 40 + raise ValidationError(msg) 42 41 return validated 43 42 44 43 def create(self, validated_data): ··· 46 45 drivers = validated_data.pop("drivers", []) 47 46 validated_data.pop("created_by", None) 48 47 49 - ambulance = super(AmbulanceSerializer, self).create(validated_data) 48 + ambulance = super().create(validated_data) 50 49 51 50 for d in drivers: 52 51 d["ambulance"] = ambulance ··· 55 54 56 55 def update(self, instance, validated_data): 57 56 validated_data.pop("drivers", []) 58 - ambulance = super(AmbulanceSerializer, self).update(instance, validated_data) 59 - return ambulance 57 + return super().update(instance, validated_data) 60 58 61 59 62 60 class DeleteDriverSerializer(serializers.Serializer):
+15 -24
care/facility/api/serializers/asset.py
··· 1 - from datetime import datetime 2 - 3 1 from django.core.cache import cache 4 2 from django.db import models, transaction 5 3 from django.db.models import F, Value ··· 38 36 from care.utils.assetintegration.hl7monitor import HL7MonitorAsset 39 37 from care.utils.assetintegration.onvif import OnvifAsset 40 38 from care.utils.assetintegration.ventilator import VentilatorAsset 39 + from care.utils.models.validators import MiddlewareDomainAddressValidator 41 40 from care.utils.queryset.facility import get_facility_queryset 42 - from config.serializers import ChoiceField 43 - from config.validators import MiddlewareDomainAddressValidator 41 + from care.utils.serializers.fields import ChoiceField 44 42 45 43 46 44 class AssetLocationSerializer(ModelSerializer): ··· 125 123 ) 126 124 edit.save() 127 125 128 - updated_instance = super().update(instance, validated_data) 129 - 130 - return updated_instance 126 + return super().update(instance, validated_data) 131 127 132 128 133 129 @extend_schema_field( ··· 159 155 class Meta: 160 156 model = Asset 161 157 exclude = ("deleted", "external_id", "current_location") 162 - read_only_fields = TIMESTAMP_FIELDS + ( 163 - "resolved_middleware", 164 - "latest_status", 165 - ) 158 + read_only_fields = (*TIMESTAMP_FIELDS, "resolved_middleware", "latest_status") 166 159 167 160 def validate_qr_code_id(self, value): 168 161 value = value or None # treat empty string as null ··· 195 188 ): 196 189 del attrs["warranty_amc_end_of_validity"] 197 190 198 - elif warranty_amc_end_of_validity < datetime.now().date(): 199 - raise ValidationError( 200 - "Warranty/AMC end of validity cannot be in the past" 201 - ) 191 + elif warranty_amc_end_of_validity < now().date(): 192 + msg = "Warranty/AMC end of validity cannot be in the past" 193 + raise ValidationError(msg) 202 194 203 195 # validate that last serviced date is not in the future 204 - if attrs.get("last_serviced_on"): 205 - if attrs["last_serviced_on"] > datetime.now().date(): 206 - raise ValidationError("Last serviced on cannot be in the future") 196 + if attrs.get("last_serviced_on") and attrs["last_serviced_on"] > now().date(): 197 + msg = "Last serviced on cannot be in the future" 198 + raise ValidationError(msg) 207 199 208 200 # only allow setting asset class on creation (or updation if asset class is not set) 209 201 if ( ··· 250 242 .first() 251 243 ) 252 244 if asset_using_ip: 253 - raise ValidationError( 254 - f"IP Address {ip_address} is already in use by {asset_using_ip.name} asset" 255 - ) 245 + msg = f"IP Address {ip_address} is already in use by {asset_using_ip.name} asset" 246 + raise ValidationError(msg) 256 247 257 248 return super().validate(attrs) 258 249 ··· 361 352 data["ip_address"] = instance.meta.get("local_ip_address") 362 353 if camera_access_key := instance.meta.get("camera_access_key"): 363 354 values = camera_access_key.split(":") 364 - if len(values) == 3: 355 + if len(values) == 3: # noqa: PLR2004 365 356 data["username"], data["password"], data["access_key"] = values 366 357 return data 367 358 ··· 416 407 417 408 418 409 class AssetActionSerializer(Serializer): 419 - def actionChoices(): 410 + def action_choices(): 420 411 actions = [ 421 412 OnvifAsset.OnvifActions, 422 413 HL7MonitorAsset.HL7MonitorActions, ··· 428 419 return choices 429 420 430 421 type = ChoiceField( 431 - choices=actionChoices(), 422 + choices=action_choices(), 432 423 required=True, 433 424 ) 434 425 data = JSONField(required=False)
+20 -12
care/facility/api/serializers/bed.py
··· 30 30 from care.utils.assetintegration.asset_classes import AssetClasses 31 31 from care.utils.queryset.consultation import get_consultation_queryset 32 32 from care.utils.queryset.facility import get_facility_queryset 33 - from care.utils.serializer.external_id_field import ExternalIdSerializerField 34 - from config.serializers import ChoiceField 33 + from care.utils.serializers.fields import ChoiceField, ExternalIdSerializerField 35 34 36 35 37 36 class BedSerializer(ModelSerializer): ··· 51 50 return value.strip() if value else value 52 51 53 52 def validate_number_of_beds(self, value): 54 - if value > 100: 55 - raise ValidationError("Cannot create more than 100 beds at once.") 53 + max_beds = 100 54 + if value > max_beds: 55 + msg = f"Cannot create more than {max_beds} beds at once." 56 + raise ValidationError(msg) 56 57 return value 57 58 58 59 class Meta: ··· 152 153 ).first() 153 154 if patient: 154 155 return PatientListSerializer(patient).data 156 + return None 155 157 156 158 class Meta: 157 159 model = AssetBed 158 - exclude = ("external_id", "id") + TIMESTAMP_FIELDS 160 + exclude = ("external_id", "id", *TIMESTAMP_FIELDS) 159 161 160 162 161 163 class ConsultationBedSerializer(ModelSerializer): ··· 179 181 exclude = ("deleted", "external_id") 180 182 read_only_fields = TIMESTAMP_FIELDS 181 183 182 - def validate(self, attrs): 184 + def validate(self, attrs): # noqa: PLR0912 183 185 if "consultation" not in attrs: 184 186 raise ValidationError({"consultation": "This field is required."}) 185 187 if "bed" not in attrs: ··· 192 194 193 195 facilities = get_facility_queryset(user) 194 196 if not facilities.filter(id=bed.facility_id).exists(): 195 - raise ValidationError("You do not have access to this facility") 197 + msg = "You do not have access to this facility" 198 + raise ValidationError(msg) 196 199 197 200 permitted_consultations = get_consultation_queryset(user).select_related( 198 201 "patient" ··· 205 208 or consultation.discharge_date 206 209 or consultation.death_datetime 207 210 ): 208 - raise ValidationError("Patient not active") 211 + msg = "Patient not active" 212 + raise ValidationError(msg) 209 213 210 214 # bed validations 211 215 if consultation.facility_id != bed.facility_id: 212 - raise ValidationError("Consultation and bed are not in the same facility") 216 + msg = "Consultation and bed are not in the same facility" 217 + raise ValidationError(msg) 213 218 if ( 214 219 ConsultationBed.objects.filter(bed=bed, end_date__isnull=True) 215 220 .exclude(consultation=consultation) 216 221 .exists() 217 222 ): 218 - raise ValidationError("Bed is already in use") 223 + msg = "Bed is already in use" 224 + raise ValidationError(msg) 219 225 220 226 # check whether the same set of bed and assets are already assigned 221 227 current_consultation_bed = consultation.current_bed ··· 230 236 ) 231 237 == set(attrs.get("assets", [])) 232 238 ): 233 - raise ValidationError("These set of bed and assets are already assigned") 239 + msg = "These set of bed and assets are already assigned" 240 + raise ValidationError(msg) 234 241 235 242 # date validations 236 243 # note: end_date is for setting end date on current instance ··· 323 330 ) 324 331 not_found_assets = set(assets_ids) - set(assets) 325 332 if not_found_assets: 326 - raise ValidationError( 333 + msg = ( 327 334 "Some assets are not available - " 328 335 f"{' ,'.join([str(x) for x in not_found_assets])}" 329 336 ) 337 + raise ValidationError(msg) 330 338 obj: ConsultationBed = super().create(validated_data) 331 339 if assets_ids: 332 340 asset_objects = Asset.objects.filter(external_id__in=assets_ids).only(
+10 -9
care/facility/api/serializers/consultation_diagnosis.py
··· 14 14 class ConsultationCreateDiagnosisSerializer(serializers.ModelSerializer): 15 15 def validate_verification_status(self, value): 16 16 if value in INACTIVE_CONDITION_VERIFICATION_STATUSES: 17 - raise serializers.ValidationError("Verification status not allowed") 17 + msg = "Verification status not allowed" 18 + raise serializers.ValidationError(msg) 18 19 return value 19 20 20 21 class Meta: ··· 54 55 55 56 def validate_diagnosis(self, value): 56 57 if self.instance and value != self.instance.diagnosis: 57 - raise serializers.ValidationError("Diagnosis cannot be changed") 58 + msg = "Diagnosis cannot be changed" 59 + raise serializers.ValidationError(msg) 58 60 59 61 if ( 60 62 not self.instance ··· 63 65 diagnosis=value, 64 66 ).exists() 65 67 ): 66 - raise serializers.ValidationError( 67 - "Diagnosis already exists for consultation" 68 - ) 68 + msg = "Diagnosis already exists for consultation" 69 + raise serializers.ValidationError(msg) 69 70 70 71 return value 71 72 72 73 def validate_verification_status(self, value): 73 74 if not self.instance and value in INACTIVE_CONDITION_VERIFICATION_STATUSES: 74 - raise serializers.ValidationError("Verification status not allowed") 75 + msg = "Verification status not allowed" 76 + raise serializers.ValidationError(msg) 75 77 return value 76 78 77 79 def validate_is_principal(self, value): ··· 87 89 qs = qs.exclude(id=self.instance.id) 88 90 89 91 if qs.exists(): 90 - raise serializers.ValidationError( 91 - "Consultation already has a principal diagnosis. Unset the existing principal diagnosis first." 92 - ) 92 + msg = "Consultation already has a principal diagnosis. Unset the existing principal diagnosis first." 93 + raise serializers.ValidationError(msg) 93 94 94 95 return value 95 96
+25 -24
care/facility/api/serializers/daily_round.py
··· 1 1 from datetime import timedelta 2 + from typing import TYPE_CHECKING 2 3 3 4 from django.db import transaction 4 5 from django.utils import timezone ··· 16 17 from care.facility.models.daily_round import DailyRound 17 18 from care.facility.models.notification import Notification 18 19 from care.facility.models.patient_base import SuggestionChoices 19 - from care.facility.models.patient_consultation import PatientConsultation 20 20 from care.users.api.serializers.user import UserBaseMinimumSerializer 21 21 from care.utils.notification_handler import NotificationGenerator 22 22 from care.utils.queryset.facility import get_home_facility_queryset 23 - from config.serializers import ChoiceField 23 + from care.utils.serializers.fields import ChoiceField 24 + 25 + if TYPE_CHECKING: 26 + from care.facility.models.patient_consultation import PatientConsultation 24 27 25 28 26 29 class DailyRoundSerializer(serializers.ModelSerializer): ··· 102 105 last_edited_by = UserBaseMinimumSerializer(read_only=True) 103 106 created_by = UserBaseMinimumSerializer(read_only=True) 104 107 105 - # bed_object = BedSerializer(read_only=True) 106 - 107 108 class Meta: 108 109 model = DailyRound 109 110 read_only_fields = ( ··· 120 121 if value is not None: 121 122 sys, dia = value.get("systolic"), value.get("diastolic") 122 123 if sys is not None and dia is not None and sys < dia: 123 - raise ValidationError("Systolic must be greater than diastolic") 124 + msg = "Systolic must be greater than diastolic" 125 + raise ValidationError(msg) 124 126 return value 125 127 126 128 def update(self, instance, validated_data): ··· 295 297 {"consultation": ["Discharged Consultation data cannot be updated"]} 296 298 ) 297 299 298 - if "action" in validated: 299 - if validated["action"] == PatientRegistration.ActionEnum.REVIEW: 300 - if "consultation__review_interval" not in validated: 301 - raise ValidationError( 302 - { 303 - "review_interval": [ 304 - "This field is required as the patient has been requested Review." 305 - ] 306 - } 307 - ) 308 - if validated["consultation__review_interval"] <= 0: 309 - raise ValidationError( 310 - { 311 - "review_interval": [ 312 - "This field value is must be greater than 0." 313 - ] 314 - } 315 - ) 300 + if ( 301 + "action" in validated 302 + and validated["action"] == PatientRegistration.ActionEnum.REVIEW 303 + ): 304 + if "consultation__review_interval" not in validated: 305 + raise ValidationError( 306 + { 307 + "review_interval": [ 308 + "This field is required as the patient has been requested Review." 309 + ] 310 + } 311 + ) 312 + if validated["consultation__review_interval"] <= 0: 313 + raise ValidationError( 314 + {"review_interval": ["This field value is must be greater than 0."]} 315 + ) 316 316 317 317 if "bed" in validated: 318 318 external_id = validated.pop("bed")["external_id"] ··· 327 327 328 328 def validate_taken_at(self, value): 329 329 if value and value > timezone.now(): 330 - raise serializers.ValidationError("Cannot create an update in the future") 330 + msg = "Cannot create an update in the future" 331 + raise serializers.ValidationError(msg) 331 332 return value
+6 -6
care/facility/api/serializers/encounter_symptom.py
··· 33 33 34 34 def validate_onset_date(self, value): 35 35 if value and value > now(): 36 - raise serializers.ValidationError("Onset date cannot be in the future") 36 + msg = "Onset date cannot be in the future" 37 + raise serializers.ValidationError(msg) 37 38 return value 38 39 39 40 def validate(self, attrs): ··· 49 50 if self.instance 50 51 else validated_data.get("onset_date") 51 52 ) 52 - if cure_date := validated_data.get("cure_date"): 53 - if cure_date < onset_date: 54 - raise serializers.ValidationError( 55 - {"cure_date": "Cure date should be after onset date"} 56 - ) 53 + if validated_data.get("cure_date") and validated_data["cure_date"] < onset_date: 54 + raise serializers.ValidationError( 55 + {"cure_date": "Cure date should be after onset date"} 56 + ) 57 57 58 58 if validated_data.get("symptom") != Symptom.OTHERS and validated_data.get( 59 59 "other_symptom"
+10 -14
care/facility/api/serializers/facility.py
··· 14 14 ) 15 15 from care.utils.file_uploads.cover_image import upload_cover_image 16 16 from care.utils.models.validators import ( 17 + MiddlewareDomainAddressValidator, 17 18 cover_image_validator, 18 19 custom_image_extension_validator, 19 20 ) 20 - from care.utils.serializer.external_id_field import ExternalIdSerializerField 21 - from config.serializers import ChoiceField 22 - from config.validators import MiddlewareDomainAddressValidator 21 + from care.utils.serializers.fields import ChoiceField, ExternalIdSerializerField 23 22 24 23 User = get_user_model() 25 24 ··· 97 96 """Serializer for facility.models.Facility.""" 98 97 99 98 facility_type = ChoiceField(choices=FACILITY_TYPES) 100 - # A valid location => { 101 - # "latitude": 49.8782482189424, 102 - # "longitude": 24.452545489 103 - # } 104 99 read_cover_image_url = serializers.URLField(read_only=True) 105 - # location = PointField(required=False) 106 100 features = serializers.ListField( 107 101 child=serializers.ChoiceField(choices=FEATURE_CHOICES), 108 102 required=False, ··· 155 149 156 150 def validate_middleware_address(self, value): 157 151 if not value: 158 - raise serializers.ValidationError("Middleware Address is required") 152 + msg = "Middleware Address is required" 153 + raise serializers.ValidationError(msg) 159 154 value = value.strip() 160 155 if not value: 161 156 return value ··· 166 161 167 162 def validate_features(self, value): 168 163 if len(value) != len(set(value)): 169 - raise serializers.ValidationError( 170 - "Features should not contain duplicate values." 171 - ) 164 + msg = "Features should not contain duplicate values." 165 + raise serializers.ValidationError(msg) 172 166 return value 173 167 174 168 def create(self, validated_data): ··· 211 205 hub: Facility = self.context["facility"] 212 206 213 207 if hub == spoke: 214 - raise serializers.ValidationError("Cannot set a facility as it's own spoke") 208 + msg = "Cannot set a facility as it's own spoke" 209 + raise serializers.ValidationError(msg) 215 210 216 211 if FacilityHubSpoke.objects.filter( 217 212 Q(hub=hub, spoke=spoke) | Q(hub=spoke, spoke=hub) 218 213 ).first(): 219 - raise serializers.ValidationError("Facility is already a spoke/hub") 214 + msg = "Facility is already a spoke/hub" 215 + raise serializers.ValidationError(msg) 220 216 221 217 return spoke 222 218
+2 -2
care/facility/api/serializers/facility_capacity.py
··· 2 2 3 3 from care.facility.api.serializers import TIMESTAMP_FIELDS 4 4 from care.facility.models import FacilityCapacity, RoomType 5 - from config.serializers import ChoiceField 5 + from care.utils.serializers.fields import ChoiceField 6 6 7 7 8 8 class FacilityCapacitySerializer(serializers.ModelSerializer): ··· 44 44 super().__init__() 45 45 46 46 class Meta: 47 - exclude = TIMESTAMP_FIELDS + ("facility",) 47 + exclude = (*TIMESTAMP_FIELDS, "facility")
+55 -47
care/facility/api/serializers/file_upload.py
··· 16 16 from care.users.api.serializers.user import UserBaseMinimumSerializer 17 17 from care.users.models import User 18 18 from care.utils.notification_handler import NotificationGenerator 19 - from config.serializers import ChoiceField 19 + from care.utils.serializers.fields import ChoiceField 20 20 21 21 22 - def check_permissions(file_type, associating_id, user, action="create"): 22 + def check_permissions(file_type, associating_id, user, action="create"): # noqa: PLR0911, PLR0912 23 23 try: 24 24 if file_type == FileUpload.FileType.PATIENT.value: 25 25 patient = PatientRegistration.objects.get(external_id=associating_id) ··· 27 27 raise serializers.ValidationError( 28 28 {"patient": "Cannot upload file for a discharged patient."} 29 29 ) 30 - if patient.assigned_to: 31 - if user == patient.assigned_to: 32 - return patient.id 33 - if patient.last_consultation: 34 - if patient.last_consultation.assigned_to: 35 - if user == patient.last_consultation.assigned_to: 36 - return patient.id 30 + if patient.assigned_to and user == patient.assigned_to: 31 + return patient.id 32 + if ( 33 + patient.last_consultation 34 + and patient.last_consultation.assigned_to 35 + and user == patient.last_consultation.assigned_to 36 + ): 37 + return patient.id 37 38 if not has_facility_permission(user, patient.facility): 38 - raise Exception("No Permission") 39 + msg = "No Permission" 40 + raise Exception(msg) 39 41 return patient.id 40 - elif file_type == FileUpload.FileType.CONSULTATION.value: 42 + if file_type == FileUpload.FileType.CONSULTATION.value: 41 43 consultation = PatientConsultation.objects.get(external_id=associating_id) 42 44 if consultation.discharge_date and not action == "read": 43 45 raise serializers.ValidationError( ··· 45 47 "consultation": "Cannot upload file for a discharged consultation." 46 48 } 47 49 ) 48 - if consultation.patient.assigned_to: 49 - if user == consultation.patient.assigned_to: 50 - return consultation.id 51 - if consultation.assigned_to: 52 - if user == consultation.assigned_to: 53 - return consultation.id 50 + if ( 51 + consultation.patient.assigned_to 52 + and user == consultation.patient.assigned_to 53 + ): 54 + return consultation.id 55 + if consultation.assigned_to and user == consultation.assigned_to: 56 + return consultation.id 54 57 if not ( 55 58 has_facility_permission(user, consultation.patient.facility) 56 59 or has_facility_permission(user, consultation.facility) 57 60 ): 58 - raise Exception("No Permission") 61 + msg = "No Permission" 62 + raise Exception(msg) 59 63 return consultation.id 60 - elif file_type == FileUpload.FileType.CONSENT_RECORD.value: 64 + if file_type == FileUpload.FileType.CONSENT_RECORD.value: 61 65 consultation = PatientConsent.objects.get( 62 66 external_id=associating_id 63 67 ).consultation ··· 68 72 } 69 73 ) 70 74 if ( 71 - user == consultation.assigned_to 72 - or user == consultation.patient.assigned_to 75 + user in (consultation.assigned_to, consultation.patient.assigned_to) 73 76 or has_facility_permission(user, consultation.facility) 74 77 or has_facility_permission(user, consultation.patient.facility) 75 78 ): 76 79 return associating_id 77 - raise Exception("No Permission") 78 - elif file_type == FileUpload.FileType.DISCHARGE_SUMMARY.value: 80 + msg = "No Permission" 81 + raise Exception(msg) 82 + if file_type == FileUpload.FileType.DISCHARGE_SUMMARY.value: 79 83 consultation = PatientConsultation.objects.get(external_id=associating_id) 80 84 if ( 81 85 consultation.patient.assigned_to ··· 88 92 has_facility_permission(user, consultation.patient.facility) 89 93 or has_facility_permission(user, consultation.facility) 90 94 ): 91 - raise Exception("No Permission") 95 + msg = "No Permission" 96 + raise Exception(msg) 92 97 return consultation.external_id 93 - elif file_type == FileUpload.FileType.SAMPLE_MANAGEMENT.value: 98 + if file_type == FileUpload.FileType.SAMPLE_MANAGEMENT.value: 94 99 sample = PatientSample.objects.get(external_id=associating_id) 95 100 patient = sample.patient 96 - if patient.assigned_to: 97 - if user == patient.assigned_to: 98 - return sample.id 99 - if sample.consultation: 100 - if sample.consultation.assigned_to: 101 - if user == sample.consultation.assigned_to: 102 - return sample.id 103 - if sample.testing_facility: 104 - if has_facility_permission( 105 - user, 106 - Facility.objects.get( 107 - external_id=sample.testing_facility.external_id 108 - ), 109 - ): 110 - return sample.id 101 + if patient.assigned_to and user == patient.assigned_to: 102 + return sample.id 103 + if ( 104 + sample.consultation 105 + and sample.consultation.assigned_to 106 + and user == sample.consultation.assigned_to 107 + ): 108 + return sample.id 109 + if sample.testing_facility and has_facility_permission( 110 + user, 111 + Facility.objects.get(external_id=sample.testing_facility.external_id), 112 + ): 113 + return sample.id 111 114 if not has_facility_permission(user, patient.facility): 112 - raise Exception("No Permission") 115 + msg = "No Permission" 116 + raise Exception(msg) 113 117 return sample.id 114 - elif file_type == FileUpload.FileType.CLAIM.value or file_type == FileUpload.FileType.COMMUNICATION.value: 118 + if file_type in ( 119 + FileUpload.FileType.CLAIM.value, 120 + FileUpload.FileType.COMMUNICATION.value, 121 + ): 115 122 return associating_id 116 - else: 117 - raise Exception("Undefined File Type") 123 + msg = "Undefined File Type" 124 + raise Exception(msg) 118 125 119 - except Exception: 120 - raise serializers.ValidationError({"permission": "denied"}) 126 + except Exception as e: 127 + raise serializers.ValidationError({"permission": "denied"}) from e 121 128 122 129 123 130 class FileUploadCreateSerializer(serializers.ModelSerializer): ··· 248 255 def validate(self, attrs): 249 256 validated = super().validate(attrs) 250 257 if validated.get("is_archived") and not validated.get("archive_reason"): 251 - raise ValidationError("Archive reason must be specified.") 258 + msg = "Archive reason must be specified." 259 + raise ValidationError(msg) 252 260 return validated 253 261 254 262
+2 -2
care/facility/api/serializers/hospital_doctor.py
··· 2 2 3 3 from care.facility.api.serializers import TIMESTAMP_FIELDS 4 4 from care.facility.models import DOCTOR_TYPES, HospitalDoctors 5 - from config.serializers import ChoiceField 5 + from care.utils.serializers.fields import ChoiceField 6 6 7 7 8 8 class HospitalDoctorSerializer(serializers.ModelSerializer): ··· 15 15 "id", 16 16 "area_text", 17 17 ) 18 - exclude = TIMESTAMP_FIELDS + ("facility", "external_id") 18 + exclude = (*TIMESTAMP_FIELDS, "facility", "external_id")
+8 -9
care/facility/api/serializers/inventory.py
··· 68 68 69 69 try: 70 70 item.allowed_units.get(id=unit.id) 71 - except FacilityInventoryUnit.DoesNotExist: 71 + except FacilityInventoryUnit.DoesNotExist as e: 72 72 raise serializers.ValidationError( 73 73 {"unit": ["Item cannot be measured with unit"]} 74 - ) 74 + ) from e 75 75 76 76 multiplier = 1 77 77 ··· 80 80 multiplier = FacilityInventoryUnitConverter.objects.get( 81 81 from_unit=unit, to_unit=item.default_unit 82 82 ).multiplier 83 - except FacilityInventoryUnitConverter.DoesNotExist: 83 + except FacilityInventoryUnitConverter.DoesNotExist as e: 84 84 raise serializers.ValidationError( 85 85 {"item": ["Please Ask Admin to Add Conversion Metrics"]} 86 - ) 86 + ) from e 87 87 88 88 validated_data["created_by"] = self.context["request"].user 89 89 ··· 197 197 198 198 try: 199 199 instance = super().create(validated_data) 200 - except IntegrityError: 200 + except IntegrityError as e: 201 201 raise serializers.ValidationError( 202 202 {"item": ["Item min quantity already set"]} 203 - ) 203 + ) from e 204 204 205 205 try: 206 206 summary_obj = FacilityInventorySummary.objects.get( ··· 214 214 return instance 215 215 216 216 def update(self, instance, validated_data): 217 - if "item" in validated_data: 218 - if instance.item != validated_data["item"]: 219 - raise serializers.ValidationError({"item": ["Item cannot be Changed"]}) 217 + if "item" in validated_data and instance.item != validated_data["item"]: 218 + raise serializers.ValidationError({"item": ["Item cannot be Changed"]}) 220 219 221 220 item = validated_data["item"] 222 221
+1 -1
care/facility/api/serializers/notification.py
··· 2 2 3 3 from care.facility.models.notification import Notification 4 4 from care.users.api.serializers.user import UserBaseMinimumSerializer 5 - from config.serializers import ChoiceField 5 + from care.utils.serializers.fields import ChoiceField 6 6 7 7 8 8 class NotificationSerializer(serializers.ModelSerializer):
+28 -42
care/facility/api/serializers/patient.py
··· 1 - import datetime 2 - 3 1 from django.conf import settings 4 2 from django.db import transaction 5 - from django.utils.timezone import make_aware, now 3 + from django.utils.timezone import now 6 4 from rest_framework import serializers 7 5 8 6 from care.facility.api.serializers import TIMESTAMP_FIELDS ··· 46 44 from care.users.models import User 47 45 from care.utils.notification_handler import NotificationGenerator 48 46 from care.utils.queryset.facility import get_home_facility_queryset 49 - from care.utils.serializer.external_id_field import ExternalIdSerializerField 50 - from config.serializers import ChoiceField 47 + from care.utils.serializers.fields import ChoiceField, ExternalIdSerializerField 51 48 52 49 53 50 class PatientMetaInfoSerializer(serializers.ModelSerializer): ··· 102 99 "allergies", 103 100 "external_id", 104 101 ) 105 - read_only = TIMESTAMP_FIELDS + ("death_datetime",) 102 + read_only = (*TIMESTAMP_FIELDS, "death_datetime") 106 103 107 104 108 105 class PatientContactDetailsSerializer(serializers.ModelSerializer): ··· 144 141 145 142 last_consultation = PatientConsultationSerializer(read_only=True) 146 143 facility_object = FacilitySerializer(source="facility", read_only=True) 147 - # nearest_facility_object = FacilitySerializer( 148 - # source="nearest_facility", read_only=True 149 - # ) 150 144 151 145 source = ChoiceField( 152 146 choices=PatientRegistration.SourceChoices, ··· 170 164 last_edited = UserBaseMinimumSerializer(read_only=True) 171 165 created_by = UserBaseMinimumSerializer(read_only=True) 172 166 vaccine_name = serializers.ChoiceField( 173 - choices=PatientRegistration.vaccineChoices, required=False, allow_null=True 167 + choices=PatientRegistration.VaccineChoices, required=False, allow_null=True 174 168 ) 175 169 176 170 assigned_to_object = UserBaseMinimumSerializer(source="assigned_to", read_only=True) ··· 189 183 "external_id", 190 184 ) 191 185 include = ("contacted_patients",) 192 - read_only = TIMESTAMP_FIELDS + ( 186 + read_only = ( 187 + *TIMESTAMP_FIELDS, 193 188 "last_edited", 194 189 "created_by", 195 190 "is_active", 196 191 "death_datetime", 197 192 ) 198 193 199 - # def get_last_consultation(self, obj): 200 - # last_consultation = PatientConsultation.objects.filter(patient=obj).last() 201 - # if not last_consultation: 202 - # return None 203 - # return PatientConsultationSerializer(last_consultation).data 204 - 205 - # def validate_facility(self, value): 206 - # if value is not None and Facility.objects.filter(external_id=value).first() is None: 207 - # raise serializers.ValidationError("facility not found") 208 - # return value 209 - 210 194 def validate_countries_travelled(self, value): 211 195 if not value: 212 196 value = [] ··· 216 200 217 201 def validate_date_of_birth(self, value): 218 202 if value and value > now().date(): 219 - raise serializers.ValidationError("Enter a valid DOB such that age > 0") 203 + msg = "Enter a valid DOB such that age > 0" 204 + raise serializers.ValidationError(msg) 220 205 return value 221 206 222 207 def validate_year_of_birth(self, value): 223 208 if value and value > now().year: 224 - raise serializers.ValidationError("Enter a valid year of birth") 209 + msg = "Enter a valid year of birth" 210 + raise serializers.ValidationError(msg) 225 211 return value 226 212 227 213 def validate(self, attrs): ··· 239 225 240 226 if validated.get("is_vaccinated"): 241 227 if validated.get("number_of_doses") == 0: 242 - raise serializers.ValidationError("Number of doses cannot be 0") 228 + msg = "Number of doses cannot be 0" 229 + raise serializers.ValidationError(msg) 243 230 if validated.get("vaccine_name") is None: 244 - raise serializers.ValidationError("Vaccine name cannot be null") 231 + msg = "Vaccine name cannot be null" 232 + raise serializers.ValidationError(msg) 245 233 246 234 return validated 247 235 ··· 276 264 277 265 # Authorisation checks end 278 266 279 - if "srf_id" in validated_data: 280 - if validated_data["srf_id"]: 281 - self.check_external_entry(validated_data["srf_id"]) 267 + if validated_data.get("srf_id"): 268 + self.check_external_entry(validated_data["srf_id"]) 282 269 283 270 validated_data["created_by"] = self.context["request"].user 284 271 patient = super().create(validated_data) ··· 325 312 external_id=external_id 326 313 ).id 327 314 328 - if "srf_id" in validated_data: 329 - if instance.srf_id != validated_data["srf_id"]: 330 - self.check_external_entry(validated_data["srf_id"]) 315 + if ( 316 + "srf_id" in validated_data 317 + and instance.srf_id != validated_data["srf_id"] 318 + ): 319 + self.check_external_entry(validated_data["srf_id"]) 331 320 332 321 patient = super().update(instance, validated_data) 333 322 Disease.objects.filter(patient=patient).update(deleted=True) ··· 371 360 372 361 class FacilityPatientStatsHistorySerializer(serializers.ModelSerializer): 373 362 id = serializers.CharField(source="external_id", read_only=True) 374 - entry_date = serializers.DateField( 375 - default=make_aware(datetime.datetime.today()).date() 376 - ) 363 + entry_date = serializers.DateField(default=lambda: now().date()) 377 364 facility = ExternalIdSerializerField( 378 365 queryset=Facility.objects.all(), read_only=True 379 366 ) ··· 430 417 431 418 def validate_year_of_birth(self, value): 432 419 if self.instance and self.instance.year_of_birth != value: 433 - raise serializers.ValidationError("Year of birth does not match") 420 + msg = "Year of birth does not match" 421 + raise serializers.ValidationError(msg) 434 422 return value 435 423 436 424 def create(self, validated_data): ··· 531 519 if validated_data.get("reply_to"): 532 520 reply_to_note = validated_data["reply_to"] 533 521 if reply_to_note.thread != validated_data["thread"]: 534 - raise serializers.ValidationError( 535 - "Reply to note should be in the same thread" 536 - ) 522 + msg = "Reply to note should be in the same thread" 523 + raise serializers.ValidationError(msg) 537 524 if reply_to_note.consultation != validated_data.get("consultation"): 538 - raise serializers.ValidationError( 539 - "Reply to note should be in the same consultation" 540 - ) 525 + msg = "Reply to note should be in the same consultation" 526 + raise serializers.ValidationError(msg) 541 527 542 528 user = self.context["request"].user 543 529 note = validated_data.get("note")
+91 -90
care/facility/api/serializers/patient_consultation.py
··· 66 66 from care.utils.lock import Lock 67 67 from care.utils.notification_handler import NotificationGenerator 68 68 from care.utils.queryset.facility import get_home_facility_queryset 69 - from care.utils.serializer.external_id_field import ExternalIdSerializerField 70 - from config.serializers import ChoiceField 69 + from care.utils.serializers.fields import ChoiceField, ExternalIdSerializerField 71 70 72 71 MIN_ENCOUNTER_DATE = make_aware(settings.MIN_ENCOUNTER_DATE) 73 72 ··· 196 195 197 196 class Meta: 198 197 model = PatientConsultation 199 - read_only_fields = TIMESTAMP_FIELDS + ( 198 + read_only_fields = ( 199 + *TIMESTAMP_FIELDS, 200 200 "last_updated_by_telemedicine", 201 201 "discharge_date", 202 202 "last_edited_by", ··· 227 227 raise ValidationError( 228 228 {"consultation": ["Discharged Consultation data cannot be updated"]} 229 229 ) 230 - else: 231 - instance.medico_legal_case = validated_data.pop("medico_legal_case") 232 - instance.save() 233 - return instance 230 + instance.medico_legal_case = validated_data.pop("medico_legal_case") 231 + instance.save() 232 + return instance 234 233 235 234 if instance.suggestion == SuggestionChoices.OP: 236 235 instance.discharge_date = localtime(now()) ··· 259 258 self.context["request"].user == instance.assigned_to 260 259 ) 261 260 262 - if "is_kasp" in validated_data: 263 - if validated_data["is_kasp"] and (not instance.is_kasp): 264 - validated_data["kasp_enabled_date"] = localtime(now()) 261 + if ( 262 + "is_kasp" in validated_data 263 + and validated_data["is_kasp"] 264 + and (not instance.is_kasp) 265 + ): 266 + validated_data["kasp_enabled_date"] = localtime(now()) 265 267 266 268 _temp = instance.assigned_to 267 269 ··· 275 277 old=old_instance, 276 278 ) 277 279 278 - if "assigned_to" in validated_data: 279 - if validated_data["assigned_to"] != _temp and validated_data["assigned_to"]: 280 - NotificationGenerator( 281 - event=Notification.Event.PATIENT_CONSULTATION_ASSIGNMENT, 282 - caused_by=self.context["request"].user, 283 - caused_object=instance, 284 - facility=instance.patient.facility, 285 - notification_mediums=[ 286 - Notification.Medium.SYSTEM, 287 - Notification.Medium.WHATSAPP, 288 - ], 289 - ).generate() 280 + if ( 281 + "assigned_to" in validated_data 282 + and validated_data["assigned_to"] != _temp 283 + and validated_data["assigned_to"] 284 + ): 285 + NotificationGenerator( 286 + event=Notification.Event.PATIENT_CONSULTATION_ASSIGNMENT, 287 + caused_by=self.context["request"].user, 288 + caused_object=instance, 289 + facility=instance.patient.facility, 290 + notification_mediums=[ 291 + Notification.Medium.SYSTEM, 292 + Notification.Medium.WHATSAPP, 293 + ], 294 + ).generate() 290 295 291 296 NotificationGenerator( 292 297 event=Notification.Event.PATIENT_CONSULTATION_UPDATED, ··· 297 302 298 303 return consultation 299 304 300 - def create(self, validated_data): 305 + def create(self, validated_data): # noqa: PLR0915 PLR0912 301 306 if route_to_facility := validated_data.get("route_to_facility"): 302 307 if route_to_facility == RouteToFacility.OUTPATIENT: 303 308 validated_data["icu_admission_date"] = None ··· 387 392 {"consultation": "Exists please Edit Existing Consultation"} 388 393 ) 389 394 390 - if "is_kasp" in validated_data: 391 - if validated_data["is_kasp"]: 392 - validated_data["kasp_enabled_date"] = now() 395 + if validated_data.get("is_kasp"): 396 + validated_data["kasp_enabled_date"] = now() 393 397 394 398 bed = validated_data.pop("bed", None) 395 399 ··· 496 500 def validate_create_diagnoses(self, value): 497 501 # Reject if create_diagnoses is present for edits 498 502 if self.instance and value: 499 - raise ValidationError("Bulk create diagnoses is not allowed on update") 503 + msg = "Bulk create diagnoses is not allowed on update" 504 + raise ValidationError(msg) 500 505 501 506 # Reject if no diagnoses are provided 502 507 if len(value) == 0: 503 - raise ValidationError("Atleast one diagnosis is required") 508 + msg = "Atleast one diagnosis is required" 509 + raise ValidationError(msg) 504 510 505 511 # Reject if duplicate diagnoses are provided 506 - if len(value) != len(set([obj["diagnosis"].id for obj in value])): 507 - raise ValidationError("Duplicate diagnoses are not allowed") 512 + if len(value) != len({obj["diagnosis"].id for obj in value}): 513 + msg = "Duplicate diagnoses are not allowed" 514 + raise ValidationError(msg) 508 515 509 516 principal_diagnosis, confirmed_diagnoses = None, [] 510 517 for obj in value: ··· 514 521 # Reject if there are more than one principal diagnosis 515 522 if obj["is_principal"]: 516 523 if principal_diagnosis: 517 - raise ValidationError( 518 - "Only one diagnosis can be set as principal diagnosis" 519 - ) 524 + msg = "Only one diagnosis can be set as principal diagnosis" 525 + raise ValidationError(msg) 520 526 principal_diagnosis = obj 521 527 522 528 # Reject if principal diagnosis is not one of confirmed diagnosis (if it is present) ··· 526 532 and principal_diagnosis["verification_status"] 527 533 != ConditionVerificationStatus.CONFIRMED 528 534 ): 529 - raise ValidationError( 530 - "Only confirmed diagnosis can be set as principal diagnosis if it is present" 531 - ) 535 + msg = "Only confirmed diagnosis can be set as principal diagnosis if it is present" 536 + raise ValidationError(msg) 532 537 533 538 return value 534 539 535 540 def validate_create_symptoms(self, value): 536 541 if self.instance: 537 - raise ValidationError("Bulk create symptoms is not allowed on update") 542 + msg = "Bulk create symptoms is not allowed on update" 543 + raise ValidationError(msg) 538 544 539 545 counter: set[int | str] = set() 540 546 for obj in value: ··· 550 556 item: str = other_symptom.strip().lower() 551 557 if item in counter: 552 558 # Reject if duplicate symptoms are provided 553 - raise ValidationError("Duplicate symptoms are not allowed") 559 + msg = "Duplicate symptoms are not allowed" 560 + raise ValidationError(msg) 554 561 if not obj.get("cure_date"): 555 562 # skip duplicate symptom check for ones that has cure date 556 563 counter.add(item) ··· 593 600 return None 594 601 return value.strip() 595 602 596 - def validate(self, attrs): 603 + def validate(self, attrs): # noqa: PLR0912 597 604 validated = super().validate(attrs) 598 605 # TODO Add Bed Authorisation Validation 599 606 ··· 621 628 ] 622 629 } 623 630 ) 624 - if not treating_physician.user_type == User.TYPE_VALUE_MAP["Doctor"]: 625 - raise ValidationError("Only Doctors can verify a Consultation") 631 + if treating_physician.user_type != User.TYPE_VALUE_MAP["Doctor"]: 632 + msg = "Only Doctors can verify a Consultation" 633 + raise ValidationError(msg) 626 634 627 635 facility = ( 628 636 self.instance ··· 631 639 ) 632 640 # Check if the Doctor is associated with the Facility (.facilities) 633 641 if not treating_physician.facilities.filter(id=facility.id).exists(): 634 - raise ValidationError( 635 - "The treating doctor is no longer linked to this facility. Please update the respective field in the form before proceeding." 636 - ) 642 + msg = "The treating doctor is no longer linked to this facility. Please update the respective field in the form before proceeding." 643 + raise ValidationError(msg) 637 644 638 645 if ( 639 646 treating_physician.home_facility 640 647 and treating_physician.home_facility != facility 641 648 ): 649 + msg = "Home Facility of the Doctor must be the same as the Consultation Facility" 650 + raise ValidationError(msg) 651 + 652 + if "suggestion" in validated and validated["suggestion"] is SuggestionChoices.R: 653 + if not validated.get("referred_to") and not validated.get( 654 + "referred_to_external" 655 + ): 642 656 raise ValidationError( 643 - "Home Facility of the Doctor must be the same as the Consultation Facility" 657 + { 658 + "referred_to": [ 659 + f"This field is required as the suggestion is {SuggestionChoices.R}." 660 + ] 661 + } 644 662 ) 645 - 646 - if "suggestion" in validated: 647 - if validated["suggestion"] is SuggestionChoices.R: 648 - if not validated.get("referred_to") and not validated.get( 649 - "referred_to_external" 650 - ): 651 - raise ValidationError( 652 - { 653 - "referred_to": [ 654 - f"This field is required as the suggestion is {SuggestionChoices.R}." 655 - ] 656 - } 657 - ) 658 - if validated.get("referred_to_external"): 659 - validated["referred_to"] = None 660 - elif validated.get("referred_to"): 661 - validated["referred_to_external"] = None 663 + if validated.get("referred_to_external"): 664 + validated["referred_to"] = None 665 + elif validated.get("referred_to"): 666 + validated["referred_to_external"] = None 662 667 663 - if "action" in validated: 664 - if validated["action"] == PatientRegistration.ActionEnum.REVIEW: 665 - if "review_interval" not in validated: 666 - raise ValidationError( 667 - { 668 - "review_interval": [ 669 - "This field is required as the patient has been requested Review." 670 - ] 671 - } 672 - ) 673 - if validated["review_interval"] <= 0: 674 - raise ValidationError( 675 - { 676 - "review_interval": [ 677 - "This field value is must be greater than 0." 678 - ] 679 - } 680 - ) 668 + if ( 669 + "action" in validated 670 + and validated["action"] == PatientRegistration.ActionEnum.REVIEW 671 + ): 672 + if "review_interval" not in validated: 673 + raise ValidationError( 674 + { 675 + "review_interval": [ 676 + "This field is required as the patient has been requested Review." 677 + ] 678 + } 679 + ) 680 + if validated["review_interval"] <= 0: 681 + raise ValidationError( 682 + {"review_interval": ["This field value is must be greater than 0."]} 683 + ) 681 684 682 685 if not self.instance and "create_diagnoses" not in validated: 683 686 raise ValidationError({"create_diagnoses": ["This field is required."]}) ··· 894 897 895 898 def validate_patient_code_status(self, value): 896 899 if value == PatientCodeStatusType.NOT_SPECIFIED: 897 - raise ValidationError( 898 - "Specify a correct Patient Code Status for the Consent" 899 - ) 900 + msg = "Specify a correct Patient Code Status for the Consent" 901 + raise ValidationError(msg) 900 902 return value 901 903 902 904 def validate(self, attrs): ··· 905 907 user.user_type < User.TYPE_VALUE_MAP["DistrictAdmin"] 906 908 and self.context["consultation"].facility_id != user.home_facility_id 907 909 ): 908 - raise ValidationError( 909 - "Only Home Facility Staff can create consent for a Consultation" 910 - ) 910 + msg = "Only Home Facility Staff can create consent for a Consultation" 911 + raise ValidationError(msg) 911 912 912 913 if ( 913 914 attrs.get("type", None) ··· 936 937 ) 937 938 return attrs 938 939 939 - def clear_existing_records(self, consultation, type, user, self_id=None): 940 + def clear_existing_records(self, consultation, _type, user, self_id=None): 940 941 consents = PatientConsent.objects.filter( 941 - consultation=consultation, type=type 942 + consultation=consultation, type=_type 942 943 ).exclude(id=self_id) 943 944 944 945 archived_date = timezone.now() ··· 962 963 with transaction.atomic(): 963 964 self.clear_existing_records( 964 965 consultation=self.context["consultation"], 965 - type=validated_data["type"], 966 + _type=validated_data["type"], 966 967 user=self.context["request"].user, 967 968 ) 968 969 validated_data["consultation"] = self.context["consultation"] ··· 973 974 with transaction.atomic(): 974 975 self.clear_existing_records( 975 976 consultation=instance.consultation, 976 - type=instance.type, 977 + _type=instance.type, 977 978 user=self.context["request"].user, 978 979 self_id=instance.id, 979 980 )
+11 -13
care/facility/api/serializers/patient_external_test.py
··· 23 23 ) 24 24 result_date = serializers.DateField(input_formats=["%Y-%m-%d"], required=False) 25 25 26 - def validate_empty_values(self, data, *args, **kwargs): 27 - # if "is_repeat" in data: 28 - # is_repeat = data["is_repeat"] 29 - # if is_repeat.lower() == "yes": 30 - # data["is_repeat"] = True 31 - # else: 32 - # data["is_repeat"] = False 26 + def validate_empty_values(self, data, *args, **kwargs): # noqa: PLR0912 33 27 district_obj = None 34 28 if "district" in data: 35 29 district = data["district"] ··· 76 70 if "ward" in data and local_body_obj: 77 71 try: 78 72 int(data["ward"]) 79 - except Exception: 80 - raise ValidationError({"ward": ["Ward must be an integer value"]}) 73 + except Exception as e: 74 + raise ValidationError( 75 + {"ward": ["Ward must be an integer value"]} 76 + ) from e 81 77 if data["ward"]: 82 78 ward_obj = Ward.objects.filter( 83 79 number=data["ward"], local_body=local_body_obj ··· 92 88 return super().validate_empty_values(data, *args, **kwargs) 93 89 94 90 def create(self, validated_data): 95 - if "srf_id" in validated_data: 96 - if PatientRegistration.objects.filter( 91 + if ( 92 + "srf_id" in validated_data 93 + and PatientRegistration.objects.filter( 97 94 srf_id__iexact=validated_data["srf_id"] 98 - ).exists(): 99 - validated_data["patient_created"] = True 95 + ).exists() 96 + ): 97 + validated_data["patient_created"] = True 100 98 return super().create(validated_data) 101 99 102 100 class Meta:
+1 -1
care/facility/api/serializers/patient_icmr.py
··· 7 7 PatientSampleICMR, 8 8 ) 9 9 from care.users.models import GENDER_CHOICES 10 - from config.serializers import ChoiceField 10 + from care.utils.serializers.fields import ChoiceField 11 11 12 12 13 13 class ICMRPersonalDetails(serializers.ModelSerializer):
+8 -15
care/facility/api/serializers/patient_investigation.py
··· 15 15 class PatientInvestigationGroupSerializer(serializers.ModelSerializer): 16 16 class Meta: 17 17 model = PatientInvestigationGroup 18 - exclude = TIMESTAMP_FIELDS + ("id",) 18 + exclude = (*TIMESTAMP_FIELDS, "id") 19 19 20 20 21 21 class PatientInvestigationSerializer(serializers.ModelSerializer): ··· 23 23 24 24 class Meta: 25 25 model = PatientInvestigation 26 - exclude = TIMESTAMP_FIELDS + ("id",) 26 + exclude = (*TIMESTAMP_FIELDS, "id") 27 27 28 28 29 29 class MinimalPatientInvestigationSerializer(serializers.ModelSerializer): 30 30 class Meta: 31 31 model = PatientInvestigation 32 - exclude = TIMESTAMP_FIELDS + ("id", "groups") 32 + exclude = (*TIMESTAMP_FIELDS, "id", "groups") 33 33 34 34 35 35 class PatientInvestigationSessionSerializer(serializers.ModelSerializer): ··· 38 38 39 39 class Meta: 40 40 model = InvestigationSession 41 - exclude = TIMESTAMP_FIELDS + ("external_id", "id") 41 + exclude = (*TIMESTAMP_FIELDS, "external_id", "id") 42 42 43 43 44 44 class InvestigationValueSerializer(serializers.ModelSerializer): ··· 58 58 59 59 class Meta: 60 60 model = InvestigationValue 61 - read_only_fields = TIMESTAMP_FIELDS + ( 61 + read_only_fields = ( 62 + *TIMESTAMP_FIELDS, 62 63 "session_id", 63 64 "investigation", 64 65 "consultation", 65 66 "session", 66 67 ) 67 - exclude = TIMESTAMP_FIELDS + ("external_id",) 68 + exclude = (*TIMESTAMP_FIELDS, "external_id") 68 69 69 70 def update(self, instance, validated_data): 70 71 if instance.consultation.discharge_date: ··· 72 73 {"consultation": ["Discharged Consultation data cannot be updated"]} 73 74 ) 74 75 75 - # Removed since it might flood messages 76 - # NotificationGenerator( 77 - # event=Notification.Event.INVESTIGATION_UPDATED, 78 - # caused_by=self.context["request"].user, 79 - # caused_object=instance, 80 - # facility=instance.consultation.patient.facility, 81 - # ).generate() 82 - 83 76 return super().update(instance, validated_data) 84 77 85 78 ··· 87 80 class Meta: 88 81 model = InvestigationValue 89 82 read_only_fields = TIMESTAMP_FIELDS 90 - exclude = TIMESTAMP_FIELDS + ("external_id",) 83 + exclude = (*TIMESTAMP_FIELDS, "external_id") 91 84 92 85 93 86 class ValueSerializer(serializers.ModelSerializer):
+15 -20
care/facility/api/serializers/patient_otp.py
··· 1 - import random 1 + import secrets 2 2 import string 3 3 from datetime import timedelta 4 4 ··· 8 8 from rest_framework.exceptions import ValidationError 9 9 10 10 from care.facility.models.patient import PatientMobileOTP 11 - from care.utils.sms.sendSMS import sendSMS 11 + from care.utils.sms.send_sms import send_sms 12 12 13 13 14 14 def rand_pass(size): 15 15 if not settings.USE_SMS: 16 16 return "45612" 17 - generate_pass = "".join( 18 - [random.choice(string.ascii_uppercase + string.digits) for n in range(size)] 19 - ) 20 17 21 - return generate_pass 22 - 23 - 24 - def send_sms(otp, phone_number): 25 - if settings.USE_SMS: 26 - sendSMS( 27 - phone_number, 28 - ( 29 - f"Open Healthcare Network Patient Management System Login, OTP is {otp} . " 30 - "Please do not share this Confidential Login Token with anyone else" 31 - ), 32 - ) 33 - else: 34 - print(otp, phone_number) 18 + return "".join( 19 + secrets.choice(string.ascii_uppercase + string.digits) for _ in range(size) 20 + ) 35 21 36 22 37 23 class PatientMobileOTPSerializer(serializers.ModelSerializer): ··· 56 42 otp_obj = super().create(validated_data) 57 43 otp = rand_pass(settings.OTP_LENGTH) 58 44 59 - send_sms(otp, otp_obj.phone_number) 45 + if settings.USE_SMS: 46 + send_sms( 47 + otp_obj.phone_number, 48 + ( 49 + f"Open Healthcare Network Patient Management System Login, OTP is {otp} . " 50 + "Please do not share this Confidential Login Token with anyone else" 51 + ), 52 + ) 53 + elif settings.DEBUG: 54 + print(otp, otp_obj.phone_number) # noqa: T201 60 55 61 56 otp_obj.otp = otp 62 57 otp_obj.save()
+10 -7
care/facility/api/serializers/patient_sample.py
··· 12 12 PatientSampleFlow, 13 13 ) 14 14 from care.users.api.serializers.user import UserBaseMinimumSerializer 15 - from care.utils.serializer.external_id_field import ExternalIdSerializerField 16 - from config.serializers import ChoiceField 15 + from care.utils.serializers.fields import ChoiceField, ExternalIdSerializerField 17 16 18 17 19 18 class PatientSampleFlowSerializer(serializers.ModelSerializer): ··· 79 78 80 79 class Meta: 81 80 model = PatientSample 82 - read_only_fields = TIMESTAMP_FIELDS + ( 81 + read_only_fields = ( 82 + *TIMESTAMP_FIELDS, 83 83 "id", 84 84 "facility", 85 85 "last_edited_by", ··· 91 91 validated_data.pop("status", None) 92 92 validated_data.pop("result", None) 93 93 94 - sample = super(PatientSampleSerializer, self).create(validated_data) 94 + sample = super().create(validated_data) 95 95 sample.created_by = self.context["request"].user 96 96 sample.last_edited_by = self.context["request"].user 97 97 sample.save() ··· 119 119 validated_data["status"] = PatientSample.SAMPLE_TEST_FLOW_MAP[ 120 120 "COMPLETED" 121 121 ] 122 - except KeyError: 123 - raise ValidationError({"status": ["is required"]}) 122 + except KeyError as e: 123 + raise ValidationError({"status": ["is required"]}) from e 124 124 valid_choices = PatientSample.SAMPLE_FLOW_RULES[ 125 125 PatientSample.SAMPLE_TEST_FLOW_CHOICES[instance.status - 1][1] 126 126 ] ··· 134 134 ) 135 135 if choice == "COMPLETED" and not validated_data.get("result"): 136 136 raise ValidationError({"result": ["is required as the test is complete"]}) 137 - if choice == "COMPLETED" and instance.result != 3: 137 + if ( 138 + choice == "COMPLETED" 139 + and instance.result != PatientSample.SAMPLE_TEST_RESULT_MAP["AWAITING"] 140 + ): 138 141 raise ValidationError( 139 142 {"result": ["cannot change result for completed test."]} 140 143 )
+18 -20
care/facility/api/serializers/prescription.py
··· 32 32 33 33 def validate_administered_date(self, value): 34 34 if value > timezone.now(): 35 - raise serializers.ValidationError( 36 - "Administered Date cannot be in the future." 37 - ) 35 + msg = "Administered Date cannot be in the future." 36 + raise serializers.ValidationError(msg) 38 37 if self.context["prescription"].created_date > value: 39 - raise serializers.ValidationError( 40 - "Administered Date cannot be before Prescription Date." 41 - ) 38 + msg = "Administered Date cannot be before Prescription Date." 39 + raise serializers.ValidationError(msg) 42 40 return value 43 41 44 42 def validate(self, attrs): ··· 50 48 raise serializers.ValidationError( 51 49 {"dosage": "Dosage is required for titrated prescriptions."} 52 50 ) 53 - elif ( 54 - self.context["prescription"].dosage_type != PrescriptionDosageType.TITRATED 55 - ): 51 + if self.context["prescription"].dosage_type != PrescriptionDosageType.TITRATED: 56 52 attrs.pop("dosage", None) 57 53 58 54 return super().validate(attrs) ··· 107 103 MedibaseMedicine, external_id=attrs["medicine"] 108 104 ) 109 105 110 - if not self.instance: 111 - if Prescription.objects.filter( 106 + if ( 107 + not self.instance 108 + and Prescription.objects.filter( 112 109 consultation__external_id=self.context["request"].parser_context[ 113 110 "kwargs" 114 111 ]["consultation_external_id"], 115 112 medicine=attrs["medicine"], 116 113 discontinued=False, 117 - ).exists(): 118 - raise serializers.ValidationError( 119 - { 120 - "medicine": ( 121 - "This medicine is already prescribed to this patient. " 122 - "Please discontinue the existing prescription to prescribe again." 123 - ) 124 - } 125 - ) 114 + ).exists() 115 + ): 116 + raise serializers.ValidationError( 117 + { 118 + "medicine": ( 119 + "This medicine is already prescribed to this patient. " 120 + "Please discontinue the existing prescription to prescribe again." 121 + ) 122 + } 123 + ) 126 124 127 125 if not attrs.get("base_dosage"): 128 126 raise serializers.ValidationError(
+13 -12
care/facility/api/serializers/resources.py
··· 13 13 ) 14 14 from care.facility.models.resources import RESOURCE_SUB_CATEGORY_CHOICES 15 15 from care.users.api.serializers.user import UserBaseMinimumSerializer 16 - from care.utils.serializer.external_id_field import ExternalIdSerializerField 17 - from config.serializers import ChoiceField 16 + from care.utils.serializers.fields import ChoiceField, ExternalIdSerializerField 18 17 19 18 20 19 def inverse_choices(choices): ··· 86 85 super().__init__(instance=instance, **kwargs) 87 86 88 87 def update(self, instance, validated_data): 88 + # ruff: noqa: N806 better to refactor this 89 89 LIMITED_RECIEVING_STATUS_ = [] 90 90 LIMITED_RECIEVING_STATUS = [ 91 91 REVERSE_REQUEST_STATUS_CHOICES[x] for x in LIMITED_RECIEVING_STATUS_ ··· 101 101 LIMITED_REQUEST_STATUS = [ 102 102 REVERSE_REQUEST_STATUS_CHOICES[x] for x in LIMITED_REQUEST_STATUS_ 103 103 ] 104 - # LIMITED_ORGIN_STATUS = [] 105 104 106 105 user = self.context["request"].user 107 106 108 107 if "status" in validated_data: 109 108 if validated_data["status"] in LIMITED_RECIEVING_STATUS: 110 - if instance.assigned_facility: 111 - if not has_facility_permission(user, instance.assigned_facility): 112 - raise ValidationError({"status": ["Permission Denied"]}) 113 - elif validated_data["status"] in LIMITED_REQUEST_STATUS: 114 - if not has_facility_permission(user, instance.approving_facility): 109 + if instance.assigned_facility and not has_facility_permission( 110 + user, instance.assigned_facility 111 + ): 115 112 raise ValidationError({"status": ["Permission Denied"]}) 113 + elif validated_data[ 114 + "status" 115 + ] in LIMITED_REQUEST_STATUS and not has_facility_permission( 116 + user, instance.approving_facility 117 + ): 118 + raise ValidationError({"status": ["Permission Denied"]}) 116 119 117 120 # Dont allow editing origin or patient 118 121 if "origin_facility" in validated_data: ··· 120 123 121 124 instance.last_edited_by = self.context["request"].user 122 125 123 - new_instance = super().update(instance, validated_data) 124 - 125 - return new_instance 126 + return super().update(instance, validated_data) 126 127 127 128 def create(self, validated_data): 128 129 # Do Validity checks for each of these data ··· 158 159 class Meta: 159 160 model = ResourceRequestComment 160 161 exclude = ("deleted", "request", "external_id") 161 - read_only_fields = TIMESTAMP_FIELDS + ("created_by",) 162 + read_only_fields = (*TIMESTAMP_FIELDS, "created_by")
+24 -19
care/facility/api/serializers/shifting.py
··· 33 33 from care.users.api.serializers.lsg import StateSerializer 34 34 from care.users.api.serializers.user import UserBaseMinimumSerializer 35 35 from care.utils.notification_handler import NotificationGenerator 36 - from care.utils.serializer.external_id_field import ExternalIdSerializerField 37 - from config.serializers import ChoiceField 36 + from care.utils.serializers.fields import ChoiceField, ExternalIdSerializerField 38 37 39 38 40 39 def inverse_choices(choices): ··· 44 43 return output 45 44 46 45 47 - REVERSE_SHIFTING_STATUS_CHOICES = inverse_choices(SHIFTING_STATUS_CHOICES) 46 + REVERSE_SHIFTING_STATUS_CHOICES: dict[str, int] = inverse_choices( 47 + SHIFTING_STATUS_CHOICES 48 + ) 48 49 49 50 50 51 def has_facility_permission(user, facility): ··· 240 241 241 242 def validate_shifting_approving_facility(self, value): 242 243 if not settings.PEACETIME_MODE and not value: 243 - raise ValidationError("Shifting Approving Facility is required") 244 + msg = "Shifting Approving Facility is required" 245 + raise ValidationError(msg) 244 246 return value 245 247 246 - def update(self, instance, validated_data): 248 + def update(self, instance, validated_data): # noqa: PLR0912 247 249 if instance.status == REVERSE_SHIFTING_STATUS_CHOICES["CANCELLED"]: 248 - raise ValidationError("Permission Denied, Shifting request was cancelled.") 249 - elif instance.status == REVERSE_SHIFTING_STATUS_CHOICES["COMPLETED"]: 250 - raise ValidationError("Permission Denied, Shifting request was completed.") 250 + msg = "Permission Denied, Shifting request was cancelled." 251 + raise ValidationError(msg) 252 + if instance.status == REVERSE_SHIFTING_STATUS_CHOICES["COMPLETED"]: 253 + msg = "Permission Denied, Shifting request was completed." 254 + raise ValidationError(msg) 251 255 252 256 # Dont allow editing origin or patient 253 257 validated_data.pop("origin_facility") ··· 289 293 raise ValidationError({"status": ["Permission Denied"]}) 290 294 291 295 elif ( 292 - status in self.LIMITED_RECIEVING_STATUS 293 - and instance.assigned_facility 294 - and not has_facility_permission(user, instance.assigned_facility) 295 - ) or status in self.LIMITED_SHIFTING_STATUS and not has_facility_permission( 296 - user, instance.shifting_approving_facility 296 + ( 297 + status in self.LIMITED_RECIEVING_STATUS 298 + and instance.assigned_facility 299 + and not has_facility_permission(user, instance.assigned_facility) 300 + ) 301 + or status in self.LIMITED_SHIFTING_STATUS 302 + and not has_facility_permission( 303 + user, instance.shifting_approving_facility 304 + ) 297 305 ): 298 306 raise ValidationError({"status": ["Permission Denied"]}) 299 307 ··· 346 354 "status" in validated_data 347 355 and new_instance.shifting_approving_facility is not None 348 356 and validated_data["status"] != old_status 349 - and validated_data["status"] == 40 357 + and validated_data["status"] 358 + == REVERSE_SHIFTING_STATUS_CHOICES["DESTINATION APPROVED"] 350 359 ): 351 360 NotificationGenerator( 352 361 event=Notification.Event.SHIFTING_UPDATED, ··· 569 578 class Meta: 570 579 model = ShiftingRequestComment 571 580 exclude = ("deleted", "request") 572 - read_only_fields = TIMESTAMP_FIELDS + ( 573 - "created_by", 574 - "external_id", 575 - "id", 576 - ) 581 + read_only_fields = (*TIMESTAMP_FIELDS, "created_by", "external_id", "id")
+1 -1
care/facility/api/viewsets/ambulance.py
··· 58 58 def get_serializer_class(self): 59 59 if self.action == "add_driver": 60 60 return AmbulanceDriverSerializer 61 - elif self.action == "remove_driver": 61 + if self.action == "remove_driver": 62 62 return DeleteDriverSerializer 63 63 return AmbulanceSerializer 64 64
+24 -24
care/facility/api/viewsets/asset.py
··· 1 + import logging 1 2 import re 3 + from typing import TYPE_CHECKING 2 4 3 5 from django.conf import settings 4 6 from django.core.cache import cache ··· 58 60 ) 59 61 from care.users.models import User 60 62 from care.utils.assetintegration.asset_classes import AssetClasses 61 - from care.utils.assetintegration.base import BaseAssetIntegration 62 63 from care.utils.cache.cache_allowed_facilities import get_accessible_facilities 63 64 from care.utils.filters.choicefilter import CareChoiceFilter, inverse_choices 64 65 from care.utils.queryset.asset_location import get_asset_location_queryset 65 66 from care.utils.queryset.facility import get_facility_queryset 66 67 from config.authentication import MiddlewareAuthentication 67 68 69 + if TYPE_CHECKING: 70 + from care.utils.assetintegration.base import BaseAssetIntegration 71 + 72 + logger = logging.getLogger(__name__) 73 + 74 + 68 75 inverse_asset_type = inverse_choices(AssetTypeChoices) 69 76 inverse_asset_status = inverse_choices(StatusChoices) 70 77 ··· 131 138 def destroy(self, request, *args, **kwargs): 132 139 instance = self.get_object() 133 140 if instance.bed_set.filter(deleted=False).count(): 134 - raise ValidationError("Cannot delete a Location with associated Beds") 141 + msg = "Cannot delete a Location with associated Beds" 142 + raise ValidationError(msg) 135 143 if instance.asset_set.filter(deleted=False).count(): 136 - raise ValidationError("Cannot delete a Location with associated Assets") 144 + msg = "Cannot delete a Location with associated Assets" 145 + raise ValidationError(msg) 137 146 138 147 return super().destroy(request, *args, **kwargs) 139 148 ··· 243 252 content_type__model="asset", 244 253 object_external_id=self.kwargs["asset_external_id"], 245 254 ) 246 - else: 247 - raise exceptions.PermissionDenied( 248 - "You do not have access to this asset's availability records" 249 - ) 250 - elif "asset_location_external_id" in self.kwargs: 255 + msg = "You do not have access to this asset's availability records" 256 + raise exceptions.PermissionDenied(msg) 257 + if "asset_location_external_id" in self.kwargs: 251 258 asset_location = get_object_or_404( 252 259 AssetLocation, external_id=self.kwargs["asset_location_external_id"] 253 260 ) ··· 256 263 content_type__model="assetlocation", 257 264 object_external_id=self.kwargs["asset_location_external_id"], 258 265 ) 259 - else: 260 - raise exceptions.PermissionDenied( 261 - "You do not have access to this asset location's availability records" 262 - ) 263 - else: 264 - raise exceptions.ValidationError( 265 - "Either asset_external_id or asset_location_external_id is required" 266 - ) 266 + msg = "You do not have access to this asset location's availability records" 267 + raise exceptions.PermissionDenied(msg) 268 + msg = "Either asset_external_id or asset_location_external_id is required" 269 + raise exceptions.ValidationError(msg) 267 270 268 271 269 272 class AssetViewSet( ··· 302 305 queryset = queryset.filter( 303 306 current_location__facility__id__in=allowed_facilities 304 307 ) 305 - queryset = queryset.annotate( 308 + return queryset.annotate( 306 309 latest_status=Subquery( 307 310 AvailabilityRecord.objects.filter( 308 311 content_type__model="asset", ··· 312 315 .values("status")[:1] 313 316 ) 314 317 ) 315 - return queryset 316 318 317 319 def list(self, request, *args, **kwargs): 318 320 if settings.CSV_REQUEST_PARAMETER in request.GET: ··· 323 325 queryset, field_header_map=mapping, field_serializer_map=pretty_mapping 324 326 ) 325 327 326 - return super(AssetViewSet, self).list(request, *args, **kwargs) 328 + return super().list(request, *args, **kwargs) 327 329 328 330 def destroy(self, request, *args, **kwargs): 329 331 user = self.request.user 330 332 if user.user_type >= User.TYPE_VALUE_MAP["DistrictAdmin"]: 331 333 return super().destroy(request, *args, **kwargs) 332 - else: 333 - raise exceptions.AuthenticationFailed( 334 - "Only District Admin and above can delete assets" 335 - ) 334 + msg = "Only District Admin and above can delete assets" 335 + raise exceptions.AuthenticationFailed(msg) 336 336 337 337 @extend_schema( 338 338 responses={200: UserDefaultAssetLocationSerializer()}, tags=["asset"] ··· 416 416 ) 417 417 418 418 except Exception as e: 419 - print(f"error: {e}") 419 + logger.info("Failed to operate asset: %s", e) 420 420 return Response( 421 421 {"message": "Internal Server Error"}, 422 422 status=status.HTTP_500_INTERNAL_SERVER_ERROR,
+1 -1
care/facility/api/viewsets/bed.py
··· 131 131 132 132 def destroy(self, request, *args, **kwargs): 133 133 if request.user.user_type < User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 134 - raise PermissionDenied() 134 + raise PermissionDenied 135 135 instance = self.get_object() 136 136 if instance.is_occupied: 137 137 raise DRFValidationError(
+1 -4
care/facility/api/viewsets/daily_round.py
··· 14 14 from care.facility.models.daily_round import DailyRound 15 15 from care.utils.queryset.consultation import get_consultation_queryset 16 16 17 - DailyRoundAttributes = [f.name for f in DailyRound._meta.get_fields()] 17 + DailyRoundAttributes = [f.name for f in DailyRound._meta.get_fields()] # noqa: SLF001 18 18 19 19 20 20 class DailyRoundFilterSet(filters.FilterSet): ··· 97 97 raise ValidationError(errors) 98 98 99 99 page = request.data.get("page", 1) 100 - 101 - # to_time = datetime.now() - timedelta(days=((page - 1) * self.DEFAULT_LOOKUP_DAYS)) 102 - # from_time = to_time - timedelta(days=self.DEFAULT_LOOKUP_DAYS) 103 100 104 101 consultation = get_object_or_404( 105 102 get_consultation_queryset(request.user).filter(
-2
care/facility/api/viewsets/events.py
··· 68 68 ) 69 69 filter_backends = (filters.DjangoFilterBackend,) 70 70 filterset_class = PatientConsultationEventFilterSet 71 - # lookup_field = "external_id" 72 - # lookup_url_kwarg = "external_id" 73 71 74 72 def get_consultation_obj(self): 75 73 return get_object_or_404(
+1 -1
care/facility/api/viewsets/facility.py
··· 144 144 queryset, field_header_map=mapping, field_serializer_map=pretty_mapping 145 145 ) 146 146 147 - return super(FacilityViewSet, self).list(request, *args, **kwargs) 147 + return super().list(request, *args, **kwargs) 148 148 149 149 @extend_schema(tags=["facility"]) 150 150 @method_decorator(parser_classes([MultiPartParser]))
+2 -2
care/facility/api/viewsets/facility_capacity.py
··· 27 27 ) 28 28 if user.is_superuser: 29 29 return queryset 30 - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: 30 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: 31 31 return queryset.filter(facility__state=user.state) 32 - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 32 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 33 33 return queryset.filter(facility__district=user.district) 34 34 return queryset.filter(facility__users__id__exact=user.id) 35 35
+2 -2
care/facility/api/viewsets/facility_users.py
··· 47 47 queryset=UserSkill.objects.filter(skill__deleted=False), 48 48 ), 49 49 ) 50 - except Facility.DoesNotExist: 51 - raise ValidationError({"Facility": "Facility not found"}) 50 + except Facility.DoesNotExist as e: 51 + raise ValidationError({"Facility": "Facility not found"}) from e
+7 -9
care/facility/api/viewsets/file_upload.py
··· 37 37 "PATIENT", 38 38 "CONSULTATION", 39 39 ) 40 - else: 41 - return request.data.get("file_type") not in ( 42 - "PATIENT", 43 - "CONSULTATION", 44 - ) 40 + return request.data.get("file_type") not in ( 41 + "PATIENT", 42 + "CONSULTATION", 43 + ) 45 44 return True 46 45 47 46 def has_object_permission(self, request, view, obj) -> bool: ··· 67 66 def get_serializer_class(self): 68 67 if self.action == "retrieve": 69 68 return FileUploadRetrieveSerializer 70 - elif self.action == "list": 69 + if self.action == "list": 71 70 return FileUploadListSerializer 72 - elif self.action == "create": 71 + if self.action == "create": 73 72 return FileUploadCreateSerializer 74 - else: 75 - return FileUploadUpdateSerializer 73 + return FileUploadUpdateSerializer 76 74 77 75 def get_queryset(self): 78 76 if "file_type" not in self.request.GET:
-1
care/facility/api/viewsets/icd.py
··· 8 8 9 9 10 10 class ICDViewSet(ViewSet): 11 - 12 11 def serialize_data(self, objects: list[ICD11]): 13 12 return [diagnosis.get_representation() for diagnosis in objects] 14 13
+17 -30
care/facility/api/viewsets/inventory.py
··· 33 33 ) 34 34 from care.users.models import User 35 35 from care.utils.queryset.facility import get_facility_queryset 36 - from care.utils.validation.integer_validation import check_integer 36 + 37 + 38 + def check_integer(vals): 39 + if not isinstance(vals, list): 40 + vals = [vals] 41 + for i in range(len(vals)): 42 + try: 43 + vals[i] = int(vals[i]) 44 + except Exception as e: 45 + raise ValidationError({"value": "Integer Required"}) from e 46 + return vals 37 47 38 48 39 49 class FacilityInventoryFilter(filters.FilterSet): ··· 87 97 ) 88 98 if user.is_superuser: 89 99 return queryset 90 - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: 100 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: 91 101 return queryset.filter(facility__state=user.state) 92 - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 102 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 93 103 return queryset.filter(facility__district=user.district) 94 104 return queryset.filter(facility__users__id__exact=user.id) 95 105 ··· 171 181 ) 172 182 if user.is_superuser: 173 183 return queryset 174 - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: 184 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: 175 185 return queryset.filter(facility__state=user.state) 176 - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 186 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 177 187 return queryset.filter(facility__district=user.district) 178 188 return queryset.filter(facility__users__id__exact=user.id) 179 189 ··· 215 225 ) 216 226 if user.is_superuser: 217 227 return queryset 218 - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: 228 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: 219 229 return queryset.filter(facility__state=user.state) 220 - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 230 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 221 231 return queryset.filter(facility__district=user.district) 222 232 return queryset.filter(facility__users__id__exact=user.id) 223 233 ··· 225 235 return get_object_or_404( 226 236 self.get_queryset(), external_id=self.kwargs.get("external_id") 227 237 ) 228 - 229 - 230 - # class FacilityInventoryBurnRateFilter(filters.FilterSet): 231 - # name = filters.CharFilter(field_name="facility__name", lookup_expr="icontains") 232 - # item = filters.NumberFilter(field_name="item_id") 233 - 234 - 235 - # class FacilityInventoryBurnRateViewSet( 236 - # UserAccessMixin, ListModelMixin, RetrieveModelMixin, GenericViewSet, 237 - # ): 238 - # queryset = FacilityInventoryBurnRate.objects.select_related( 239 - # "item", "item__default_unit", "facility__district" 240 - # ).all() 241 - # filter_backends = (filters.DjangoFilterBackend,) 242 - # filterset_class = FacilityInventoryBurnRateFilter 243 - # permission_classes = (IsAuthenticated, DRYPermissions) 244 - # serializer_class = FacilityInventoryBurnRateSerializer 245 - 246 - # def filter_queryset(self, queryset): 247 - # queryset = super().filter_queryset(queryset) 248 - # if self.kwargs.get("facility_external_id"): 249 - # queryset = queryset.filter(facility__external_id=self.kwargs.get("facility_external_id")) 250 - # return self.filter_by_user_scope(queryset)
+1 -1
care/facility/api/viewsets/mixins/access.py
··· 53 53 asset_permissions = (DRYAssetPermissions,) 54 54 55 55 def get_authenticators(self): 56 - return [MiddlewareAssetAuthentication()] + super().get_authenticators() 56 + return [MiddlewareAssetAuthentication(), *super().get_authenticators()] 57 57 58 58 def get_permissions(self): 59 59 """
+1 -1
care/facility/api/viewsets/mixins/history.py
··· 1 1 from rest_framework.decorators import action 2 2 3 - from care.utils.serializer.history_serializer import ModelHistorySerializer 3 + from care.utils.serializers.history_serializer import ModelHistorySerializer 4 4 5 5 6 6 class HistoryMixin:
+1 -1
care/facility/api/viewsets/notification.py
··· 68 68 def notify(self, request, *args, **kwargs): 69 69 user = request.user 70 70 if user.user_type < User.TYPE_VALUE_MAP["Doctor"]: 71 - raise PermissionDenied() 71 + raise PermissionDenied 72 72 if "facility" not in request.data or request.data["facility"] == "": 73 73 raise ValidationError({"facility": "is required"}) 74 74 if "message" not in request.data or request.data["message"] == "":
+54 -63
care/facility/api/viewsets/patient.py
··· 1 - import datetime 2 1 import json 3 2 from json import JSONDecodeError 4 3 ··· 97 96 98 97 99 98 class PatientFilterSet(filters.FilterSet): 100 - 101 99 last_consultation_field = "last_consultation" 102 100 103 101 source = filters.ChoiceFilter(choices=PatientRegistration.SourceChoices) ··· 356 354 def filter_queryset(self, request, queryset, view): 357 355 ordering = request.query_params.get("ordering", "") 358 356 359 - if ordering == "category_severity" or ordering == "-category_severity": 357 + if ordering in ("category_severity", "-category_severity"): 360 358 category_ordering = { 361 359 category: index + 1 362 360 for index, (category, _) in enumerate(CATEGORY_CHOICES) ··· 507 505 def get_serializer_class(self): 508 506 if self.action == "list": 509 507 return PatientListSerializer 510 - elif self.action == "icmr_sample": 508 + if self.action == "icmr_sample": 511 509 return PatientICMRSerializer 512 - elif self.action == "transfer": 510 + if self.action == "transfer": 513 511 return PatientTransferSerializer 514 - else: 515 - return self.serializer_class 512 + return self.serializer_class 516 513 517 514 def filter_queryset(self, queryset: QuerySet) -> QuerySet: 518 515 if self.action == "list" and settings.CSV_REQUEST_PARAMETER in self.request.GET: ··· 586 583 field_serializer_map=PatientRegistration.CSV_MAKE_PRETTY, 587 584 ) 588 585 589 - return super(PatientViewSet, self).list(request, *args, **kwargs) 586 + return super().list(request, *args, **kwargs) 590 587 591 588 @extend_schema(tags=["patient"]) 592 589 @action(detail=True, methods=["POST"]) ··· 805 802 ) 806 803 if user.is_superuser: 807 804 return queryset 808 - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: 805 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: 809 806 return queryset.filter(facility__state=user.state) 810 - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 807 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 811 808 return queryset.filter(facility__district=user.district) 812 809 return queryset.filter(facility__users__id__exact=user.id) 813 810 ··· 836 833 - entry_date_before: date in YYYY-MM-DD format, inclusive of this date 837 834 838 835 """ 839 - return super(FacilityPatientStatsHistoryViewSet, self).list( 840 - request, *args, **kwargs 841 - ) 836 + return super().list(request, *args, **kwargs) 842 837 843 838 844 839 class PatientSearchSetPagination(PageNumberPagination): ··· 864 859 865 860 def get_queryset(self): 866 861 if self.action != "list": 867 - return super(PatientSearchViewSet, self).get_queryset() 862 + return super().get_queryset() 863 + serializer = PatientSearchSerializer( 864 + data=self.request.query_params, partial=True 865 + ) 866 + serializer.is_valid(raise_exception=True) 867 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 868 + search_keys = [ 869 + "date_of_birth", 870 + "year_of_birth", 871 + "phone_number", 872 + "name", 873 + "age", 874 + ] 868 875 else: 869 - serializer = PatientSearchSerializer( 870 - data=self.request.query_params, partial=True 876 + search_keys = [ 877 + "date_of_birth", 878 + "year_of_birth", 879 + "phone_number", 880 + "age", 881 + ] 882 + search_fields = { 883 + key: serializer.validated_data[key] 884 + for key in search_keys 885 + if serializer.validated_data.get(key) 886 + } 887 + if not search_fields: 888 + raise serializers.ValidationError( 889 + { 890 + "detail": [ 891 + f"None of the search keys provided. Available: {', '.join(search_keys)}" 892 + ] 893 + } 871 894 ) 872 - serializer.is_valid(raise_exception=True) 873 - if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 874 - search_keys = [ 875 - "date_of_birth", 876 - "year_of_birth", 877 - "phone_number", 878 - "name", 879 - "age", 880 - ] 881 - else: 882 - search_keys = [ 883 - "date_of_birth", 884 - "year_of_birth", 885 - "phone_number", 886 - "age", 887 - ] 888 - search_fields = { 889 - key: serializer.validated_data[key] 890 - for key in search_keys 891 - if serializer.validated_data.get(key) 892 - } 893 - if not search_fields: 894 - raise serializers.ValidationError( 895 - { 896 - "detail": [ 897 - f"None of the search keys provided. Available: {', '.join(search_keys)}" 898 - ] 899 - } 900 - ) 901 895 902 - # if not self.request.user.is_superuser: 903 - # search_fields["state_id"] = self.request.user.state_id 904 - 905 - if "age" in search_fields: 906 - age = search_fields.pop("age") 907 - year_of_birth = datetime.datetime.now().year - age 908 - search_fields["age__gte"] = year_of_birth - 5 909 - search_fields["age__lte"] = year_of_birth + 5 896 + if "age" in search_fields: 897 + age = search_fields.pop("age") 898 + year_of_birth = timezone.now().year - age 899 + search_fields["age__gte"] = year_of_birth - 5 900 + search_fields["age__lte"] = year_of_birth + 5 910 901 911 - name = search_fields.pop("name", None) 902 + name = search_fields.pop("name", None) 912 903 913 - queryset = self.queryset.filter(**search_fields) 904 + queryset = self.queryset.filter(**search_fields) 914 905 915 - if name: 916 - queryset = ( 917 - queryset.annotate(similarity=TrigramSimilarity("name", name)) 918 - .filter(similarity__gt=0.2) 919 - .order_by("-similarity") 920 - ) 906 + if name: 907 + queryset = ( 908 + queryset.annotate(similarity=TrigramSimilarity("name", name)) 909 + .filter(similarity__gt=0.2) 910 + .order_by("-similarity") 911 + ) 921 912 922 - return queryset 913 + return queryset 923 914 924 915 @extend_schema(tags=["patient"]) 925 916 def list(self, request, *args, **kwargs): ··· 939 930 `Eg: api/v1/patient/search/?year_of_birth=1992&phone_number=%2B917795937091` 940 931 941 932 """ 942 - return super(PatientSearchViewSet, self).list(request, *args, **kwargs) 933 + return super().list(request, *args, **kwargs) 943 934 944 935 945 936 class PatientNotesFilterSet(filters.FilterSet):
+9 -12
care/facility/api/viewsets/patient_consultation.py
··· 70 70 def get_serializer_class(self): 71 71 if self.action == "patient_from_asset": 72 72 return PatientConsultationIDSerializer 73 - elif self.action == "discharge_patient": 73 + if self.action == "discharge_patient": 74 74 return PatientConsultationDischargeSerializer 75 - elif self.action == "email_discharge_summary": 75 + if self.action == "email_discharge_summary": 76 76 return EmailDischargeSummarySerializer 77 - else: 78 - return self.serializer_class 77 + return self.serializer_class 79 78 80 79 def get_permissions(self): 81 80 if self.action == "patient_from_asset": ··· 97 96 ) 98 97 if self.request.user.is_superuser: 99 98 return self.queryset 100 - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: 99 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: 101 100 return self.queryset.filter( 102 101 patient__facility__state=self.request.user.state 103 102 ) 104 - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 103 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 105 104 return self.queryset.filter( 106 105 patient__facility__district=self.request.user.district 107 106 ) ··· 304 303 305 304 with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_file: 306 305 discharge_summary.generate_discharge_summary_pdf(data, tmp_file) 306 + tmp_file.seek(0) 307 307 308 - with open(tmp_file.name, "rb") as pdf_file: 309 - pdf_content = pdf_file.read() 310 - 311 - response = HttpResponse(pdf_content, content_type="application/pdf") 312 - response["Content-Disposition"] = 'inline; filename="discharge_summary.pdf"' 308 + response = HttpResponse(tmp_file, content_type="application/pdf") 309 + response["Content-Disposition"] = 'inline; filename="discharge_summary.pdf"' 313 310 314 - return response 311 + return response 315 312 316 313 317 314 class PatientConsentViewSet(
+14 -23
care/facility/api/viewsets/patient_external_test.py
··· 28 28 from care.users.models import User 29 29 30 30 31 - def prettyerrors(errors): 31 + def pretty_errors(errors): 32 32 pretty_errors = defaultdict(list) 33 - for attribute in PatientExternalTest.HEADER_CSV_MAPPING.keys(): 33 + for attribute in PatientExternalTest.HEADER_CSV_MAPPING: 34 34 if attribute in errors: 35 35 for error in errors.get(attribute, ""): 36 36 pretty_errors[attribute].append(str(error)) ··· 46 46 self.field_name + "__in": values, 47 47 self.field_name + "__isnull": False, 48 48 } 49 - qs = qs.filter(**_filter) 50 - return qs 49 + return qs.filter(**_filter) 51 50 52 51 53 52 class PatientExternalTestFilter(filters.FilterSet): ··· 113 112 return queryset 114 113 115 114 def get_serializer_class(self): 116 - if self.action == "update" or self.action == "partial_update": 115 + if self.action in ("update", "partial_update"): 117 116 return PatientExternalTestUpdateSerializer 118 117 return super().get_serializer_class() 119 118 120 119 def destroy(self, request, *args, **kwargs): 121 120 if self.request.user.user_type < User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 122 - raise PermissionDenied() 121 + raise PermissionDenied 123 122 return super().destroy(request, *args, **kwargs) 124 123 125 124 def check_upload_permission(self): 126 - if ( 125 + return bool( 127 126 self.request.user.is_superuser is True 128 127 or self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] 129 - ): 130 - return True 131 - return False 128 + ) 132 129 133 130 def list(self, request, *args, **kwargs): 134 131 if settings.CSV_REQUEST_PARAMETER in request.GET: ··· 140 137 field_header_map=mapping, 141 138 field_serializer_map=pretty_mapping, 142 139 ) 143 - return super(PatientExternalTestViewSet, self).list(request, *args, **kwargs) 140 + return super().list(request, *args, **kwargs) 144 141 145 142 @extend_schema(tags=["external_result"]) 146 143 @action(methods=["POST"], detail=False) 147 144 def bulk_upsert(self, request, *args, **kwargs): 148 145 if not self.check_upload_permission(): 149 - raise PermissionDenied("Permission to Endpoint Denied") 150 - # if len(request.FILES.keys()) != 1: 151 - # raise ValidationError({"file": "Upload 1 File at a time"}) 152 - # csv_file = request.FILES[list(request.FILES.keys())[0]] 153 - # csv_file.seek(0) 154 - # reader = csv.DictReader(io.StringIO(csv_file.read().decode("utf-8-sig"))) 146 + msg = "Permission to Endpoint Denied" 147 + raise PermissionDenied(msg) 155 148 if "sample_tests" not in request.data: 156 149 raise ValidationError({"sample_tests": "No Data was provided"}) 157 150 if not isinstance(request.data["sample_tests"], list): ··· 163 156 raise ValidationError({"Error": "User must belong to same district"}) 164 157 165 158 errors = [] 166 - counter = 0 167 159 ser_objects = [] 168 160 invalid = False 169 161 for sample in request.data["sample_tests"]: 170 - counter += 1 171 - serialiser_obj = PatientExternalTestSerializer(data=sample) 172 - valid = serialiser_obj.is_valid() 173 - current_error = prettyerrors(serialiser_obj._errors) 162 + serializer = PatientExternalTestSerializer(data=sample) 163 + valid = serializer.is_valid() 164 + current_error = pretty_errors(serializer._errors) # noqa: SLF001 174 165 if current_error and (not valid): 175 166 errors.append(current_error) 176 167 invalid = True 177 - ser_objects.append(serialiser_obj) 168 + ser_objects.append(serializer) 178 169 if invalid: 179 170 return Response(errors, status=status.HTTP_400_BAD_REQUEST) 180 171 for ser_object in ser_objects:
+8 -10
care/facility/api/viewsets/patient_investigation.py
··· 44 44 if not value: 45 45 return qs 46 46 47 - qs = qs.filter(groups__external_id=value) 48 - return qs 47 + return qs.filter(groups__external_id=value) 49 48 50 49 51 50 class PatientInvestigationFilter(filters.FilterSet): ··· 115 114 queryset.filter(investigation__external_id__in=investigations.split(",")) 116 115 .order_by("-session__created_date") 117 116 .distinct("session__created_date")[ 118 - (session_page - 1) 119 - * self.SESSION_PER_PAGE : (session_page) 117 + (session_page - 1) * self.SESSION_PER_PAGE : (session_page) 120 118 * self.SESSION_PER_PAGE 121 119 ] 122 120 ) ··· 125 123 queryset = queryset.filter(session_id__in=sessions.values("session_id")) 126 124 if self.request.user.is_superuser: 127 125 return queryset 128 - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: 126 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: 129 127 return queryset.filter( 130 128 consultation__patient__facility__state=self.request.user.state 131 129 ) 132 - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 130 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 133 131 return queryset.filter( 134 132 consultation__patient__facility__district=self.request.user.district 135 133 ) ··· 168 166 ) 169 167 if self.request.user.is_superuser: 170 168 return queryset 171 - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: 169 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: 172 170 return queryset.filter( 173 171 consultation__patient__facility__state=self.request.user.state 174 172 ) 175 - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 173 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 176 174 return queryset.filter( 177 175 consultation__patient__facility__district=self.request.user.district 178 176 ) ··· 206 204 responses={204: "Operation successful"}, 207 205 tags=["investigation"], 208 206 ) 209 - @action(detail=False, methods=["PUT"]) 210 - def batchUpdate(self, request, *args, **kwargs): 207 + @action(detail=False, methods=["PUT"], url_path="batchUpdate") 208 + def batch_update(self, request, *args, **kwargs): 211 209 if "investigations" not in request.data: 212 210 return Response( 213 211 {"investigation": "is required"},
+6 -6
care/facility/api/viewsets/patient_otp.py
··· 1 - from re import error 2 - 3 1 from django.conf import settings 4 2 from drf_spectacular.utils import extend_schema, extend_schema_view 5 3 from rest_framework import mixins ··· 28 26 @action(detail=False, methods=["POST"]) 29 27 def login(self, request): 30 28 if "phone_number" not in request.data or "otp" not in request.data: 31 - raise ValidationError("Request Incomplete") 29 + msg = "Request Incomplete" 30 + raise ValidationError(msg) 32 31 phone_number = request.data["phone_number"] 33 32 otp = request.data["otp"] 34 33 try: 35 34 mobile_validator(phone_number) 36 - except error: 37 - raise ValidationError({"phone_number": "Invalid phone number format"}) 35 + except Exception as e: 36 + raise ValidationError( 37 + {"phone_number": "Invalid phone number format"} 38 + ) from e 38 39 if len(otp) != settings.OTP_LENGTH: 39 40 raise ValidationError({"otp": "Invalid OTP"}) 40 41 ··· 47 48 48 49 otp_object.is_used = True 49 50 otp_object.save() 50 - # return JWT 51 51 52 52 token = PatientToken() 53 53 token["phone_number"] = phone_number
+1 -2
care/facility/api/viewsets/patient_otp_data.py
··· 28 28 def get_serializer_class(self): 29 29 if self.action == "list": 30 30 return PatientListSerializer 31 - else: 32 - return self.serializer_class 31 + return self.serializer_class
+3 -3
care/facility/api/viewsets/patient_sample.py
··· 93 93 return serializer_class 94 94 95 95 def get_queryset(self): 96 - queryset = super(PatientSampleViewSet, self).get_queryset() 96 + queryset = super().get_queryset() 97 97 if self.kwargs.get("patient_external_id") is not None: 98 98 queryset = queryset.filter( 99 99 patient__external_id=self.kwargs.get("patient_external_id") ··· 118 118 not self.kwargs.get("patient_external_id") 119 119 and request.user.user_type < User.TYPE_VALUE_MAP["Doctor"] 120 120 ): 121 - raise PermissionDenied() 121 + raise PermissionDenied 122 122 123 123 if settings.CSV_REQUEST_PARAMETER in request.GET: 124 124 queryset = ( ··· 131 131 field_header_map=PatientSample.CSV_MAPPING, 132 132 field_serializer_map=PatientSample.CSV_MAKE_PRETTY, 133 133 ) 134 - return super(PatientSampleViewSet, self).list(request, *args, **kwargs) 134 + return super().list(request, *args, **kwargs) 135 135 136 136 def perform_create(self, serializer): 137 137 validated_data = serializer.validated_data
+2 -3
care/facility/api/viewsets/prescription.py
··· 184 184 185 185 186 186 class MedibaseViewSet(ViewSet): 187 - 188 187 def serialize_data(self, objects: list[MedibaseMedicine]): 189 188 return [medicine.get_representation() for medicine in objects] 190 189 ··· 195 194 limit = 30 196 195 197 196 query = [] 198 - if type := request.query_params.get("type"): 199 - query.append(MedibaseMedicine.type == type) 197 + if t := request.query_params.get("type"): 198 + query.append(MedibaseMedicine.type == t) 200 199 201 200 if q := request.query_params.get("query"): 202 201 query.append(
+2 -2
care/facility/api/viewsets/resources.py
··· 46 46 q_objects |= Q(approving_facility__state=request.user.state) 47 47 q_objects |= Q(assigned_facility__state=request.user.state) 48 48 return queryset.filter(q_objects) 49 - elif request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 49 + if request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 50 50 q_objects = Q(origin_facility__district=request.user.district) 51 51 q_objects |= Q(approving_facility__district=request.user.district) 52 52 q_objects |= Q(assigned_facility__district=request.user.district) ··· 165 165 request__assigned_facility__state=self.request.user.state 166 166 ) 167 167 return queryset.filter(q_objects) 168 - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 168 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 169 169 q_objects = Q( 170 170 request__origin_facility__district=self.request.user.district 171 171 )
+34 -29
care/facility/api/viewsets/shifting.py
··· 15 15 from rest_framework.viewsets import GenericViewSet 16 16 17 17 from care.facility.api.serializers.shifting import ( 18 + REVERSE_SHIFTING_STATUS_CHOICES, 18 19 ShiftingDetailSerializer, 19 20 ShiftingListSerializer, 20 21 ShiftingRequestCommentDetailSerializer, ··· 161 162 @action(detail=True, methods=["POST"]) 162 163 def transfer(self, request, *args, **kwargs): 163 164 shifting_obj = self.get_object() 164 - if has_facility_permission( 165 - request.user, shifting_obj.shifting_approving_facility 166 - ) or has_facility_permission(request.user, shifting_obj.assigned_facility): 167 - if shifting_obj.assigned_facility and shifting_obj.status >= 70: 168 - if shifting_obj.patient: 169 - patient = shifting_obj.patient 170 - patient.facility = shifting_obj.assigned_facility 171 - patient.is_active = True 172 - patient.allow_transfer = False 173 - patient.save() 174 - shifting_obj.status = 80 175 - shifting_obj.save(update_fields=["status"]) 176 - # Discharge from all other active consultations 177 - PatientConsultation.objects.filter( 178 - patient=patient, discharge_date__isnull=True 179 - ).update( 180 - discharge_date=localtime(now()), 181 - new_discharge_reason=NewDischargeReasonEnum.REFERRED, 182 - ) 183 - ConsultationBed.objects.filter( 184 - consultation=patient.last_consultation, 185 - end_date__isnull=True, 186 - ).update(end_date=localtime(now())) 165 + if ( 166 + ( 167 + has_facility_permission( 168 + request.user, shifting_obj.shifting_approving_facility 169 + ) 170 + or has_facility_permission(request.user, shifting_obj.assigned_facility) 171 + ) 172 + and shifting_obj.assigned_facility 173 + and shifting_obj.status 174 + >= REVERSE_SHIFTING_STATUS_CHOICES["TRANSFER IN PROGRESS"] 175 + and shifting_obj.patient 176 + ): 177 + patient = shifting_obj.patient 178 + patient.facility = shifting_obj.assigned_facility 179 + patient.is_active = True 180 + patient.allow_transfer = False 181 + patient.save() 182 + shifting_obj.status = REVERSE_SHIFTING_STATUS_CHOICES["COMPLETED"] 183 + shifting_obj.save(update_fields=["status"]) 184 + # Discharge from all other active consultations 185 + PatientConsultation.objects.filter( 186 + patient=patient, discharge_date__isnull=True 187 + ).update( 188 + discharge_date=localtime(now()), 189 + new_discharge_reason=NewDischargeReasonEnum.REFERRED, 190 + ) 191 + ConsultationBed.objects.filter( 192 + consultation=patient.last_consultation, 193 + end_date__isnull=True, 194 + ).update(end_date=localtime(now())) 187 195 188 - return Response( 189 - {"transfer": "completed"}, status=status.HTTP_200_OK 190 - ) 196 + return Response({"transfer": "completed"}, status=status.HTTP_200_OK) 191 197 return Response( 192 198 {"error": "Invalid Request"}, status=status.HTTP_400_BAD_REQUEST 193 199 ) ··· 204 210 field_header_map=ShiftingRequest.CSV_MAPPING, 205 211 field_serializer_map=ShiftingRequest.CSV_MAKE_PRETTY, 206 212 ) 207 - response = super().list(request, *args, **kwargs) 208 - return response 213 + return super().list(request, *args, **kwargs) 209 214 210 215 211 216 class ShifitngRequestCommentViewSet( ··· 236 241 request__assigned_facility__state=self.request.user.state 237 242 ) 238 243 return queryset.filter(q_objects) 239 - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 244 + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: 240 245 q_objects = Q( 241 246 request__origin_facility__district=self.request.user.district 242 247 )
-55
care/facility/api/viewsets/summary.py
··· 48 48 def list(self, request, *args, **kwargs): 49 49 return super().list(request, *args, **kwargs) 50 50 51 - # def get_queryset(self): 52 - # user = self.request.user 53 - # queryset = self.queryset 54 - # if user.is_superuser: 55 - # return queryset 56 - # elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"]: 57 - # return queryset.filter(facility__district=user.district) 58 - # elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateReadOnlyAdmin"]: 59 - # return queryset.filter(facility__state=user.state) 60 - # return queryset.filter(facility__users__id__exact=user.id) 61 - 62 51 63 52 class TriageSummaryViewSet(ListModelMixin, GenericViewSet): 64 53 lookup_field = "external_id" ··· 71 60 filter_backends = (filters.DjangoFilterBackend,) 72 61 filterset_class = FacilitySummaryFilter 73 62 74 - # def get_queryset(self): 75 - # user = self.request.user 76 - # queryset = self.queryset 77 - # if user.is_superuser: 78 - # return queryset 79 - # elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"]: 80 - # return queryset.filter(facility__district=user.district) 81 - # elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateReadOnlyAdmin"]: 82 - # return queryset.filter(facility__state=user.state) 83 - # return queryset.filter(facility__users__id__exact=user.id) 84 - 85 63 @extend_schema(tags=["summary"]) 86 64 @method_decorator(cache_page(60 * 60)) 87 65 def list(self, request, *args, **kwargs): ··· 99 77 filter_backends = (filters.DjangoFilterBackend,) 100 78 filterset_class = FacilitySummaryFilter 101 79 102 - # def get_queryset(self): 103 - # user = self.request.user 104 - # queryset = self.queryset 105 - # if user.is_superuser: 106 - # return queryset 107 - # elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"]: 108 - # return queryset.filter(facility__district=user.district) 109 - # elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateReadOnlyAdmin"]: 110 - # return queryset.filter(facility__state=user.state) 111 - # return queryset.filter(facility__users__id__exact=user.id) 112 - 113 80 @extend_schema(tags=["summary"]) 114 81 @method_decorator(cache_page(60 * 60 * 10)) 115 82 def list(self, request, *args, **kwargs): ··· 132 99 def list(self, request, *args, **kwargs): 133 100 return super().list(request, *args, **kwargs) 134 101 135 - # def get_queryset(self): 136 - # user = self.request.user 137 - # queryset = self.queryset 138 - # if user.is_superuser: 139 - # return queryset 140 - # elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"]: 141 - # return queryset.filter(facility__district=user.district) 142 - # elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateReadOnlyAdmin"]: 143 - # return queryset.filter(facility__state=user.state) 144 - # return queryset.filter(facility__users__id__exact=user.id) 145 - 146 102 147 103 class DistrictSummaryFilter(filters.FilterSet): 148 104 start_date = filters.DateFilter(field_name="created_date", lookup_expr="gte") ··· 168 124 @method_decorator(cache_page(60 * 10)) 169 125 def list(self, request, *args, **kwargs): 170 126 return super().list(request, *args, **kwargs) 171 - 172 - # def get_queryset(self): 173 - # user = self.request.user 174 - # queryset = self.queryset 175 - # if user.is_superuser: 176 - # return queryset 177 - # elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"]: 178 - # return queryset.filter(facility__district=user.district) 179 - # elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateReadOnlyAdmin"]: 180 - # return queryset.filter(facility__state=user.state) 181 - # return queryset.filter(facility__users__id__exact=user.id)
+4 -5
care/facility/events/handler.py
··· 25 25 fields: set[str] = ( 26 26 get_changed_fields(old_instance, object_instance) 27 27 if old_instance 28 - else {field.name for field in object_instance._meta.fields} 28 + else {field.name for field in object_instance._meta.fields} # noqa: SLF001 29 29 ) 30 30 31 31 fields_to_store = fields_to_store & fields if fields_to_store else fields ··· 91 91 taken_at = created_date 92 92 93 93 with transaction.atomic(): 94 - if isinstance(objects, (QuerySet, list, tuple)): 94 + if isinstance(objects, QuerySet | list | tuple): 95 95 if old is not None: 96 - raise ValueError( 97 - "diff is not available when objects is a list or queryset" 98 - ) 96 + msg = "diff is not available when objects is a list or queryset" 97 + raise ValueError(msg) 99 98 for obj in objects: 100 99 create_consultation_event_entry( 101 100 consultation_id,
+5 -4
care/facility/management/commands/add_daily_round_consultation.py
··· 11 11 help = "Populate daily round for consultations" 12 12 13 13 def handle(self, *args, **options): 14 + batch_size = 10000 14 15 consultations = list( 15 16 PatientConsultation.objects.filter( 16 17 last_daily_round__isnull=True 17 18 ).values_list("external_id") 18 19 ) 19 20 total_count = len(consultations) 20 - print(f"{total_count} Consultations need to be updated") 21 + self.stdout.write(f"{total_count} Consultations need to be updated") 21 22 i = 0 22 23 for consultation_eid in consultations: 23 - if i > 10000 and i % 10000 == 0: 24 - print(f"{i} operations performed") 24 + if i > batch_size and i % batch_size == 0: 25 + self.stdout.write(f"{i} operations performed") 25 26 i = i + 1 26 27 PatientConsultation.objects.filter(external_id=consultation_eid[0]).update( 27 28 last_daily_round=DailyRound.objects.filter( ··· 30 31 .order_by("-created_date") 31 32 .first() 32 33 ) 33 - print("Operation Completed") 34 + self.stdout.write("Operation Completed")
+2 -2
care/facility/management/commands/clean_patient_phone_numbers.py
··· 33 33 except Exception: 34 34 failed.append({"id": patient.id, "phone_number": patient.phone_number}) 35 35 36 - print(f"Completed for {qs.count()} | Failed for {len(failed)}") 37 - print(f"Failed for {json.dumps(failed)}") 36 + self.stdout.write(f"Completed for {qs.count()} | Failed for {len(failed)}") 37 + self.stdout.write(f"Failed for {json.dumps(failed)}")
+1 -1
care/facility/management/commands/generate_jwks.py
··· 11 11 help = "Generate JWKS" 12 12 13 13 def handle(self, *args, **options): 14 - print(generate_encoded_jwks()) 14 + self.stdout.write(generate_encoded_jwks())
+3 -4
care/facility/management/commands/load_dummy_data.py
··· 17 17 def handle(self, *args, **options): 18 18 env = os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") 19 19 if "production" in env or "staging" in env: 20 - raise CommandError( 21 - "This command is not intended to be run in production environment." 22 - ) 20 + msg = "This command is not intended to be run in production environment." 21 + raise CommandError(msg) 23 22 24 23 try: 25 24 management.call_command("loaddata", self.BASE_URL + "states.json") ··· 32 31 ) 33 32 management.call_command("populate_investigations") 34 33 except Exception as e: 35 - raise CommandError(e) 34 + raise CommandError(e) from e
+4 -1
care/facility/management/commands/load_event_types.py
··· 271 271 ) 272 272 273 273 def create_objects( 274 - self, types: tuple[EventType, ...], model: str = None, parent: EventType = None 274 + self, 275 + types: tuple[EventType, ...], 276 + model: str | None = None, 277 + parent: EventType = None, 275 278 ): 276 279 for event_type in types: 277 280 model = event_type.get("model", model)
+12 -6
care/facility/management/commands/load_icd11_diagnoses_data.py
··· 1 1 import json 2 + from typing import TYPE_CHECKING 2 3 4 + from django.conf import settings 3 5 from django.core.management import BaseCommand, CommandError 4 6 5 7 from care.facility.models.icd11_diagnosis import ICD11Diagnosis 6 8 9 + if TYPE_CHECKING: 10 + from pathlib import Path 11 + 7 12 8 13 def fetch_data(): 9 - with open("data/icd11.json") as json_file: 14 + icd11_json: Path = settings.BASE_DIR / "data" / "icd11.json" 15 + with icd11_json.open() as json_file: 10 16 return json.load(json_file) 11 17 12 18 ··· 117 123 118 124 # The following code is never executed as the `icd11.json` file is 119 125 # pre-sorted and hence the parent is always present before the child. 120 - print("Full-scan for", id, item["label"]) 126 + self.stdout.write("Full-scan for", id, item["label"]) 121 127 return self.find_roots( 122 - [ 128 + next( 123 129 icd11_object 124 130 for icd11_object in self.data 125 131 if icd11_object["ID"] == item["parentId"] 126 - ][0] 132 + ) 127 133 ) 128 134 129 135 def handle(self, *args, **options): 130 - print("Loading ICD11 diagnoses data to database...") 136 + self.stdout.write("Loading ICD11 diagnoses data to database...") 131 137 try: 132 138 self.data = fetch_data() 133 139 ··· 162 168 ignore_conflicts=True, # Voluntarily set to skip duplicates, so that we can run this command multiple times + existing relations are not affected 163 169 ) 164 170 except Exception as e: 165 - raise CommandError(e) 171 + raise CommandError(e) from e
+10 -2
care/facility/management/commands/load_medicines_data.py
··· 1 1 import json 2 + from typing import TYPE_CHECKING 2 3 4 + from django.conf import settings 3 5 from django.core.management import BaseCommand 4 6 5 7 from care.facility.models import MedibaseMedicine 8 + 9 + if TYPE_CHECKING: 10 + from pathlib import Path 6 11 7 12 8 13 class Command(BaseCommand): ··· 14 19 help = "Loads Medibase Medicines into the database from medibase.json" 15 20 16 21 def fetch_data(self): 17 - with open("data/medibase.json") as json_file: 22 + medibase_json: Path = settings.BASE_DIR / "data" / "medibase.json" 23 + with medibase_json.open() as json_file: 18 24 return json.load(json_file) 19 25 20 26 def handle(self, *args, **options): 21 - print("Loading Medibase Medicines into the database from medibase.json") 27 + self.stdout.write( 28 + "Loading Medibase Medicines into the database from medibase.json" 29 + ) 22 30 23 31 medibase_objects = self.fetch_data() 24 32 MedibaseMedicine.objects.bulk_create(
+4 -4
care/facility/management/commands/load_redis_index.py
··· 18 18 19 19 def handle(self, *args, **options): 20 20 if cache.get("redis_index_loading"): 21 - print("Redis Index already loading, skipping") 21 + self.stdout.write("Redis Index already loading, skipping") 22 22 return 23 23 24 - cache.set("redis_index_loading", True, timeout=60 * 5) 24 + cache.set("redis_index_loading", value=True, timeout=60 * 5) 25 25 26 26 load_icd11_diagnosis() 27 27 load_medibase_medicines() ··· 35 35 if load_static_data: 36 36 load_static_data() 37 37 except ModuleNotFoundError: 38 - print(f"Module {module_path} not found") 38 + self.stdout.write(f"Module {module_path} not found") 39 39 except Exception as e: 40 - print(f"Error loading static data for {plug.name}: {e}") 40 + self.stdout.write(f"Error loading static data for {plug.name}: {e}") 41 41 42 42 cache.delete("redis_index_loading")
+1 -1
care/facility/management/commands/port_patient_wards.py
··· 32 32 patient.save() 33 33 except Exception: 34 34 failed += 1 35 - print( 35 + self.stdout.write( 36 36 str(failed), 37 37 " failed operations ", 38 38 str(success),
+6 -6
care/facility/management/commands/summarize.py
··· 1 1 from django.core.management.base import BaseCommand 2 2 3 - from care.facility.utils.summarisation.district.patient_summary import ( 3 + from care.facility.utils.summarization.district.patient_summary import ( 4 4 district_patient_summary, 5 5 ) 6 - from care.facility.utils.summarisation.facility_capacity import ( 6 + from care.facility.utils.summarization.facility_capacity import ( 7 7 facility_capacity_summary, 8 8 ) 9 - from care.facility.utils.summarisation.patient_summary import patient_summary 9 + from care.facility.utils.summarization.patient_summary import patient_summary 10 10 11 11 12 12 class Command(BaseCommand): ··· 18 18 19 19 def handle(self, *args, **options): 20 20 patient_summary() 21 - print("Patients Summarised") 21 + self.stdout.write("Patients Summarised") 22 22 facility_capacity_summary() 23 - print("Capacity Summarised") 23 + self.stdout.write("Capacity Summarised") 24 24 district_patient_summary() 25 - print("District Wise Patient Summarised") 25 + self.stdout.write("District Wise Patient Summarised")
+2 -2
care/facility/management/commands/sync_external_test_patient.py
··· 12 12 help = "Sync the patient created flag in external tests" 13 13 14 14 def handle(self, *args, **options): 15 - print("Starting Sync") 15 + self.stdout.write("Starting Sync") 16 16 for patient in PatientRegistration.objects.all(): 17 17 if patient.srf_id: 18 18 PatientExternalTest.objects.filter( 19 19 srf_id__iexact=patient.srf_id 20 20 ).update(patient_created=True) 21 - print("Completed Sync") 21 + self.stdout.write("Completed Sync")
+2 -2
care/facility/management/commands/sync_patient_age.py
··· 19 19 except Exception: 20 20 failed += 1 21 21 if failed: 22 - print(f"Failed for {failed} Patient") 22 + self.stdout.write(f"Failed for {failed} Patient") 23 23 else: 24 - print("Successfully Synced Age") 24 + self.stdout.write("Successfully Synced Age")
-13
care/facility/models/ambulance.py
··· 32 32 ) 33 33 owner_is_smart_phone = models.BooleanField(default=True) 34 34 35 - # primary_district = models.IntegerField(choices=DISTRICT_CHOICES, blank=False) 36 - # secondary_district = models.IntegerField(choices=DISTRICT_CHOICES, blank=True, null=True) 37 - # third_district = models.IntegerField(choices=DISTRICT_CHOICES, blank=True, null=True) 38 - 39 35 primary_district = models.ForeignKey( 40 36 District, 41 37 on_delete=models.PROTECT, ··· 121 117 ] 122 118 ) 123 119 ) 124 - 125 - # class Meta: 126 - # constraints = [ 127 - # models.CheckConstraint( 128 - # name="ambulance_free_or_price", 129 - # check=models.Q(price_per_km__isnull=False) 130 - # | models.Q(has_free_service=True), 131 - # ) 132 - # ] 133 120 134 121 135 122 class AmbulanceDriver(FacilityBaseModel):
+5 -1
care/facility/models/asset.py
··· 62 62 63 63 AssetTypeChoices = [(e.value, e.name) for e in AssetType] 64 64 65 - AssetClassChoices = [(e.name, e.value._name) for e in AssetClasses] 65 + AssetClassChoices = [(e.name, e.value._name) for e in AssetClasses] # noqa: SLF001 66 66 67 67 68 68 class Status(enum.Enum): ··· 162 162 "hostname": hostname, 163 163 "source": "facility", 164 164 } 165 + return None 165 166 166 167 class Meta: 167 168 constraints = [ ··· 312 313 313 314 class Meta: 314 315 ordering = ["-edited_on"] 316 + 317 + def __str__(self): 318 + return f"{self.asset_service.asset.name} - {self.serviced_on}"
+8 -5
care/facility/models/daily_round.py
··· 571 571 if self.output is not None: 572 572 self.total_output_calculated = sum([x["quantity"] for x in self.output]) 573 573 574 - super(DailyRound, self).save(*args, **kwargs) 574 + super().save(*args, **kwargs) 575 575 576 576 @staticmethod 577 577 def has_read_permission(request): ··· 585 585 return request.user.is_superuser or ( 586 586 (request.user in consultation.patient.facility.users.all()) 587 587 or ( 588 - request.user == consultation.assigned_to 589 - or request.user == consultation.patient.assigned_to 588 + request.user 589 + in (consultation.assigned_to, consultation.patient.assigned_to) 590 590 ) 591 591 or ( 592 592 request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] ··· 620 620 and request.user in self.consultation.patient.facility.users.all() 621 621 ) 622 622 or ( 623 - self.consultation.assigned_to == request.user 624 - or request.user == self.consultation.patient.assigned_to 623 + request.user 624 + in ( 625 + self.consultation.assigned_to, 626 + self.consultation.patient.assigned_to, 627 + ) 625 628 ) 626 629 or ( 627 630 request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]
+2 -1
care/facility/models/encounter_symptom.py
··· 83 83 84 84 def save(self, *args, **kwargs): 85 85 if self.other_symptom and self.symptom != Symptom.OTHERS: 86 - raise ValueError("Other Symptom should be empty when Symptom is not OTHERS") 86 + msg = "Other Symptom should be empty when Symptom is not OTHERS" 87 + raise ValueError(msg) 87 88 88 89 if self.clinical_impression_status != ClinicalImpressionStatus.ENTERED_IN_ERROR: 89 90 if self.onset_date and self.cure_date:
+10 -17
care/facility/models/events.py
··· 31 31 created_date = models.DateTimeField(auto_now_add=True) 32 32 is_active = models.BooleanField(default=True) 33 33 34 - def get_descendants(self): 35 - descendants = list(self.children.all()) 36 - for child in self.children.all(): 37 - descendants.extend(child.get_descendants()) 38 - return descendants 34 + def __str__(self) -> str: 35 + return f"{self.model} - {self.name}" 39 36 40 37 def save(self, *args, **kwargs): 41 38 if self.description is not None and not self.description.strip(): 42 39 self.description = None 43 40 return super().save(*args, **kwargs) 44 41 45 - def __str__(self) -> str: 46 - return f"{self.model} - {self.name}" 42 + def get_descendants(self): 43 + descendants = list(self.children.all()) 44 + for child in self.children.all(): 45 + descendants.extend(child.get_descendants()) 46 + return descendants 47 47 48 48 49 49 class PatientConsultationEvent(models.Model): ··· 68 68 max_length=10, choices=ChangeType, default=ChangeType.CREATED 69 69 ) 70 70 71 - def __str__(self) -> str: 72 - return f"{self.id} - {self.consultation_id} - {self.event_type} - {self.change_type}" 73 - 74 71 class Meta: 75 72 ordering = ["-created_date"] 76 73 indexes = [models.Index(fields=["consultation", "is_latest"])] 77 - # constraints = [ 78 - # models.UniqueConstraint( 79 - # fields=["consultation", "event_type", "is_latest"], 80 - # condition=models.Q(is_latest=True), 81 - # name="unique_consultation_event_type_is_latest", 82 - # ) 83 - # ] 74 + 75 + def __str__(self) -> str: 76 + return f"{self.id} - {self.consultation_id} - {self.event_type} - {self.change_type}"
+9 -8
care/facility/models/facility.py
··· 90 90 (5, "Hotel"), 91 91 (6, "Lodge"), 92 92 (7, "TeleMedicine"), 93 - # (8, "Govt Hospital"), # Change from "Govt Hospital" to "Govt Medical College Hospitals" 93 + # 8, "Govt Hospital" # Change from "Govt Hospital" to "Govt Medical College Hospitals" 94 94 (9, "Govt Labs"), 95 95 (10, "Private Labs"), 96 96 # Use 8xx for Govt owned hospitals and health centres 97 97 (800, "Primary Health Centres"), 98 - # (801, "24x7 Public Health Centres"), # Change from "24x7 Public Health Centres" to "Primary Health Centres" 98 + # 801, "24x7 Public Health Centres" # Change from "24x7 Public Health Centres" to "Primary Health Centres" 99 99 (802, "Family Health Centres"), 100 100 (803, "Community Health Centres"), 101 - # (820, "Urban Primary Health Center"), # Change from "Urban Primary Health Center" to "Primary Health Centres" 101 + # 820, "Urban Primary Health Center" # Change from "Urban Primary Health Center" to "Primary Health Centres" 102 102 (830, "Taluk Hospitals"), 103 - # (831, "Taluk Headquarters Hospitals"), # Change from "Taluk Headquarters Hospitals" to "Taluk Hospitals" 103 + # 831, "Taluk Headquarters Hospitals" # Change from "Taluk Headquarters Hospitals" to "Taluk Hospitals" 104 104 (840, "Women and Child Health Centres"), 105 - # (850, "General hospitals"), # Change from "General hospitals" to "District Hospitals" 105 + # 850, "General hospitals" # Change from "General hospitals" to "District Hospitals" 106 106 (860, "District Hospitals"), 107 107 (870, "Govt Medical College Hospitals"), 108 108 (900, "Co-operative hospitals"), 109 109 (910, "Autonomous healthcare facility"), 110 110 # Use 9xx for Labs 111 - # (950, "Corona Testing Labs"), # Change from "Corona Testing Labs" to "Govt Labs" 111 + # 950, "Corona Testing Labs" # Change from "Corona Testing Labs" to "Govt Labs" 112 112 # Use 10xx for Corona Care Center 113 - # (1000, "Corona Care Centre"), # Change from "Corona Care Centre" to "Other" 113 + # 1000, "Corona Care Centre" # Change from "Corona Care Centre" to "Other" 114 114 (1010, "COVID-19 Domiciliary Care Center"), 115 115 # Use 11xx for First Line Treatment Centre 116 116 (1100, "First Line Treatment Centre"), ··· 207 207 "hub_id", flat=True 208 208 ) 209 209 if spoke_id in ancestors_of_base: 210 - raise serializers.ValidationError("This facility is already an ancestor hub") 210 + msg = "This facility is already an ancestor hub" 211 + raise serializers.ValidationError(msg) 211 212 for ancestor in ancestors_of_base: 212 213 check_if_spoke_is_not_ancestor(ancestor, spoke_id) 213 214
+10 -12
care/facility/models/file_upload.py
··· 46 46 class Meta: 47 47 abstract = True 48 48 49 - def delete(self, *args): 50 - self.deleted = True 51 - self.save(update_fields=["deleted"]) 52 - 53 49 def save(self, *args, **kwargs): 54 50 if "force_insert" in kwargs or (not self.internal_name): 55 51 internal_name = str(uuid4()) + str(int(time.time())) ··· 60 56 self.internal_name = internal_name 61 57 return super().save(*args, **kwargs) 62 58 59 + def delete(self, *args): 60 + self.deleted = True 61 + self.save(update_fields=["deleted"]) 62 + 63 63 def get_extension(self): 64 64 parts = self.internal_name.split(".") 65 65 return f".{parts[-1]}" if len(parts) > 1 else "" ··· 67 67 def signed_url( 68 68 self, duration=60 * 60, mime_type=None, bucket_type=BucketType.PATIENT 69 69 ): 70 - config, bucket_name = get_client_config(bucket_type, True) 70 + config, bucket_name = get_client_config(bucket_type, external=True) 71 71 s3 = boto3.client("s3", **config) 72 72 params = { 73 73 "Bucket": bucket_name, ··· 82 82 ) 83 83 84 84 def read_signed_url(self, duration=60 * 60, bucket_type=BucketType.PATIENT): 85 - config, bucket_name = get_client_config(bucket_type, True) 85 + config, bucket_name = get_client_config(bucket_type, external=True) 86 86 s3 = boto3.client("s3", **config) 87 87 return s3.generate_presigned_url( 88 88 "get_object", ··· 128 128 all data will be private and file access will be given on a NEED TO BASIS ONLY 129 129 """ 130 130 131 - # TODO : Periodic tasks that removes files that were never uploaded 132 - 133 131 class FileType(models.IntegerChoices): 134 132 OTHER = 0, "OTHER" 135 133 PATIENT = 1, "PATIENT" ··· 163 161 # TODO: switch to Choices.choices 164 162 FileTypeChoices = [(x.value, x.name) for x in FileType] 165 163 FileCategoryChoices = [(x.value, x.name) for x in BaseFileUpload.FileCategory] 164 + 165 + def __str__(self): 166 + return f"{self.FileTypeChoices[self.file_type][1]} - {self.name}{' (Archived)' if self.is_archived else ''}" 166 167 167 168 def save(self, *args, **kwargs): 168 169 from care.facility.models import PatientConsent ··· 192 193 ).exclude(pk=self.pk if self.is_archived else None) 193 194 ) 194 195 if not new_consent 195 - else models.Value(True) 196 + else models.Value(value=True) 196 197 ) 197 198 ) 198 199 .filter(has_files=True) ··· 203 204 consultation.save() 204 205 205 206 return super().save(*args, **kwargs) 206 - 207 - def __str__(self): 208 - return f"{self.FileTypeChoices[self.file_type][1]} - {self.name}{' (Archived)' if self.is_archived else ''}"
care/facility/models/json_schema/__init__.py

This is a binary file and will not be displayed.

+9 -9
care/facility/models/mixins/permissions/patient.py
··· 19 19 20 20 doctor_allowed = False 21 21 if self.last_consultation: 22 - doctor_allowed = ( 23 - self.last_consultation.assigned_to == request.user 24 - or request.user == self.assigned_to 22 + doctor_allowed = request.user in ( 23 + self.last_consultation.assigned_to, 24 + self.assigned_to, 25 25 ) 26 26 return request.user.is_superuser or ( 27 27 (hasattr(self, "created_by") and request.user == self.created_by) ··· 60 60 61 61 doctor_allowed = False 62 62 if self.last_consultation: 63 - doctor_allowed = ( 64 - self.last_consultation.assigned_to == request.user 65 - or request.user == self.assigned_to 63 + doctor_allowed = request.user in ( 64 + self.last_consultation.assigned_to, 65 + self.assigned_to, 66 66 ) 67 67 68 68 return request.user.is_superuser or ( ··· 99 99 100 100 doctor_allowed = False 101 101 if self.last_consultation: 102 - doctor_allowed = ( 103 - self.last_consultation.assigned_to == request.user 104 - or request.user == self.assigned_to 102 + doctor_allowed = request.user in ( 103 + self.last_consultation.assigned_to, 104 + self.assigned_to, 105 105 ) 106 106 107 107 return (
+32 -30
care/facility/models/patient.py
··· 69 69 70 70 SourceChoices = [(e.value, e.name) for e in SourceEnum] 71 71 72 - class vaccineEnum(enum.Enum): 72 + class VaccineEnum(enum.Enum): 73 73 COVISHIELD = "CoviShield" 74 74 COVAXIN = "Covaxin" 75 75 SPUTNIK = "Sputnik" ··· 78 78 JANSSEN = "Janssen" 79 79 SINOVAC = "Sinovac" 80 80 81 - vaccineChoices = [(e.value, e.name) for e in vaccineEnum] 81 + VaccineChoices = [(e.value, e.name) for e in VaccineEnum] 82 82 83 83 class ActionEnum(enum.Enum): 84 84 NO_ACTION = 10 ··· 116 116 "PatientMetaInfo", on_delete=models.SET_NULL, null=True 117 117 ) 118 118 119 - # name_old = EncryptedCharField(max_length=200, default="") 120 119 name = models.CharField(max_length=200, default="") 121 120 122 121 gender = models.IntegerField(choices=GENDER_CHOICES, blank=False) 123 122 124 - # phone_number_old = EncryptedCharField(max_length=14, validators=[phone_number_regex], default="") 125 123 phone_number = models.CharField( 126 124 max_length=14, validators=[mobile_or_landline_number_validator], default="" 127 125 ) ··· 130 128 max_length=14, validators=[mobile_or_landline_number_validator], default="" 131 129 ) 132 130 133 - # address_old = EncryptedTextField(default="") 134 131 address = models.TextField(default="") 135 132 permanent_address = models.TextField(default="") 136 133 ··· 205 202 blank=True, 206 203 verbose_name="Already pescribed medication if any", 207 204 ) 208 - has_SARI = models.BooleanField( 205 + has_SARI = models.BooleanField( # noqa: N815 209 206 default=False, verbose_name="Does the Patient Suffer from SARI" 210 207 ) 211 208 ··· 383 380 validators=[MinValueValidator(0), MaxValueValidator(3)], 384 381 ) 385 382 vaccine_name = models.CharField( 386 - choices=vaccineChoices, 383 + choices=VaccineChoices, 387 384 default=None, 388 385 null=True, 389 386 blank=False, ··· 487 484 year_str = f"{delta.years} year{pluralize(delta.years)}" 488 485 return f"{year_str}" 489 486 490 - elif delta.months > 0: 487 + if delta.months > 0: 491 488 month_str = f"{delta.months} month{pluralize(delta.months)}" 492 489 day_str = ( 493 490 f" {delta.days} day{pluralize(delta.days)}" if delta.days > 0 else "" 494 491 ) 495 492 return f"{month_str}{day_str}" 496 493 497 - elif delta.days > 0: 498 - day_str = f"{delta.days} day{pluralize(delta.days)}" 499 - return day_str 494 + if delta.days > 0: 495 + return f"{delta.days} day{pluralize(delta.days)}" 500 496 501 - else: 502 - return "0 days" 497 + return "0 days" 503 498 504 499 def annotate_diagnosis_ids(*args, **kwargs): 505 500 return ArrayAgg( ··· 552 547 "last_consultation__discharge_date__time": "Time of discharge", 553 548 } 554 549 555 - def format_as_date(date): 556 - return date.strftime("%d/%m/%Y") 550 + def format_as_date(self): 551 + return self.strftime("%d/%m/%Y") 557 552 558 - def format_as_time(time): 559 - return time.strftime("%H:%M") 553 + def format_as_time(self): 554 + return self.strftime("%H:%M") 560 555 561 - def format_diagnoses(diagnosis_ids): 562 - diagnoses = get_icd11_diagnoses_objects_by_ids(diagnosis_ids) 556 + def format_diagnoses(self): 557 + diagnoses = get_icd11_diagnoses_objects_by_ids(self) 563 558 return ", ".join([diagnosis["label"] for diagnosis in diagnoses]) 564 559 565 560 CSV_MAKE_PRETTY = { ··· 645 640 ) 646 641 head_of_household = models.BooleanField(blank=True, null=True) 647 642 643 + def __str__(self): 644 + return f"PatientMetaInfo - {self.id}" 645 + 648 646 649 647 class PatientContactDetails(models.Model): 650 648 class RelationEnum(enum.IntEnum): ··· 660 658 OTHERS = 10 661 659 662 660 class ModeOfContactEnum(enum.IntEnum): 663 - # "1. Touched body fluids of the patient (respiratory tract secretions/blood/vomit/saliva/urine/faces)" 661 + # Touched body fluids of the patient (respiratory tract secretions/blood/vomit/saliva/urine/faces) 664 662 TOUCHED_BODY_FLUIDS = 1 665 - # "2. Had direct physical contact with the body of the patient 666 - # including physical examination without full precautions." 663 + # Had direct physical contact with the body of the patient including physical examination without full precautions. 667 664 DIRECT_PHYSICAL_CONTACT = 2 668 - # "3. Touched or cleaned the linens/clothes/or dishes of the patient" 665 + # Touched or cleaned the linens/clothes/or dishes of the patient 669 666 CLEANED_USED_ITEMS = 3 670 - # "4. Lives in the same household as the patient." 667 + # Lives in the same household as the patient. 671 668 LIVE_IN_SAME_HOUSEHOLD = 4 672 - # "5. Close contact within 3ft (1m) of the confirmed case without precautions." 669 + # Close contact within 3ft (1m) of the confirmed case without precautions. 673 670 CLOSE_CONTACT_WITHOUT_PRECAUTION = 5 674 - # "6. Passenger of the aeroplane with a confirmed COVID -19 passenger for more than 6 hours." 671 + # Passenger of the aeroplane with a confirmed COVID -19 passenger for more than 6 hours. 675 672 CO_PASSENGER_AEROPLANE = 6 676 - # "7. Health care workers and other contacts who had full PPE while handling the +ve case" 673 + # Health care workers and other contacts who had full PPE while handling the +ve case 677 674 HEALTH_CARE_WITH_PPE = 7 678 - # "8. Shared the same space(same class for school/worked in 679 - # same room/similar and not having a high risk exposure" 675 + # Shared the same space(same class for school/worked in same room/similar and not having a high risk exposure 680 676 SHARED_SAME_SPACE_WITHOUT_HIGH_EXPOSURE = 8 681 - # "9. Travel in the same environment (bus/train/Flight) but not having a high-risk exposure as cited above." 677 + # Travel in the same environment (bus/train/Flight) but not having a high-risk exposure as cited above. 682 678 TRAVELLED_TOGETHER_WITHOUT_HIGH_EXPOSURE = 9 683 679 684 680 RelationChoices = [(item.value, item.name) for item in RelationEnum] ··· 708 704 deleted = models.BooleanField(default=False) 709 705 710 706 objects = BaseManager() 707 + 708 + def __str__(self): 709 + return f"{self.patient.name} - {self.patient_in_contact.name} - {self.get_relation_with_patient_display()}" 711 710 712 711 713 712 class Disease(models.Model): ··· 832 831 833 832 class Meta: 834 833 ordering = ["-edited_date"] 834 + 835 + def __str__(self): 836 + return f"PatientNotesEdit {self.patient_note} - {self.edited_by}" 835 837 836 838 837 839 class PatientAgeFunc(Func):
+10 -16
care/facility/models/patient_consultation.py
··· 247 247 ), 248 248 } 249 249 250 - # CSV_DATATYPE_DEFAULT_MAPPING = { 251 - # "encounter_date": (None, models.DateTimeField(),), 252 - # "deprecated_symptoms_onset_date": (None, models.DateTimeField(),), 253 - # "deprecated_symptoms": ("-", models.CharField(),), 254 - # "category": ("-", models.CharField(),), 255 - # "examination_details": ("-", models.CharField(),), 256 - # "suggestion": ("-", models.CharField(),), 257 - # } 258 - 259 250 def __str__(self): 260 251 return f"{self.patient.name}<>{self.facility.name}" 261 252 ··· 271 262 if self.death_datetime and self.patient.death_datetime != self.death_datetime: 272 263 self.patient.death_datetime = self.death_datetime 273 264 self.patient.save(update_fields=["death_datetime"]) 274 - super(PatientConsultation, self).save(*args, **kwargs) 265 + super().save(*args, **kwargs) 275 266 276 267 class Meta: 277 268 constraints = [ ··· 299 290 self.patient.facility 300 291 and request.user in self.patient.facility.users.all() 301 292 ) 302 - or ( 303 - self.assigned_to == request.user 304 - or request.user == self.patient.assigned_to 305 - ) 293 + or (request.user in (self.assigned_to, self.patient.assigned_to)) 306 294 or ( 307 295 request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] 308 296 and ( ··· 351 339 User, 352 340 on_delete=models.PROTECT, 353 341 ) 342 + 343 + def __str__(self): 344 + return f"ConsultationClinician {self.consultation} - {self.clinician}" 354 345 355 346 356 347 class PatientConsent(BaseModel, ConsultationRelatedPermissionMixin): ··· 433 424 and request.user in self.consultation.patient.facility.users.all() 434 425 ) 435 426 or ( 436 - self.consultation.assigned_to == request.user 437 - or request.user == self.consultation.patient.assigned_to 427 + request.user 428 + in ( 429 + self.consultation.assigned_to, 430 + self.consultation.patient.assigned_to, 431 + ) 438 432 ) 439 433 or ( 440 434 request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]
-1
care/facility/models/patient_external_test.py
··· 79 79 "result": "Final Result", 80 80 "sample_collection_date": "Sample Collection Date", 81 81 "source": "Source", 82 - # "result_date": "", 83 82 } 84 83 85 84 def __str__(self):
+12 -43
care/facility/models/patient_icmr.py
··· 1 - import datetime 2 - 3 1 from dateutil.relativedelta import relativedelta 4 2 from django.utils import timezone 3 + from django.utils.timezone import now 5 4 6 5 from care.facility.models import ( 7 6 DISEASE_CHOICES_MAP, ··· 18 17 class Meta: 19 18 proxy = True 20 19 21 - # @property 22 - # def personal_details(self): 23 - # return self 24 - 25 - # @property 26 - # def specimen_details(self): 27 - # instance = self.patientsample_set.last() 28 - # if instance is not None: 29 - # instance.__class__ = PatientSampleICMR 30 - # return instance 31 - 32 - # @property 33 - # def patient_category(self): 34 - # instance = self.consultations.last() 35 - # if instance: 36 - # instance.__class__ = PatientConsultationICMR 37 - # return instance 38 - 39 - # @property 40 - # def exposure_history(self): 41 - # return self 42 - 43 - # @property 44 - # def medical_conditions(self): 45 - # instance = self.patientsample_set.last() 46 - # if instance is not None: 47 - # instance.__class__ = PatientSampleICMR 48 - # return instance 49 - 50 20 def get_age_delta(self): 51 21 start = self.date_of_birth or timezone.datetime(self.year_of_birth, 1, 1).date() 52 22 end = (self.death_datetime or timezone.now()).date() ··· 78 48 79 49 @property 80 50 def has_travel_to_foreign_last_14_days(self): 51 + unsafe_travel_days = 14 81 52 if self.countries_travelled: 82 53 return len(self.countries_travelled) != 0 and ( 83 54 self.date_of_return 84 - and (self.date_of_return.date() - datetime.datetime.now().date()).days 85 - <= 14 55 + and (self.date_of_return.date() - now().date()).days 56 + <= unsafe_travel_days 86 57 ) 58 + return None 87 59 88 60 @property 89 61 def travel_end_date(self): ··· 201 173 def date_of_onset_of_symptoms(self): 202 174 if symptom := self.consultation.symptoms.first(): 203 175 return symptom.onset_date.date() 176 + return None 204 177 205 178 206 179 class PatientConsultationICMR(PatientConsultation): ··· 208 181 proxy = True 209 182 210 183 def is_symptomatic(self): 211 - if ( 212 - SYMPTOM_CHOICES[0][0] not in self.symptoms.choices.keys() 184 + return bool( 185 + SYMPTOM_CHOICES[0][0] not in self.symptoms.choices 213 186 or self.symptoms_onset_date is not None 214 - ): 215 - return True 216 - else: 217 - return False 187 + ) 218 188 219 189 def symptomatic_international_traveller( 220 190 self, 221 191 ): 192 + unsafe_travel_days = 14 222 193 return bool( 223 194 self.patient.countries_travelled 224 195 and len(self.patient.countries_travelled) != 0 225 196 and ( 226 197 self.patient.date_of_return 227 - and ( 228 - self.patient.date_of_return.date() - datetime.datetime.now().date() 229 - ).days 230 - <= 14 198 + and (self.patient.date_of_return.date() - now().date()).days 199 + <= unsafe_travel_days 231 200 ) 232 201 and self.is_symptomatic() 233 202 )
+3 -1
care/facility/models/shifting.py
··· 41 41 (40, "SEVERE"), 42 42 ] 43 43 44 - REVERSE_SHIFTING_STATUS_CHOICES = reverse_choices(SHIFTING_STATUS_CHOICES) 44 + REVERSE_SHIFTING_STATUS_CHOICES: dict[int, str] = reverse_choices( 45 + SHIFTING_STATUS_CHOICES 46 + ) 45 47 46 48 47 49 class ShiftingRequest(FacilityBaseModel):
+9
care/facility/models/summary.py
··· 44 44 models.Index(fields=["-created_date", "s_type"]), 45 45 ] 46 46 47 + def __str__(self): 48 + return f"FacilityRelatedSummary - {self.facility} - {self.s_type}" 49 + 47 50 48 51 DISTRICT_SUMMARY_CHOICES = (("PatientSummary", "PatientSummary"),) 49 52 ··· 78 81 models.Index(fields=["-created_date", "s_type"]), 79 82 ] 80 83 84 + def __str__(self): 85 + return f"DistrictScopedSummary - {self.district} - {self.s_type}" 86 + 81 87 82 88 LSG_SUMMARY_CHOICES = (("PatientSummary", "PatientSummary"),) 83 89 ··· 109 115 ), 110 116 models.Index(fields=["-created_date", "s_type"]), 111 117 ] 118 + 119 + def __str__(self): 120 + return f"LocalBodyScopedSummary - {self.lsg} - {self.s_type}"
care/facility/models/tests/__init__.py

This is a binary file and will not be displayed.

+1 -1
care/facility/signals/asset_updates.py
··· 17 17 return 18 18 19 19 if instance.pk: 20 - instance._previous_values = { 20 + instance._previous_values = { # noqa: SLF001 21 21 "hostname": instance.resolved_middleware.get("hostname"), 22 22 } 23 23
+6 -2
care/facility/static_data/icd11.py
··· 1 + import logging 1 2 import re 2 3 from typing import TypedDict 3 4 ··· 6 7 7 8 from care.facility.models.icd11_diagnosis import ICD11Diagnosis 8 9 from care.utils.static_data.models.base import BaseRedisModel 10 + 11 + logger = logging.getLogger(__name__) 12 + 9 13 10 14 DISEASE_CODE_PATTERN = r"^(?:[A-Z]+\d|\d+[A-Z])[A-Z\d.]*\s" 11 15 ··· 33 37 34 38 35 39 def load_icd11_diagnosis(): 36 - print("Loading ICD11 Diagnosis into the redis cache...", end="", flush=True) 40 + logger.info("Loading ICD11 Diagnosis into the redis cache...") 37 41 38 42 icd_objs = ICD11Diagnosis.objects.order_by("id").values_list( 39 43 "id", "label", "meta_chapter_short" ··· 49 53 vec=diagnosis[1].replace(".", "\\.", 1), 50 54 ).save() 51 55 Migrator().run() 52 - print("Done") 56 + logger.info("ICD11 Diagnosis Loaded") 53 57 54 58 55 59 def get_icd11_diagnosis_object_by_id(
+5 -2
care/facility/static_data/medibase.py
··· 1 + import logging 1 2 from typing import TypedDict 2 3 3 4 from django.core.paginator import Paginator ··· 7 8 8 9 from care.facility.models.prescription import MedibaseMedicine as MedibaseMedicineModel 9 10 from care.utils.static_data.models.base import BaseRedisModel 11 + 12 + logger = logging.getLogger(__name__) 10 13 11 14 12 15 class MedibaseMedicineObject(TypedDict): ··· 46 49 47 50 48 51 def load_medibase_medicines(): 49 - print("Loading Medibase Medicines into the redis cache...", end="", flush=True) 52 + logger.info("Loading Medibase Medicines into the redis cache...") 50 53 51 54 medibase_objects = ( 52 55 MedibaseMedicineModel.objects.order_by("external_id") ··· 87 90 vec=f"{medicine[1]} {medicine[3]} {medicine[4]}", 88 91 ).save() 89 92 Migrator().run() 90 - print("Done") 93 + logger.info("Medibase Medicines Loaded")
+10 -10
care/facility/tasks/__init__.py
··· 8 8 from care.facility.tasks.plausible_stats import capture_goals 9 9 from care.facility.tasks.redis_index import load_redis_index 10 10 from care.facility.tasks.summarisation import ( 11 - summarise_district_patient, 12 - summarise_facility_capacity, 13 - summarise_patient, 14 - summarise_tests, 15 - summarise_triage, 11 + summarize_district_patient, 12 + summarize_facility_capacity, 13 + summarize_patient, 14 + summarize_tests, 15 + summarize_triage, 16 16 ) 17 17 18 18 ··· 26 26 if settings.TASK_SUMMARIZE_TRIAGE: 27 27 sender.add_periodic_task( 28 28 crontab(hour="*/4", minute="59"), 29 - summarise_triage.s(), 29 + summarize_triage.s(), 30 30 name="summarise_triage", 31 31 ) 32 32 if settings.TASK_SUMMARIZE_TESTS: 33 33 sender.add_periodic_task( 34 34 crontab(hour="23", minute="59"), 35 - summarise_tests.s(), 35 + summarize_tests.s(), 36 36 name="summarise_tests", 37 37 ) 38 38 if settings.TASK_SUMMARIZE_FACILITY_CAPACITY: 39 39 sender.add_periodic_task( 40 40 crontab(minute="*/5"), 41 - summarise_facility_capacity.s(), 41 + summarize_facility_capacity.s(), 42 42 name="summarise_facility_capacity", 43 43 ) 44 44 if settings.TASK_SUMMARIZE_PATIENT: 45 45 sender.add_periodic_task( 46 46 crontab(hour="*/1", minute="59"), 47 - summarise_patient.s(), 47 + summarize_patient.s(), 48 48 name="summarise_patient", 49 49 ) 50 50 if settings.TASK_SUMMARIZE_DISTRICT_PATIENT: 51 51 sender.add_periodic_task( 52 52 crontab(hour="*/1", minute="59"), 53 - summarise_district_patient.s(), 53 + summarize_district_patient.s(), 54 54 name="summarise_district_patient", 55 55 ) 56 56
+9 -7
care/facility/tasks/asset_monitor.py
··· 1 1 import logging 2 2 from datetime import datetime 3 - from typing import Any 3 + from typing import TYPE_CHECKING, Any 4 4 5 5 from celery import shared_task 6 6 from django.contrib.contenttypes.models import ContentType ··· 9 9 10 10 from care.facility.models.asset import Asset, AvailabilityRecord, AvailabilityStatus 11 11 from care.utils.assetintegration.asset_classes import AssetClasses 12 - from care.utils.assetintegration.base import BaseAssetIntegration 12 + 13 + if TYPE_CHECKING: 14 + from care.utils.assetintegration.base import BaseAssetIntegration 13 15 14 16 logger = logging.getLogger(__name__) 15 17 16 18 17 19 @shared_task 18 - def check_asset_status(): 19 - logger.info(f"Checking Asset Status: {timezone.now()}") 20 + def check_asset_status(): # noqa: PLR0912 21 + logger.info("Checking Asset Status: %s", timezone.now()) 20 22 21 23 assets = ( 22 24 Asset.objects.exclude(Q(asset_class=None) | Q(asset_class="")) ··· 50 52 51 53 if not resolved_middleware: 52 54 logger.warning( 53 - f"Asset {asset.external_id} does not have a middleware hostname" 55 + "Asset %s does not have a middleware hostname", asset.external_id 54 56 ) 55 57 continue 56 58 ··· 91 93 else: 92 94 result = asset_class.api_get(asset_class.get_url("devices/status")) 93 95 except Exception as e: 94 - logger.warning(f"Middleware {resolved_middleware} is down", e) 96 + logger.warning("Middleware %s is down: %s", resolved_middleware, e) 95 97 96 98 # If no status is returned, setting default status as down 97 99 if not result or "error" in result: ··· 138 140 timestamp=status_record.get("time", timezone.now()), 139 141 ) 140 142 except Exception as e: 141 - logger.error("Error in Asset Status Check", e) 143 + logger.error("Error in Asset Status Check: %s", e)
+8 -8
care/facility/tasks/discharge_summary.py
··· 12 12 email_discharge_summary, 13 13 generate_and_upload_discharge_summary, 14 14 ) 15 - from care.utils.exceptions import CeleryTaskException 15 + from care.utils.exceptions import CeleryTaskError 16 16 17 17 logger: Logger = get_task_logger(__name__) 18 18 ··· 24 24 """ 25 25 Generate and Upload the Discharge Summary 26 26 """ 27 - logger.info(f"Generating Discharge Summary for {consultation_ext_id}") 27 + logger.info("Generating Discharge Summary for %s", consultation_ext_id) 28 28 try: 29 29 consultation = PatientConsultation.objects.get(external_id=consultation_ext_id) 30 30 except PatientConsultation.DoesNotExist as e: 31 - raise CeleryTaskException( 32 - f"Consultation {consultation_ext_id} does not exist" 33 - ) from e 31 + msg = f"Consultation {consultation_ext_id} does not exist" 32 + raise CeleryTaskError(msg) from e 34 33 35 34 summary_file = generate_and_upload_discharge_summary(consultation) 36 35 if not summary_file: 37 - raise CeleryTaskException("Unable to generate discharge summary") 36 + msg = "Unable to generate discharge summary" 37 + raise CeleryTaskError(msg) 38 38 39 39 return summary_file.id 40 40 ··· 45 45 expires=10 * 60, 46 46 ) 47 47 def email_discharge_summary_task(file_id: int, emails: Iterable[str]): 48 - logger.info(f"Emailing Discharge Summary {file_id} to {emails}") 48 + logger.info("Emailing Discharge Summary %s to %s", file_id, emails) 49 49 try: 50 50 summary = FileUpload.objects.get(id=file_id) 51 51 except FileUpload.DoesNotExist: 52 - logger.error(f"Summary {file_id} does not exist") 52 + logger.error("Summary %s does not exist", file_id) 53 53 return False 54 54 email_discharge_summary(summary, emails) 55 55 return True
+8 -5
care/facility/tasks/location_monitor.py
··· 18 18 @shared_task 19 19 def check_location_status(): 20 20 location_content_type = ContentType.objects.get_for_model(AssetLocation) 21 - logger.info(f"Checking Location Status: {timezone.now()}") 21 + logger.info("Checking Location Status: %s", timezone.now()) 22 22 locations = AssetLocation.objects.all() 23 23 24 24 for location in locations: ··· 30 30 31 31 if not resolved_middleware: 32 32 logger.warning( 33 - f"No middleware hostname resolved for location {location.external_id}" 33 + "No middleware hostname resolved for location %s", 34 + location.external_id, 34 35 ) 35 36 continue 36 37 ··· 54 55 new_status = AvailabilityStatus.OPERATIONAL 55 56 56 57 except Exception as e: 57 - logger.warning(f"Middleware {resolved_middleware} is down", e) 58 + logger.warning("Middleware %s is down: %s", resolved_middleware, e) 58 59 59 60 # Fetching the last record of the location 60 61 last_record = ( ··· 74 75 status=new_status.value, 75 76 timestamp=timezone.now(), 76 77 ) 77 - logger.info(f"Location {location.external_id} status: {new_status.value}") 78 + logger.info( 79 + "Location %s status: %s", location.external_id, new_status.value 80 + ) 78 81 except Exception as e: 79 - logger.error("Error in Location Status Check", e) 82 + logger.error("Error in Location Status Check: %s", e)
+8 -4
care/facility/tasks/plausible_stats.py
··· 99 99 return 100 100 today = now().date() 101 101 yesterday = today - timedelta(days=1) 102 - logger.info(f"Capturing Goals for {yesterday}") 102 + logger.info("Capturing Goals for %s", yesterday) 103 103 104 104 for goal in Goals: 105 105 try: ··· 121 121 goal_entry_object.events = goal_data["results"]["events"]["value"] 122 122 goal_entry_object.save() 123 123 124 - logger.info(f"Saved goal entry for {goal_name} on {yesterday}") 124 + logger.info("Saved goal entry for %s on %s", goal_name, yesterday) 125 125 126 126 for property_name in goal.value: 127 127 goal_property_stats = get_goal_event_stats( ··· 145 145 property_entry_object.events = property_statistic["events"] 146 146 property_entry_object.save() 147 147 logger.info( 148 - f"Saved goal property entry for {goal_name} and property {property_name} on {yesterday}" 148 + "Saved goal property entry for %s and property %s on %s", 149 + goal_name, 150 + property_name, 151 + yesterday, 149 152 ) 153 + 150 154 except Exception as e: 151 - logger.error(f"Failed to process goal {goal_name} due to error: {e!s}") 155 + logger.error("Failed to process goal %s due to error: %s", goal_name, e)
+6 -6
care/facility/tasks/push_asset_config.py
··· 32 32 ) 33 33 response.raise_for_status() 34 34 response_json = response.json() 35 - logger.info(f"Pushed Asset Configuration to Middleware: {response_json}") 35 + logger.info("Pushed Asset Configuration to Middleware: %s", response_json) 36 36 return response_json 37 37 except Exception as e: 38 - logger.error(f"Error Pushing Asset Configuration to Middleware: {e}") 38 + logger.error("Error Pushing Asset Configuration to Middleware: %s", e) 39 39 return {"error": str(e)} 40 40 41 41 ··· 48 48 ) 49 49 response.raise_for_status() 50 50 response_json = response.json() 51 - logger.info(f"Deleted Asset from Middleware: {response_json}") 51 + logger.info("Deleted Asset from Middleware: %s", response_json) 52 52 return response_json 53 53 except Exception as e: 54 - logger.error(f"Error Deleting Asset from Middleware: {e}") 54 + logger.error("Error Deleting Asset from Middleware: %s", e) 55 55 return {"error": str(e)} 56 56 57 57 ··· 68 68 ) 69 69 response.raise_for_status() 70 70 response_json = response.json() 71 - logger.info(f"Updated Asset Configuration on Middleware: {response_json}") 71 + logger.info("Updated Asset Configuration on Middleware: %s", response_json) 72 72 return response_json 73 73 except Exception as e: 74 - logger.error(f"Error Updating Asset Configuration on Middleware: {e}") 74 + logger.error("Error Updating Asset Configuration on Middleware: %s", e) 75 75 return {"error": str(e)} 76 76 77 77
+3 -3
care/facility/tasks/redis_index.py
··· 19 19 logger.info("Redis Index already loading, skipping") 20 20 return 21 21 22 - cache.set("redis_index_loading", True, timeout=60 * 2) 22 + cache.set("redis_index_loading", value=True, timeout=60 * 2) 23 23 logger.info("Loading Redis Index") 24 24 if index_exists(): 25 25 logger.info("Index already exists, skipping") ··· 37 37 if load_static_data: 38 38 load_static_data() 39 39 except ModuleNotFoundError: 40 - logger.info(f"Module {module_path} not found") 40 + logger.info("Module %s not found", module_path) 41 41 except Exception as e: 42 - logger.info(f"Error loading static data for {plug.name}: {e}") 42 + logger.info("Error loading static data for %s: %s", plug.name, e) 43 43 44 44 cache.delete("redis_index_loading") 45 45 logger.info("Redis Index Loaded")
+18 -15
care/facility/tasks/summarisation.py
··· 1 1 from celery import shared_task 2 + from celery.utils.log import get_task_logger 2 3 3 - from care.facility.utils.summarisation.district.patient_summary import ( 4 + from care.facility.utils.summarization.district.patient_summary import ( 4 5 district_patient_summary, 5 6 ) 6 - from care.facility.utils.summarisation.facility_capacity import ( 7 + from care.facility.utils.summarization.facility_capacity import ( 7 8 facility_capacity_summary, 8 9 ) 9 - from care.facility.utils.summarisation.patient_summary import patient_summary 10 - from care.facility.utils.summarisation.tests_summary import tests_summary 11 - from care.facility.utils.summarisation.triage_summary import triage_summary 10 + from care.facility.utils.summarization.patient_summary import patient_summary 11 + from care.facility.utils.summarization.tests_summary import tests_summary 12 + from care.facility.utils.summarization.triage_summary import triage_summary 13 + 14 + logger = get_task_logger(__name__) 12 15 13 16 14 17 @shared_task 15 - def summarise_triage(): 18 + def summarize_triage(): 16 19 triage_summary() 17 - print("Summarised Triages") 20 + logger.info("Summarized Triages") 18 21 19 22 20 23 @shared_task 21 - def summarise_tests(): 24 + def summarize_tests(): 22 25 tests_summary() 23 - print("Summarised Tests") 26 + logger.info("Summarized Tests") 24 27 25 28 26 29 @shared_task 27 - def summarise_facility_capacity(): 30 + def summarize_facility_capacity(): 28 31 facility_capacity_summary() 29 - print("Summarised Facility Capacities") 32 + logger.info("Summarized Facility Capacities") 30 33 31 34 32 35 @shared_task 33 - def summarise_patient(): 36 + def summarize_patient(): 34 37 patient_summary() 35 - print("Summarised Patients") 38 + logger.info("Summarized Patients") 36 39 37 40 38 41 @shared_task 39 - def summarise_district_patient(): 42 + def summarize_district_patient(): 40 43 district_patient_summary() 41 - print("Summarised District Patients") 44 + logger.info("Summarized District Patients")
care/facility/templatetags/__init__.py

This is a binary file and will not be displayed.

+2 -2
care/facility/templatetags/data_formatting_tags.py
··· 5 5 6 6 @register.filter(name="format_empty_data") 7 7 def format_empty_data(data): 8 - if data is None or data == "" or data == 0.0 or data == []: 8 + if data is None or data in ("", 0.0, []): 9 9 return "N/A" 10 10 11 11 return data ··· 28 28 converted_items = [convert_to_sentence_case(item) for item in items] 29 29 return ", ".join(converted_items) 30 30 31 - elif isinstance(data, (list, tuple)): 31 + if isinstance(data, list | tuple): 32 32 converted_items = [convert_to_sentence_case(item) for item in data] 33 33 return ", ".join(converted_items) 34 34
+2 -1
care/facility/templatetags/filters.py
··· 24 24 def field_name_to_label(value): 25 25 if value: 26 26 return value.replace("_", " ").capitalize() 27 + return None 27 28 28 29 29 30 @register.filter(expects_localtime=True) 30 31 def parse_datetime(value): 31 32 try: 32 - return datetime.strptime(value, "%Y-%m-%dT%H:%M") 33 + return datetime.strptime(value, "%Y-%m-%dT%H:%M") # noqa: DTZ007 33 34 except ValueError: 34 35 return None
+1 -2
care/facility/templatetags/prescription_tags.py
··· 9 9 return f"{prescription.medicine_name}, titration from {prescription.base_dosage} to {prescription.target_dosage}, {prescription.route}, {prescription.frequency} for {prescription.days} days." 10 10 if prescription.dosage_type == "PRN": 11 11 return f"{prescription.medicine_name}, {prescription.base_dosage}, {prescription.route}" 12 - else: 13 - return f"{prescription.medicine_name}, {prescription.base_dosage}, {prescription.route}, {prescription.frequency} for {prescription.days} days." 12 + return f"{prescription.medicine_name}, {prescription.base_dosage}, {prescription.route}, {prescription.frequency} for {prescription.days} days."
+3 -3
care/facility/tests/test_asset_public_api.py
··· 3 3 from rest_framework.test import APITestCase 4 4 5 5 from care.facility.api.serializers.asset import AssetSerializer 6 - from care.utils.tests.test_utils import TestUtils, override_cache 6 + from care.utils.tests.test_utils import OverrideCache, TestUtils 7 7 8 8 9 9 class AssetPublicViewSetTestCase(TestUtils, APITestCase): ··· 38 38 self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) 39 39 40 40 def test_retrieve_asset_qr_cached(self): 41 - with override_cache(self): 41 + with OverrideCache(self): 42 42 response = self.client.get( 43 43 f"/api/v1/public/asset_qr/{self.asset.qr_code_id}/" 44 44 ) ··· 62 62 self.assertEqual(response.data["name"], updated_data["name"]) 63 63 64 64 def test_retrieve_asset_qr_pre_cached(self): 65 - with override_cache(self): 65 + with OverrideCache(self): 66 66 serializer = AssetSerializer(self.asset) 67 67 cache.set(f"asset:qr:{self.asset.qr_code_id}", serializer.data) 68 68 response = self.client.get(
+3 -3
care/facility/tests/test_asset_service_history_api.py
··· 1 - from datetime import datetime, timedelta 1 + from datetime import timedelta 2 2 3 3 from django.utils.timezone import now 4 4 from rest_framework import status ··· 20 20 cls.asset_location = cls.create_asset_location(cls.facility) 21 21 cls.asset = cls.create_asset(cls.asset_location) 22 22 cls.user = cls.create_user("staff", cls.district, home_facility=cls.facility) 23 - cls.today = datetime.today().strftime("%Y-%m-%d") 24 - cls.yesterday = (datetime.today() - timedelta(days=1)).strftime("%Y-%m-%d") 23 + cls.today = now().strftime("%Y-%m-%d") 24 + cls.yesterday = (now() - timedelta(days=1)).strftime("%Y-%m-%d") 25 25 cls.asset_service = AssetService.objects.create( 26 26 asset=cls.asset, 27 27 serviced_on=cls.today,
+3 -9
care/facility/tests/test_medicine_administrations_api.py
··· 123 123 f"/api/v1/consultation/{prescription.consultation.external_id}/prescription_administration/" 124 124 ) 125 125 self.assertEqual(res.status_code, status.HTTP_200_OK) 126 - self.assertTrue( 127 - any([administration_id == x["id"] for x in res.data["results"]]) 128 - ) 126 + self.assertTrue(any(administration_id == x["id"] for x in res.data["results"])) 129 127 130 128 # test archived list administrations 131 129 res = self.client.get( 132 130 f"/api/v1/consultation/{prescription.consultation.external_id}/prescription_administration/?archived=true" 133 131 ) 134 132 self.assertEqual(res.status_code, status.HTTP_200_OK) 135 - self.assertTrue( 136 - any([administration_id == x["id"] for x in res.data["results"]]) 137 - ) 133 + self.assertTrue(any(administration_id == x["id"] for x in res.data["results"])) 138 134 139 135 # test archived list administrations 140 136 res = self.client.get( 141 137 f"/api/v1/consultation/{prescription.consultation.external_id}/prescription_administration/?archived=false" 142 138 ) 143 139 self.assertEqual(res.status_code, status.HTTP_200_OK) 144 - self.assertFalse( 145 - any([administration_id == x["id"] for x in res.data["results"]]) 146 - ) 140 + self.assertFalse(any(administration_id == x["id"] for x in res.data["results"])) 147 141 148 142 def test_administer_in_future(self): 149 143 prescription = self.normal_prescription
+2 -2
care/facility/tests/test_middleware_auth.py
··· 6 6 from rest_framework.test import APITestCase 7 7 8 8 from care.utils.jwks.token_generator import generate_jwt 9 - from care.utils.tests.test_utils import TestUtils, override_cache 9 + from care.utils.tests.test_utils import OverrideCache, TestUtils 10 10 11 11 12 12 class MiddlewareAuthTestCase(TestUtils, APITestCase): ··· 80 80 response.data["username"], "middleware" + str(self.facility.external_id) 81 81 ) 82 82 83 - @override_cache 83 + @OverrideCache 84 84 @requests_mock.Mocker() 85 85 def test_middleware_authentication_cached_successful(self, mock_get_public_key): 86 86 mock_get_public_key.get(
+2 -2
care/facility/tests/test_patient_and_consultation_access.py
··· 105 105 self.assertEqual(str(patients_order[i].external_id), response[i]["id"]) 106 106 107 107 # order by modified date 108 - patients_order = patients_order[::-1] 108 + patients_order.reverse() 109 109 response = self.client.get( 110 110 f"/api/v1/facility/{self.home_facility.external_id}/discharged_patients/?ordering=modified_date", 111 111 ) ··· 113 113 for i in range(len(response)): 114 114 self.assertEqual(str(patients_order[i].external_id), response[i]["id"]) 115 115 116 - def test_patient_consultation_access(self): 116 + def test_patient_consultation_access(self): # noqa: PLR0915 117 117 # In this test, a patient is admitted to a remote facility and then later admitted to a home facility. 118 118 119 119 # Admit patient to the remote facility
+23 -23
care/facility/tests/test_patient_api.py
··· 144 144 145 145 def test_patient_notes(self): 146 146 self.client.force_authenticate(user=self.state_admin) 147 - patientId = self.patient.external_id 147 + patient_id = self.patient.external_id 148 148 response = self.client.get( 149 - f"/api/v1/patient/{patientId}/notes/", 149 + f"/api/v1/patient/{patient_id}/notes/", 150 150 { 151 151 "consultation": self.consultation.external_id, 152 152 "thread": PatientNoteThreadChoices.DOCTORS, ··· 160 160 # Test if all notes are from same consultation as requested 161 161 self.assertEqual( 162 162 str(self.consultation.external_id), 163 - [note["consultation"] for note in results][0], 163 + next(note["consultation"] for note in results), 164 164 ) 165 165 166 166 # Test created_by_local_user field if user is not from same facility as patient ··· 278 278 self.assertEqual(reply_response.status_code, status.HTTP_400_BAD_REQUEST) 279 279 280 280 def test_patient_note_edit(self): 281 - patientId = self.patient.external_id 281 + patient_id = self.patient.external_id 282 282 notes_list_response = self.client.get( 283 - f"/api/v1/patient/{patientId}/notes/?consultation={self.consultation.external_id}" 283 + f"/api/v1/patient/{patient_id}/notes/?consultation={self.consultation.external_id}" 284 284 ) 285 285 note_data = notes_list_response.json()["results"][0] 286 286 response = self.client.get( 287 - f"/api/v1/patient/{patientId}/notes/{note_data['id']}/edits/" 287 + f"/api/v1/patient/{patient_id}/notes/{note_data['id']}/edits/" 288 288 ) 289 289 290 290 data = response.json()["results"] ··· 296 296 # Test with a different user editing the note than the one who created it 297 297 self.client.force_authenticate(user=self.state_admin) 298 298 response = self.client.put( 299 - f"/api/v1/patient/{patientId}/notes/{note_data['id']}/", 299 + f"/api/v1/patient/{patient_id}/notes/{note_data['id']}/", 300 300 {"note": new_note_content}, 301 301 ) 302 302 self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) ··· 307 307 # Test with the same user editing the note 308 308 self.client.force_authenticate(user=self.user2) 309 309 response = self.client.put( 310 - f"/api/v1/patient/{patientId}/notes/{note_data['id']}/", 310 + f"/api/v1/patient/{patient_id}/notes/{note_data['id']}/", 311 311 {"note": new_note_content}, 312 312 ) 313 313 ··· 318 318 319 319 # Ensure the original note is still present in the edits 320 320 response = self.client.get( 321 - f"/api/v1/patient/{patientId}/notes/{note_data['id']}/edits/" 321 + f"/api/v1/patient/{patient_id}/notes/{note_data['id']}/edits/" 322 322 ) 323 323 324 324 data = response.json()["results"] ··· 402 402 response = self.client.get(self.get_base_url()) 403 403 self.assertEqual(response.status_code, status.HTTP_200_OK) 404 404 self.assertEqual(response.data["count"], 2) 405 - patient_1_response = [ 405 + patient_1_response = next( 406 406 x 407 407 for x in response.data["results"] 408 408 if x["id"] == str(self.patient.external_id) 409 - ][0] 410 - patient_2_response = [ 409 + ) 410 + patient_2_response = next( 411 411 x 412 412 for x in response.data["results"] 413 413 if x["id"] == str(self.patient_2.external_id) 414 - ][0] 414 + ) 415 415 self.assertEqual( 416 416 patient_1_response["last_consultation"]["has_consents"], 417 417 [ConsentType.CONSENT_FOR_ADMISSION], ··· 424 424 self.client.force_authenticate(user=self.user) 425 425 response = self.client.get(self.get_base_url()) 426 426 self.assertEqual(response.status_code, status.HTTP_200_OK) 427 - patient_1_response = [ 427 + patient_1_response = next( 428 428 x 429 429 for x in response.data["results"] 430 430 if x["id"] == str(self.patient.external_id) 431 - ][0] 431 + ) 432 432 self.assertEqual( 433 433 patient_1_response["last_consultation"]["has_consents"], 434 434 [ConsentType.CONSENT_FOR_ADMISSION], ··· 452 452 response = self.client.get(self.get_base_url()) 453 453 self.assertEqual(response.status_code, status.HTTP_200_OK) 454 454 self.assertEqual(response.data["count"], 2) 455 - patient_1_response = [ 455 + patient_1_response = next( 456 456 x 457 457 for x in response.data["results"] 458 458 if x["id"] == str(self.patient.external_id) 459 - ][0] 460 - patient_2_response = [ 459 + ) 460 + patient_2_response = next( 461 461 x 462 462 for x in response.data["results"] 463 463 if x["id"] == str(self.patient_2.external_id) 464 - ][0] 464 + ) 465 465 self.assertEqual( 466 466 patient_1_response["last_consultation"]["has_consents"], 467 467 [ConsentType.CONSENT_FOR_ADMISSION], ··· 477 477 response = self.client.get(self.get_base_url()) 478 478 self.assertEqual(response.status_code, status.HTTP_200_OK) 479 479 self.assertEqual(response.data["count"], 2) 480 - patient_1_response = [ 480 + patient_1_response = next( 481 481 x 482 482 for x in response.data["results"] 483 483 if x["id"] == str(self.patient.external_id) 484 - ][0] 485 - patient_2_response = [ 484 + ) 485 + patient_2_response = next( 486 486 x 487 487 for x in response.data["results"] 488 488 if x["id"] == str(self.patient_2.external_id) 489 - ][0] 489 + ) 490 490 self.assertEqual( 491 491 patient_1_response["last_consultation"]["has_consents"], 492 492 [ConsentType.CONSENT_FOR_ADMISSION],
+1 -1
care/facility/tests/test_patient_consultation_api.py
··· 64 64 "onset_date": now(), 65 65 }, 66 66 ], 67 - "patient_no": datetime.datetime.now().timestamp(), 67 + "patient_no": now().timestamp(), 68 68 } 69 69 70 70 def get_url(self, consultation=None):
+1 -7
care/facility/tests/test_patient_daily_rounds_api.py
··· 1 - import datetime 2 1 from datetime import timedelta 3 2 4 3 from django.utils import timezone ··· 51 50 "rounds_type": "NORMAL", 52 51 "patient_category": "Comfort", 53 52 "action": "DISCHARGE_RECOMMENDED", 54 - "taken_at": datetime.datetime.now().isoformat(), 53 + "taken_at": timezone.now().isoformat(), 55 54 } 56 55 57 56 def get_url(self, external_consultation_id=None): ··· 243 242 "region": "", 244 243 "length": 1, 245 244 "width": 1, 246 - # "exudate_amount": "None", 247 - # "tissue_type": "Closed", 248 - # "push_score": 1, 249 - # "scale": 1, 250 - # "description": "Description", 251 245 } 252 246 ], 253 247 )
+45 -69
care/facility/tests/test_pdf_generation.py
··· 1 - import os 1 + import hashlib 2 2 import subprocess 3 3 import tempfile 4 4 from datetime import date ··· 21 21 from care.utils.tests.test_utils import TestUtils 22 22 23 23 24 - def compare_pngs(png_path1, png_path2): 25 - with Image.open(png_path1) as img1, Image.open(png_path2) as img2: 26 - if img1.mode != img2.mode: 24 + def compare_images(image1_path: Path, image2_path: Path) -> bool: 25 + with Image.open(image1_path) as img1, Image.open(image2_path) as img2: 26 + if img1.mode != img2.mode or img1.size != img2.size: 27 27 return False 28 28 29 - if img1.size != img2.size: 30 - return False 29 + img1_hash = hashlib.sha256(img1.tobytes()).hexdigest() 30 + img2_hash = hashlib.sha256(img2.tobytes()).hexdigest() 31 31 32 - img1_data = list(img1.getdata()) 33 - img2_data = list(img2.getdata()) 32 + return img1_hash == img2_hash 34 33 35 - if img1_data == img2_data: 36 - return True 37 - else: 38 - return False 39 34 35 + def test_compile_typ(data) -> bool: 36 + logo_path = ( 37 + Path(settings.BASE_DIR) / "staticfiles" / "images" / "logos" / "black-logo.svg" 38 + ) 39 + data["logo_path"] = str(logo_path) 40 + content = render_to_string( 41 + "reports/patient_discharge_summary_pdf_template.typ", context=data 42 + ) 40 43 41 - def test_compile_typ(data): 42 - sample_file_path = os.path.join( 43 - os.getcwd(), "care", "facility", "tests", "sample_reports", "sample{n}.png" 44 + sample_files_dir: Path = ( 45 + settings.BASE_DIR / "care" / "facility" / "tests" / "sample_reports" 44 46 ) 45 - test_output_file_path = os.path.join( 46 - os.getcwd(), "care", "facility", "tests", "sample_reports", "test_output{n}.png" 47 + 48 + subprocess.run( # noqa: S603 49 + [ # noqa: S607 50 + "typst", 51 + "compile", 52 + "-", 53 + sample_files_dir / "test_output{n}.png", 54 + "--format", 55 + "png", 56 + ], 57 + input=content.encode("utf-8"), 58 + capture_output=True, 59 + check=True, 60 + cwd="/", 47 61 ) 48 - try: 49 - logo_path = ( 50 - Path(settings.BASE_DIR) 51 - / "staticfiles" 52 - / "images" 53 - / "logos" 54 - / "black-logo.svg" 55 - ) 56 - data["logo_path"] = str(logo_path) 57 - content = render_to_string( 58 - "reports/patient_discharge_summary_pdf_template.typ", context=data 59 - ) 60 - subprocess.run( 61 - ["typst", "compile", "-", test_output_file_path, "--format", "png"], 62 - input=content.encode("utf-8"), 63 - capture_output=True, 64 - check=True, 65 - cwd="/", 66 - ) 67 62 68 - number_of_pngs_generated = 2 69 - # To be updated only if the number of sample png increase in future 63 + sample_files = sorted(sample_files_dir.glob("sample*.png")) 64 + test_generated_files = sorted(sample_files_dir.glob("test_output*.png")) 70 65 71 - for i in range(1, number_of_pngs_generated + 1): 72 - current_sample_file_path = sample_file_path 73 - current_sample_file_path = str(current_sample_file_path).replace( 74 - "{n}", str(i) 75 - ) 66 + result = all( 67 + compare_images(sample_image, test_output_image) 68 + for sample_image, test_output_image in zip( 69 + sample_files, test_generated_files, strict=True 70 + ) 71 + ) 76 72 77 - current_test_output_file_path = test_output_file_path 78 - current_test_output_file_path = str(current_test_output_file_path).replace( 79 - "{n}", str(i) 80 - ) 73 + for file in test_generated_files: 74 + file.unlink() 81 75 82 - if not compare_pngs( 83 - Path(current_sample_file_path), Path(current_test_output_file_path) 84 - ): 85 - return False 86 - return True 87 - except Exception: 88 - return False 89 - finally: 90 - count = 1 91 - while True: 92 - current_test_output_file_path = test_output_file_path 93 - current_test_output_file_path = current_test_output_file_path.replace( 94 - "{n}", str(count) 95 - ) 96 - if Path(current_test_output_file_path).exists(): 97 - os.remove(Path(current_test_output_file_path)) 98 - else: 99 - break 100 - count += 1 76 + return result 101 77 102 78 103 79 class TestTypstInstallation(TestCase): 104 80 def test_typst_installed(self): 105 81 try: 106 - subprocess.run(["typst", "--version"], check=True, capture_output=True) 82 + subprocess.run(["typst", "--version"], check=True, capture_output=True) # noqa: S603, S607 107 83 typst_installed = True 108 84 except subprocess.CalledProcessError: 109 85 typst_installed = False ··· 218 194 with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as file: 219 195 compile_typ(file.name, test_data) 220 196 221 - self.assertTrue(os.path.exists(file.name)) 222 - self.assertGreater(os.path.getsize(file.name), 0) 197 + self.assertTrue(Path(file.name).exists()) 198 + self.assertGreater(Path(file.name).stat().st_size, 0) 223 199 224 200 def test_pdf_generation(self): 225 201 data = discharge_summary.get_discharge_summary_data(self.consultation)
care/facility/utils/icd/__init__.py

This is a binary file and will not be displayed.

+27 -15
care/facility/utils/icd/scraper.py
··· 1 1 import json 2 + import logging 2 3 import time 4 + from typing import TYPE_CHECKING 3 5 4 6 import requests 5 7 from django.conf import settings 6 8 9 + if TYPE_CHECKING: 10 + from pathlib import Path 11 + 12 + 13 + logger = logging.getLogger(__name__) 14 + 7 15 8 16 class ICDScraper: 9 17 def __init__(self): ··· 11 19 self.child_concept_url = settings.ICD_SCRAPER_CHILD_CONCEPTS_URL 12 20 self.scraped_concepts = [] 13 21 self.scraped_concept_dict = {} 22 + self.request_timeout = 10 14 23 15 - def add_query(self, url, query={}): 16 - return ( 17 - url 18 - + "?" 19 - + "&".join(map(lambda k: str(k) + "=" + str(query[k]), query.keys())) 20 - ) 24 + def add_query(self, url, query=None): 25 + if query is None: 26 + query = {} 27 + return url + "?" + "&".join(str(k) + "=" + str(query[k]) for k in query) 21 28 22 29 def get_child_concepts(self, p_concept, p_parent_id): 23 30 if p_concept["ID"] in self.scraped_concept_dict: 24 - print(f"[-] Skipped duplicate, {p_concept['label']}") 31 + logger.info("[-] Skipped duplicate, %s", p_concept["label"]) 25 32 return 26 33 27 34 self.scraped_concepts.append({**p_concept, "parentId": p_parent_id}) 28 35 self.scraped_concept_dict[p_concept["ID"]] = True 29 36 30 - print(f"[+] Added {p_concept['label']}") 37 + logger.info("[+] Added %s", p_concept["label"]) 31 38 32 39 if p_concept["isLeaf"]: 33 40 return ··· 41 48 "useHtml": "false", 42 49 "ConceptId": p_concept["ID"], 43 50 }, 44 - ) 51 + ), 52 + timeout=self.request_timeout, 45 53 ).json() 46 54 except Exception as e: 47 - print("[x] Error encountered: ", e) 48 - with open("error.txt", "a") as error_file: 49 - error_file.write(f"{p_concept['label']}\n") 55 + logger.info("[x] Error encountered: %s", e) 56 + error_file: Path = settings.BASE_DIR / "error.txt" 57 + with error_file.open("a") as ef: 58 + ef.write(f"{p_concept['label']}\n") 50 59 51 60 time.sleep(10) 52 61 concepts = requests.get( ··· 56 65 "useHtml": "false", 57 66 "ConceptId": p_concept["ID"], 58 67 }, 59 - ) 68 + ), 69 + timeout=self.request_timeout, 60 70 ).json() 61 71 62 72 for concept in concepts: ··· 66 76 self.scraped_concepts = [] 67 77 self.scraped_concept_dict = {} 68 78 root_concepts = requests.get( 69 - self.add_query(self.root_concept_url, {"useHtml": "false"}) 79 + self.add_query(self.root_concept_url, {"useHtml": "false"}), 80 + timeout=self.request_timeout, 70 81 ).json() 71 82 72 83 skip = [ ··· 81 92 self.get_child_concepts(root_concept, None) 82 93 time.sleep(3) 83 94 84 - with open("data.json", "w") as json_file: 95 + data_file: Path = settings.BASE_DIR / "data" / "icd11.json" 96 + with data_file.open("w") as json_file: 85 97 json.dump(self.scraped_concepts, json_file)
care/facility/utils/reports/__init__.py

This is a binary file and will not be displayed.

+15 -10
care/facility/utils/reports/discharge_summary.py
··· 112 112 113 113 114 114 def get_discharge_summary_data(consultation: PatientConsultation): 115 - logger.info(f"fetching discharge summary data for {consultation.external_id}") 115 + logger.info("fetching discharge summary data for %s", consultation.external_id) 116 116 samples = PatientSample.objects.filter( 117 117 patient=consultation.patient, consultation=consultation 118 118 ) ··· 212 212 "reports/patient_discharge_summary_pdf_template.typ", context=data 213 213 ) 214 214 215 - subprocess.run( 216 - [ 215 + subprocess.run( # noqa: S603 216 + [ # noqa: S607 217 217 "typst", 218 218 "compile", 219 219 "-", ··· 226 226 ) 227 227 228 228 logging.info( 229 - f"Successfully Compiled Summary pdf for {data['consultation'].external_id}" 229 + "Successfully Compiled Summary pdf for %s", data["consultation"].external_id 230 230 ) 231 231 return True 232 232 233 233 except subprocess.CalledProcessError as e: 234 234 logging.error( 235 - f"Error compiling summary pdf for {data['consultation'].external_id}: {e.stderr.decode('utf-8')}" 235 + "Error compiling summary pdf for %s: %s", 236 + data["consultation"].external_id, 237 + e.stderr.decode("utf-8"), 236 238 ) 237 239 return False 238 240 239 241 240 242 def generate_discharge_summary_pdf(data, file): 241 243 logger.info( 242 - f"Generating Discharge Summary pdf for {data['consultation'].external_id}" 244 + "Generating Discharge Summary pdf for %s", data["consultation"].external_id 243 245 ) 244 246 compile_typ(output_file=file.name, data=data) 245 247 logger.info( 246 - f"Successfully Generated Discharge Summary pdf for {data['consultation'].external_id}" 248 + "Successfully Generated Discharge Summary pdf for %s", 249 + data["consultation"].external_id, 247 250 ) 248 251 249 252 250 253 def generate_and_upload_discharge_summary(consultation: PatientConsultation): 251 - logger.info(f"Generating Discharge Summary for {consultation.external_id}") 254 + logger.info("Generating Discharge Summary for %s", consultation.external_id) 252 255 253 256 set_lock(consultation.external_id, 5) 254 257 try: ··· 267 270 set_lock(consultation.external_id, 50) 268 271 with tempfile.NamedTemporaryFile(suffix=".pdf") as file: 269 272 generate_discharge_summary_pdf(data, file) 270 - logger.info(f"Uploading Discharge Summary for {consultation.external_id}") 273 + logger.info("Uploading Discharge Summary for %s", consultation.external_id) 271 274 summary_file.put_object(file, ContentType="application/pdf") 272 275 summary_file.upload_completed = True 273 276 summary_file.save() 274 277 logger.info( 275 - f"Uploaded Discharge Summary for {consultation.external_id}, file id: {summary_file.id}" 278 + "Uploaded Discharge Summary for %s, file id: %s", 279 + consultation.external_id, 280 + summary_file.id, 276 281 ) 277 282 finally: 278 283 clear_lock(consultation.external_id)
+6 -6
care/facility/utils/summarisation/district/patient_summary.py care/facility/utils/summarization/district/patient_summary.py
··· 43 43 home_quarantine = Q(last_consultation__suggestion="HI") 44 44 45 45 total_patients_home_quarantine = patients.filter(home_quarantine).count() 46 - district_summary[local_body_object.id][ 47 - "total_patients_home_quarantine" 48 - ] = total_patients_home_quarantine 46 + district_summary[local_body_object.id]["total_patients_home_quarantine"] = ( 47 + total_patients_home_quarantine 48 + ) 49 49 50 50 # Apply Date Filters 51 51 ··· 69 69 district_summary[local_body_object.id][clean_name] = count 70 70 71 71 # Update Anything Extra 72 - district_summary[local_body_object.id][ 73 - "today_patients_home_quarantine" 74 - ] = today_patients_home_quarantine 72 + district_summary[local_body_object.id]["today_patients_home_quarantine"] = ( 73 + today_patients_home_quarantine 74 + ) 75 75 76 76 object_filter = Q(s_type="PatientSummary") & Q( 77 77 created_date__startswith=now().date()
+3 -15
care/facility/utils/summarisation/facility_capacity.py care/facility/utils/summarization/facility_capacity.py
··· 34 34 patients_in_facility.filter(is_active=True).count() 35 35 ) 36 36 discharge_patients = patients_in_facility.filter(is_active=False) 37 - capacity_summary[facility_obj.id][ 38 - "actual_discharged_patients" 39 - ] = discharge_patients.count() 37 + capacity_summary[facility_obj.id]["actual_discharged_patients"] = ( 38 + discharge_patients.count() 39 + ) 40 40 capacity_summary[facility_obj.id]["availability"] = [] 41 41 42 42 temp_inventory_summary_obj = {} ··· 53 53 created_date__gte=current_date, 54 54 probable_accident=False, 55 55 ) 56 - # start_log = log_query.order_by("created_date").first() 57 56 end_log = log_query.order_by("-created_date").first() 58 - # start_stock = summary_obj.quantity_in_default_unit 59 - # if start_log: 60 - # if start_log.is_incoming: # Add current value to current stock to get correct stock 61 - # start_stock = start_log.current_stock + start_log.quantity_in_default_unit 62 - # else: 63 - # start_stock = start_log.current_stock - start_log.quantity_in_default_unit 64 57 end_stock = summary_obj.quantity 65 58 if end_log: 66 59 end_stock = end_log.current_stock ··· 76 69 ) 77 70 if temp2: 78 71 total_added = temp2.get("quantity_in_default_unit__sum", 0) or 0 79 - 80 - # Calculate Start Stock as 81 - # end_stock = start_stock - consumption + addition 82 - # start_stock = end_stock - addition + consumption 83 - # This way the start stock will never veer off course 84 72 85 73 start_stock = end_stock - total_added + total_consumed 86 74
+6 -6
care/facility/utils/summarisation/patient_summary.py care/facility/utils/summarization/patient_summary.py
··· 37 37 home_quarantine = Q(last_consultation__suggestion="HI") 38 38 39 39 total_patients_home_quarantine = patients.filter(home_quarantine).count() 40 - patient_summary[facility_id][ 41 - "total_patients_home_quarantine" 42 - ] = total_patients_home_quarantine 40 + patient_summary[facility_id]["total_patients_home_quarantine"] = ( 41 + total_patients_home_quarantine 42 + ) 43 43 44 44 # Apply Date Filters 45 45 ··· 63 63 patient_summary[facility_id][clean_name] = count 64 64 65 65 # Update Anything Extra 66 - patient_summary[facility_id][ 67 - "today_patients_home_quarantine" 68 - ] = today_patients_home_quarantine 66 + patient_summary[facility_id]["today_patients_home_quarantine"] = ( 67 + today_patients_home_quarantine 68 + ) 69 69 70 70 for i in list(patient_summary.keys()): 71 71 object_filter = Q(s_type="PatientSummary") & Q(
care/facility/utils/summarisation/tests_summary.py care/facility/utils/summarization/tests_summary.py
care/facility/utils/summarisation/triage_summary.py care/facility/utils/summarization/triage_summary.py
care/facility/utils/summarization/__init__.py

This is a binary file and will not be displayed.

care/facility/utils/summarization/district/__init__.py

This is a binary file and will not be displayed.

+6 -2
care/users/admin.py
··· 53 53 ) 54 54 }, 55 55 ), 56 - ) + auth_admin.UserAdmin.fieldsets 56 + *auth_admin.UserAdmin.fieldsets, 57 + ) 57 58 list_display = ["username", "is_superuser"] 58 59 search_fields = ["first_name", "last_name"] 59 60 ··· 89 90 ) 90 91 91 92 class Meta: 92 - fields = "__all__" 93 + fields = ( 94 + "user", 95 + "flag", 96 + ) 93 97 model = UserFlag 94 98 95 99 form = UserFlagForm
+40 -33
care/users/api/serializers/user.py
··· 18 18 custom_image_extension_validator, 19 19 ) 20 20 from care.utils.queryset.facility import get_home_facility_queryset 21 - from care.utils.serializer.external_id_field import ExternalIdSerializerField 22 - from config.serializers import ChoiceField 21 + from care.utils.serializers.fields import ChoiceField, ExternalIdSerializerField 23 22 24 23 25 24 class SignUpSerializer(serializers.ModelSerializer): ··· 88 87 return validated 89 88 90 89 90 + MIN_USER_AGE = 16 91 + 92 + 91 93 class UserCreateSerializer(SignUpSerializer): 92 94 password = serializers.CharField(required=False) 93 95 facilities = serializers.ListSerializer( ··· 120 122 date_of_birth = serializers.DateField(required=True) 121 123 122 124 def validate_date_of_birth(self, value): 123 - if value and now().year - value.year < 16: 124 - raise serializers.ValidationError("Age must be greater than 15 years") 125 + if value and now().year - value.year < MIN_USER_AGE: 126 + error = "Age must be greater than 15 years" 127 + raise serializers.ValidationError(error) 125 128 126 129 return value 127 130 128 131 def validate_facilities(self, facility_ids): 129 - if facility_ids: 130 - if ( 131 - len(facility_ids) 132 - != Facility.objects.filter(external_id__in=facility_ids).count() 133 - ): 134 - available_facility_ids = Facility.objects.filter( 135 - external_id__in=facility_ids, 136 - ).values_list("external_id", flat=True) 137 - not_found_ids = list(set(facility_ids) - set(available_facility_ids)) 138 - raise serializers.ValidationError( 139 - f"Some facilities are not available - {', '.join([str(_id) for _id in not_found_ids])}", 140 - ) 132 + if ( 133 + facility_ids 134 + and len(facility_ids) 135 + != Facility.objects.filter(external_id__in=facility_ids).count() 136 + ): 137 + available_facility_ids = Facility.objects.filter( 138 + external_id__in=facility_ids, 139 + ).values_list("external_id", flat=True) 140 + not_found_ids = list(set(facility_ids) - set(available_facility_ids)) 141 + error = f"Some facilities are not available - {', '.join([str(_id) for _id in not_found_ids])}" 142 + raise serializers.ValidationError(error) 141 143 return facility_ids 142 144 143 145 def validate_ward(self, value): ··· 148 150 and not self.context["created_by"].user_type 149 151 >= User.TYPE_VALUE_MAP["LocalBodyAdmin"] 150 152 ): 151 - raise serializers.ValidationError("Cannot create for a different Ward") 153 + error = "Cannot create for a different Ward" 154 + raise serializers.ValidationError(error) 152 155 return value 153 156 154 157 def validate_local_body(self, value): ··· 159 162 and not self.context["created_by"].user_type 160 163 >= User.TYPE_VALUE_MAP["DistrictAdmin"] 161 164 ): 162 - raise serializers.ValidationError( 163 - "Cannot create for a different local body", 164 - ) 165 + error = "Cannot create for a different local body" 166 + raise serializers.ValidationError(error) 165 167 return value 166 168 167 169 def validate_district(self, value): ··· 172 174 and not self.context["created_by"].user_type 173 175 >= User.TYPE_VALUE_MAP["StateAdmin"] 174 176 ): 175 - raise serializers.ValidationError("Cannot create for a different district") 177 + error = "Cannot create for a different district" 178 + raise serializers.ValidationError(error) 176 179 return value 177 180 178 181 def validate_state(self, value): ··· 181 184 and value != self.context["created_by"].state 182 185 and not self.context["created_by"].is_superuser 183 186 ): 184 - raise serializers.ValidationError("Cannot create for a different state") 187 + error = "Cannot create for a different state" 188 + raise serializers.ValidationError(error) 185 189 return value 186 190 187 191 def validate(self, attrs): ··· 195 199 }, 196 200 ) 197 201 198 - if self.context["created_by"].user_type in User.READ_ONLY_TYPES: 199 - if validated["user_type"] not in User.READ_ONLY_TYPES: 200 - raise exceptions.ValidationError( 201 - { 202 - "user_type": [ 203 - "Read only users can create other read only users only", 204 - ], 205 - }, 206 - ) 202 + if ( 203 + self.context["created_by"].user_type in User.READ_ONLY_TYPES 204 + and validated["user_type"] not in User.READ_ONLY_TYPES 205 + ): 206 + raise exceptions.ValidationError( 207 + { 208 + "user_type": [ 209 + "Read only users can create other read only users only", 210 + ], 211 + }, 212 + ) 207 213 208 214 if ( 209 215 self.context["created_by"].user_type ··· 346 352 extra_kwargs = {"url": {"lookup_field": "username"}} 347 353 348 354 def validate_date_of_birth(self, value): 349 - if value and now().year - value.year < 16: 350 - raise serializers.ValidationError("Age must be greater than 15 years") 355 + if value and now().year - value.year < MIN_USER_AGE: 356 + error = "Age must be greater than 15 years" 357 + raise serializers.ValidationError(error) 351 358 352 359 return value 353 360
+1 -1
care/users/api/serializers/userskill.py
··· 2 2 3 3 from care.users.api.serializers.skill import SkillSerializer 4 4 from care.users.models import Skill, UserSkill 5 - from care.utils.serializer.external_id_field import ExternalIdSerializerField 5 + from care.utils.serializers.fields import ExternalIdSerializerField 6 6 7 7 8 8 class UserSkillSerializer(ModelSerializer):
+8 -26
care/users/api/viewsets/users.py
··· 76 76 field_name, 77 77 value, 78 78 ): 79 - if value: 80 - if value in INVERSE_USER_TYPE: 81 - return queryset.filter(user_type=INVERSE_USER_TYPE[value]) 79 + if value and value in INVERSE_USER_TYPE: 80 + return queryset.filter(user_type=INVERSE_USER_TYPE[value]) 82 81 return queryset 83 82 84 83 user_type = filters.CharFilter(method="get_user_type", field_name="user_type") ··· 123 122 filterset_class = UserFilterSet 124 123 ordering_fields = ["id", "date_joined", "last_login"] 125 124 search_fields = ["first_name", "last_name", "username"] 126 - # last_login 127 - # def get_permissions(self): 128 - # return [ 129 - # DRYPermissions(), 130 - # IsAuthenticated(), 131 - # ] 132 - # if self.request.method == "POST": 133 - # return [ 134 - # DRYPermissions(), 135 - # ] 136 - # else: 137 - # return [ 138 - # IsAuthenticated(), 139 - # DRYPermissions(), 140 - # ] 141 125 142 126 def get_queryset(self): 143 127 if self.request.user.is_superuser: ··· 172 156 def get_object(self) -> User: 173 157 try: 174 158 return super().get_object() 175 - except Http404: 176 - raise Http404("User not found") 159 + except Http404 as e: 160 + error = "User not found" 161 + raise Http404(error) from e 177 162 178 163 def get_serializer_class(self): 179 164 if self.action == "list": 180 165 return UserListSerializer 181 - elif self.action == "add_user": 166 + if self.action == "add_user": 182 167 return UserCreateSerializer 183 - # elif self.action == "create": 184 - # return SignUpSerializer 185 - elif self.action == "profile_picture": 168 + if self.action == "profile_picture": 186 169 return UserImageUploadSerializer 187 - else: 188 - return UserSerializer 170 + return UserSerializer 189 171 190 172 @extend_schema(tags=["users"]) 191 173 @action(detail=False, methods=["GET"])
+2 -3
care/users/management/commands/load_data.py
··· 58 58 states = self.valid_states 59 59 else: 60 60 if state not in self.valid_states: 61 - print("valid state options are ", self.valid_states) 62 - raise Exception("State not found") 61 + error = "State not found" 62 + raise Exception(error) 63 63 states = [state] 64 64 65 65 for state in states: 66 66 current_state_data = self.BASE_URL + state + "/lsg/" 67 - print("Processing Files From", current_state_data) 68 67 management.call_command("load_lsg_data", current_state_data) 69 68 management.call_command("load_ward_data", current_state_data)
+7 -12
care/users/management/commands/load_lsg_data.py
··· 1 - import glob 2 1 import json 3 2 from collections import defaultdict 3 + from pathlib import Path 4 4 5 5 from django.core.management.base import BaseCommand, CommandParser 6 6 ··· 24 24 local_bodies = [] 25 25 26 26 # Creates a map with first char of readable value as key 27 - LOCAL_BODY_CHOICE_MAP = dict([(c[1][0], c[0]) for c in LOCAL_BODY_CHOICES]) 27 + local_body_choice_map = {c[1][0]: c[0] for c in LOCAL_BODY_CHOICES} 28 28 29 29 state = {} 30 30 district = defaultdict(dict) ··· 34 34 return state[state_name] 35 35 state_obj = State.objects.filter(name=state_name).first() 36 36 if not state_obj: 37 - print(f"Creating State {state_name}") 38 37 state_obj = State(name=state_name) 39 38 state_obj.save() 40 39 state[state_name] = state_obj ··· 42 41 43 42 def get_district_obj(district_name, state_name): 44 43 state_obj = get_state_obj(state_name) 45 - if state_name in district: 46 - if district_name in district[state_name]: 47 - return district[state_name][district_name] 44 + if state_name in district and district_name in district[state_name]: 45 + return district[state_name][district_name] 48 46 district_obj = District.objects.filter( 49 47 name=district_name, state=state_obj 50 48 ).first() 51 49 if not district_obj: 52 50 if not district_name: 53 51 return None 54 - print(f"Creating District {district_name}") 55 52 district_obj = District(name=district_name, state=state_obj) 56 53 district_obj.save() 57 54 district[state_name][district_name] = district_obj ··· 80 77 name=lb["name"], 81 78 district=dist_obj, 82 79 localbody_code=lb.get("localbody_code"), 83 - body_type=LOCAL_BODY_CHOICE_MAP.get( 80 + body_type=local_body_choice_map.get( 84 81 (lb.get("localbody_code", " "))[0], 85 82 LOCAL_BODY_CHOICES[-1][0], 86 83 ), ··· 92 89 # Hence, those records can be ignored using the `ignore_conflicts` flag 93 90 LocalBody.objects.bulk_create(local_body_objs, ignore_conflicts=True) 94 91 95 - for f in sorted(glob.glob(f"{folder}/*.json")): 96 - counter += 1 97 - with open(f"{f}") as data_f: 92 + for counter, f in enumerate(sorted(Path.glob(f"{folder}/*.json"))): 93 + with Path(f).open() as data_f: 98 94 data = json.load(data_f) 99 95 data.pop("wards", None) 100 96 local_bodies.append(data) ··· 103 99 if counter % 1000 == 0: 104 100 create_local_bodies(local_bodies) 105 101 local_bodies = [] 106 - print(f"Completed: {counter}") 107 102 108 103 if len(local_bodies) > 0: 109 104 create_local_bodies(local_bodies)
+1 -1
care/users/management/commands/load_skill_data.py
··· 10 10 11 11 help = "Seed Data for Skills" 12 12 13 - def handle(self, *args, **options): 13 + def handle(self, *args, **kwargs): 14 14 self.stdout.write("Seeding Skills Data... ", ending="") 15 15 16 16 skills = [
+2 -4
care/users/management/commands/load_state_data.py
··· 1 1 import json 2 + from pathlib import Path 2 3 3 4 from django.core.management import BaseCommand, CommandParser 4 5 ··· 22 23 json_file_path = options["json_file_path"] 23 24 24 25 data = [] 25 - with open(json_file_path) as json_file: 26 + with Path(json_file_path).open() as json_file: 26 27 data = json.load(json_file) 27 28 28 29 for item in data: 29 30 state_name = item["state"].strip() 30 31 if state_name.lower() in states_to_ignore: 31 - print(f"Skipping {state_name}") 32 32 continue 33 33 34 34 districts = [d.strip() for d in item["districts"].split(",")] ··· 36 36 state, is_created = State.objects.get_or_create( 37 37 name__iexact=state_name, defaults={"name": state_name} 38 38 ) 39 - print(f"{'Created' if is_created else 'Retrieved'} {state_name}") 40 39 41 40 for d in districts: 42 41 _, is_created = District.objects.get_or_create( 43 42 state=state, name__iexact=d, defaults={"name": d} 44 43 ) 45 - print(f"{'Created' if is_created else 'Retrieved'} {state_name}")
+8 -7
care/users/management/commands/load_ward_data.py
··· 1 - import glob 2 1 import json 2 + import logging 3 + from pathlib import Path 3 4 4 5 from django.core.management.base import BaseCommand, CommandParser 5 6 from django.db import IntegrityError ··· 43 44 district_map = {d.name: d for d in districts} 44 45 45 46 # Creates a map with first char of readable value as key 46 - LOCAL_BODY_CHOICE_MAP = dict([(c[1][0], c[0]) for c in LOCAL_BODY_CHOICES]) 47 + local_body_choice_map = {c[1][0]: c[0] for c in LOCAL_BODY_CHOICES} 47 48 48 49 def get_local_body(lb): 49 50 if not lb["district"]: ··· 52 53 name=lb["name"], 53 54 district=district_map[lb["district"]], 54 55 localbody_code=lb.get("localbody_code"), 55 - body_type=LOCAL_BODY_CHOICE_MAP.get( 56 + body_type=local_body_choice_map.get( 56 57 (lb.get("localbody_code", " "))[0], 57 58 LOCAL_BODY_CHOICES[-1][0], 58 59 ), 59 60 ).first() 60 61 61 - for f in sorted(glob.glob(f"{folder}/*.json")): 62 - with open(f"{f}") as data_f: 62 + for f in sorted(Path.glob(f"{folder}/*.json")): 63 + with Path(f).open() as data_f: 63 64 data = json.load(data_f) 64 65 wards = data.pop("wards", None) 65 66 if wards is None: 66 - print("Ward Data not Found ") 67 + logging.info("Ward Data not Found ") 67 68 if data.get("district") is not None: 68 69 local_body = get_local_body(data) 69 70 if not local_body: ··· 80 81 obj.save() 81 82 except IntegrityError: 82 83 pass 83 - print("Processed ", str(counter), " wards") 84 + logging.info("Processed %s wards", str(counter))
+4 -3
care/users/management/commands/populate_investigations.py
··· 1 1 import json 2 + from pathlib import Path 2 3 3 4 from django.core.management import BaseCommand 4 5 from django.db import transaction ··· 8 9 PatientInvestigationGroup, 9 10 ) 10 11 11 - with open("data/investigations.json") as investigations_data: 12 + with Path("data/investigations.json").open() as investigations_data: 12 13 investigations = json.load(investigations_data) 13 14 14 - with open("data/investigation_groups.json") as investigation_groups_data: 15 + with Path("data/investigation_groups.json").open() as investigation_groups_data: 15 16 investigation_groups = json.load(investigation_groups_data) 16 17 17 18 ··· 22 23 23 24 help = "Seed Data for Investigations" 24 25 25 - def handle(self, *args, **options): 26 + def handle(self, *args, **kwargs): 26 27 investigation_group_dict = {} 27 28 28 29 investigation_groups_to_create = [
+1 -3
care/users/management/commands/seed_data.py
··· 15 15 16 16 help = "Seed Data for Inventory" 17 17 18 - def handle(self, *args, **options): 19 - print("Creating Units for Inventory as well as their conversion rates") 20 - 18 + def handle(self, *args, **kwargs): 21 19 # Inventory Unit 22 20 23 21 items, _ = FacilityInventoryUnit.objects.get_or_create(name="Items")
+4 -1
care/users/models.py
··· 147 147 f"It looks like you haven't loaded district data. It is recommended to populate district data before you create a super user. Please run `python manage.py {data_command}`.\n Proceed anyway? [y/N]" 148 148 ) 149 149 if proceed.lower() != "y": 150 - raise Exception("Aborted Superuser Creation") 150 + raise Exception 151 151 district = None 152 152 153 153 extra_fields["district"] = district ··· 438 438 ) 439 439 start_date = models.DateTimeField(default=now) 440 440 end_date = models.DateTimeField(null=True, blank=True) 441 + 442 + def __str__(self): 443 + return self.facility.name 441 444 442 445 443 446 class UserFlag(BaseFlag):
+1 -1
care/users/reset_password_views.py
··· 170 170 ) 171 171 except ValidationError as e: 172 172 # raise a validation error for the serializer 173 - raise exceptions.ValidationError({"password": e.messages}) 173 + raise exceptions.ValidationError({"password": e.messages}) from e 174 174 175 175 reset_password_token.user.set_password(password) 176 176 reset_password_token.user.save()
+1 -7
care/users/signals.py
··· 18 18 """ 19 19 Handles password reset tokens 20 20 When a token is created, an e-mail needs to be sent to the user 21 - :param sender: View Class that sent the signal 22 - :param instance: View Instance that sent the signal 23 - :param reset_password_token: Token Model Object 24 - :param args: 25 - :param kwargs: 26 - :return: 27 21 """ 28 22 # send an e-mail to the user 29 23 context = { ··· 57 51 fields_to_save &= set(update_fields) 58 52 if fields_to_save: 59 53 with contextlib.suppress(IndexError): 60 - instance._previous_values = instance.__class__._base_manager.filter( 54 + instance._previous_values = instance.__class__._base_manager.filter( # noqa SLF001 61 55 pk=instance.pk 62 56 ).values(*fields_to_save)[0] 63 57
-30
care/users/tests/test_models.py
··· 15 15 name="ghatak", description="corona virus specialist" 16 16 ) 17 17 18 - def test_max_length_name(self): 19 - """Test max length for name is 255""" 20 - skill = self.skill 21 - max_length = skill._meta.get_field("name").max_length 22 - self.assertEqual(max_length, 255) 23 - 24 18 def test_object_name(self): 25 19 """Test that the name is returned while printing the object""" 26 20 skill = self.skill ··· 35 29 """ 36 30 cls.state = State.objects.create(name="kerala") 37 31 38 - def test_max_length_name(self): 39 - """Test max length for name is 255""" 40 - state = self.state 41 - max_length = state._meta.get_field("name").max_length 42 - self.assertEqual(max_length, 255) 43 - 44 32 def test_object_name(self): 45 33 """Test that the correct format is returned while printing the object""" 46 34 state = self.state ··· 56 44 state = State.objects.create(name="uttar pradesh") 57 45 cls.district = District.objects.create(state=state, name="name") 58 46 59 - def test_max_length_name(self): 60 - """Test max length for name is 255""" 61 - district = self.district 62 - max_length = district._meta.get_field("name").max_length 63 - self.assertEqual(max_length, 255) 64 - 65 47 def test_object_name(self): 66 48 """Test that the correct format is returned while printing the object""" 67 49 district = self.district ··· 83 65 district=district, name="blabla", body_type=1 84 66 ) 85 67 86 - def test_max_length_name(self): 87 - """Test max length for name is 255""" 88 - local_body = self.local_body 89 - max_length = local_body._meta.get_field("name").max_length 90 - self.assertEqual(max_length, 255) 91 - 92 68 def test_object_name(self): 93 69 """Test that the correct format is returned while printing the object""" 94 70 local_body = self.local_body ··· 114 90 gender=1, 115 91 date_of_birth=date(2005, 1, 1), 116 92 ) 117 - 118 - def test_max_length_phone_number(self): 119 - """Test maximum length for phone number is 14""" 120 - user = self.user 121 - max_length = user._meta.get_field("phone_number").max_length 122 - self.assertEqual(max_length, 14)
+22 -31
care/utils/assetintegration/base.py
··· 2 2 3 3 import requests 4 4 from django.conf import settings 5 + from rest_framework import status 5 6 from rest_framework.exceptions import APIException 6 7 7 8 from care.utils.jwks.token_generator import generate_jwt ··· 33 34 "Accept": "application/json", 34 35 } 35 36 36 - def api_post(self, url, data=None): 37 - req = requests.post( 38 - url, 39 - json=data, 40 - headers=self.get_headers(), 41 - timeout=self.timeout, 42 - ) 37 + def _validate_response(self, response: requests.Response): 43 38 try: 44 - response = req.json() 45 - if req.status_code >= 400: 46 - raise APIException(response, req.status_code) 47 - return response 39 + if response.status_code >= status.HTTP_400_BAD_REQUEST: 40 + raise APIException(response.text, response.status_code) 41 + return response.json() 48 42 49 - except requests.Timeout: 50 - raise APIException({"error": "Request Timeout"}, 504) 43 + except requests.Timeout as e: 44 + raise APIException({"error": "Request Timeout"}, 504) from e 45 + 46 + except json.decoder.JSONDecodeError as e: 47 + raise APIException( 48 + {"error": "Invalid Response"}, response.status_code 49 + ) from e 51 50 52 - except json.decoder.JSONDecodeError: 53 - raise APIException({"error": "Invalid Response"}, req.status_code) 51 + def api_post(self, url, data=None): 52 + return self._validate_response( 53 + requests.post( 54 + url, json=data, headers=self.get_headers(), timeout=self.timeout 55 + ) 56 + ) 54 57 55 58 def api_get(self, url, data=None): 56 - req = requests.get( 57 - url, 58 - params=data, 59 - headers=self.get_headers(), 60 - timeout=self.timeout, 59 + return self._validate_response( 60 + requests.get( 61 + url, params=data, headers=self.get_headers(), timeout=self.timeout 62 + ) 61 63 ) 62 - try: 63 - if req.status_code >= 400: 64 - raise APIException(req.text, req.status_code) 65 - response = req.json() 66 - return response 67 - 68 - except requests.Timeout: 69 - raise APIException({"error": "Request Timeout"}, 504) 70 - 71 - except json.decoder.JSONDecodeError: 72 - raise APIException({"error": "Invalid Response"}, req.status_code)
+2 -2
care/utils/assetintegration/hl7monitor.py
··· 17 17 super().__init__(meta) 18 18 except KeyError as e: 19 19 raise ValidationError( 20 - dict((key, f"{key} not found in asset metadata") for key in e.args) 21 - ) 20 + {key: f"{key} not found in asset metadata" for key in e.args} 21 + ) from e 22 22 23 23 def handle_action(self, action): 24 24 action_type = action["type"]
+2 -2
care/utils/assetintegration/onvif.py
··· 24 24 self.access_key = self.meta["camera_access_key"].split(":")[2] 25 25 except KeyError as e: 26 26 raise ValidationError( 27 - dict((key, f"{key} not found in asset metadata") for key in e.args) 28 - ) 27 + {key: f"{key} not found in asset metadata" for key in e.args} 28 + ) from e 29 29 30 30 def handle_action(self, action): 31 31 action_type = action["type"]
+2 -2
care/utils/assetintegration/ventilator.py
··· 17 17 super().__init__(meta) 18 18 except KeyError as e: 19 19 raise ValidationError( 20 - dict((key, f"{key} not found in asset metadata") for key in e.args) 21 - ) 20 + {key: f"{key} not found in asset metadata" for key in e.args} 21 + ) from e 22 22 23 23 def handle_action(self, action): 24 24 action_type = action["type"]
care/utils/cache/__init__.py

This is a binary file and will not be displayed.

care/utils/csp/__init__.py

This is a binary file and will not be displayed.

+5 -4
care/utils/csp/config.py
··· 1 1 import enum 2 - from typing import TypeAlias, TypedDict 2 + from typing import TypedDict 3 3 4 4 from django.conf import settings 5 5 ··· 11 11 endpoint_url: str 12 12 13 13 14 - BucketName: TypeAlias = str 14 + type BucketName = str 15 15 16 16 17 17 class CSProvider(enum.Enum): ··· 57 57 def get_client_config(bucket_type: BucketType, external=False): 58 58 if bucket_type == BucketType.FACILITY: 59 59 return get_facility_bucket_config(external=external) 60 - elif bucket_type == BucketType.PATIENT: 60 + if bucket_type == BucketType.PATIENT: 61 61 return get_patient_bucket_config(external=external) 62 - raise ValueError("Invalid Bucket Type") 62 + msg = "Invalid Bucket Type" 63 + raise ValueError(msg)
+7 -7
care/utils/event_utils.py
··· 14 14 15 15 def get_changed_fields(old: Model, new: Model) -> set[str]: 16 16 changed_fields: set[str] = set() 17 - for field in new._meta.fields: 17 + for field in new._meta.fields: # noqa: SLF001 18 18 field_name = field.name 19 19 if getattr(old, field_name, None) != getattr(new, field_name, None): 20 20 changed_fields.add(field_name) 21 21 return changed_fields 22 22 23 23 24 - def serialize_field(object: Model, field_name: str): 24 + def serialize_field(obj: Model, field_name: str): 25 25 if "__" in field_name: 26 26 field_name, sub_field = field_name.split("__", 1) 27 - related_object = getattr(object, field_name, None) 27 + related_object = getattr(obj, field_name, None) 28 28 return serialize_field(related_object, sub_field) 29 29 30 30 value = None ··· 33 33 except AttributeError: 34 34 if object is not None: 35 35 logger.warning( 36 - f"Field {field_name} not found in {object.__class__.__name__}" 36 + "Field %s not found in %s", field_name, object.__class__.__name__ 37 37 ) 38 38 return None 39 39 40 40 try: 41 41 # serialize choice fields with display value 42 - field = object._meta.get_field(field_name) 42 + field = object._meta.get_field(field_name) # noqa: SLF001 43 43 if issubclass(field.__class__, Field) and field.choices: 44 44 value = getattr(object, f"get_{field_name}_display", lambda: value)() 45 45 except FieldDoesNotExist: ··· 51 51 52 52 def model_diff(old, new): 53 53 diff = {} 54 - for field in new._meta.fields: 54 + for field in new._meta.fields: # noqa: SLF001 55 55 field_name = field.name 56 56 if getattr(old, field_name, None) != getattr(new, field_name, None): 57 57 diff[field_name] = getattr(new, field_name, None) ··· 65 65 return list(o) 66 66 if isinstance(o, datetime): 67 67 return o.isoformat() 68 - logger.warning(f"Serializing Unknown Type {type(o)}, {o}") 68 + logger.warning("Serializing Unknown Type %s, %s", type(o), o) 69 69 return str(o)
+1 -1
care/utils/exceptions.py
··· 1 - class CeleryTaskException(Exception): 1 + class CeleryTaskError(Exception): 2 2 pass
+3 -3
care/utils/file_uploads/cover_image.py
··· 18 18 try: 19 19 s3.delete_object(Bucket=bucket_name, Key=image_key) 20 20 except Exception: 21 - logger.warning(f"Failed to delete cover image {image_key}") 21 + logger.warning("Failed to delete cover image %s", image_key) 22 22 23 23 24 24 def upload_cover_image( 25 25 image: UploadedFile, 26 26 object_external_id: str, 27 27 folder: Literal["cover_images", "avatars"], 28 - old_key: str = None, 28 + old_key: str | None = None, 29 29 ) -> str: 30 30 config, bucket_name = get_client_config(BucketType.FACILITY) 31 31 s3 = boto3.client("s3", **config) ··· 34 34 try: 35 35 s3.delete_object(Bucket=bucket_name, Key=old_key) 36 36 except Exception: 37 - logger.warning(f"Failed to delete old cover image {old_key}") 37 + logger.warning("Failed to delete old cover image %s", old_key) 38 38 39 39 image_extension = image.name.rsplit(".", 1)[-1] 40 40 image_key = (
+1 -2
care/utils/filters/multiselect.py
··· 9 9 return None 10 10 values_list = value.split(",") 11 11 filters = {self.field_name + "__in": values_list} 12 - qs = qs.filter(**filters) 13 - return qs 12 + return qs.filter(**filters)
+2 -2
care/utils/jwks/generate_jwk.py
··· 17 17 def get_jwks_from_file(base_path: Path): 18 18 file_path = base_path / "jwks.b64.txt" 19 19 try: 20 - with open(file_path, "r") as file: 20 + with file_path.open() as file: 21 21 return file.read() 22 22 except FileNotFoundError: 23 23 jwks = generate_encoded_jwks() 24 - with open(file_path, "w") as file: 24 + with file_path.open("w") as file: 25 25 file.write(jwks) 26 26 return jwks
+2 -3
care/utils/jwks/token_generator.py
··· 1 - from datetime import datetime 2 - 3 1 from authlib.jose import jwt 4 2 from django.conf import settings 3 + from django.utils.timezone import now 5 4 6 5 7 6 def generate_jwt(claims=None, exp=60, jwks=None): ··· 10 9 if jwks is None: 11 10 jwks = settings.JWKS 12 11 header = {"alg": "RS256"} 13 - time = int(datetime.now().timestamp()) 12 + time = int(now().timestamp()) 14 13 payload = { 15 14 "iat": time, 16 15 "exp": time + exp,
+1 -1
care/utils/lock.py
··· 15 15 self.timeout = timeout 16 16 17 17 def acquire(self): 18 - if not cache.set(self.key, True, self.timeout, nx=True): 18 + if not cache.set(self.key, value=True, timeout=self.timeout, nx=True): 19 19 raise ObjectLocked 20 20 21 21 def release(self):
+10 -16
care/utils/models/base.py
··· 11 11 qs = super().get_queryset() 12 12 return qs.filter(deleted=False) 13 13 14 - # def filter(self, *args, **kwargs): 15 - # _id = kwargs.pop("id", "----") 16 - # if _id != "----" and not isinstance(_id, int): 17 - # kwargs["external_id"] = _id 18 - # return super().filter(*args, **kwargs) 19 - 20 14 21 15 class BaseModel(models.Model): 22 16 external_id = models.UUIDField(default=uuid4, unique=True, db_index=True) ··· 52 46 class Meta: 53 47 abstract = True 54 48 49 + def save(self, *args, **kwargs): 50 + self.validate_flag(self.flag) 51 + cache.delete( 52 + self.cache_key_template.format( 53 + entity_id=self.entity_id, flag_name=self.flag 54 + ) 55 + ) 56 + cache.delete(self.all_flags_cache_key_template.format(entity_id=self.entity_id)) 57 + return super().save(*args, **kwargs) 58 + 55 59 @property 56 60 def entity(self): 57 61 return getattr(self, self.entity_field_name) ··· 63 67 @classmethod 64 68 def validate_flag(cls, flag_name: FlagName): 65 69 FlagRegistry.validate_flag_name(cls.flag_type, flag_name) 66 - 67 - def save(self, *args, **kwargs): 68 - self.validate_flag(self.flag) 69 - cache.delete( 70 - self.cache_key_template.format( 71 - entity_id=self.entity_id, flag_name=self.flag 72 - ) 73 - ) 74 - cache.delete(self.all_flags_cache_key_template.format(entity_id=self.entity_id)) 75 - return super().save(*args, **kwargs) 76 70 77 71 @classmethod 78 72 def check_entity_has_flag(cls, entity_id: int, flag_name: FlagName) -> bool:
+34 -22
care/utils/models/validators.py
··· 48 48 49 49 message = str(error).replace("\n\n", ": ").replace("\n", "") 50 50 container.append(ValidationError(message)) 51 + return None 51 52 52 53 53 54 @deconstructible ··· 98 99 99 100 def __init__(self, types: Iterable[str], *args, **kwargs): 100 101 if not isinstance(types, Iterable) or isinstance(types, str) or len(types) == 0: 101 - raise ValueError("The `types` argument must be a non-empty iterable.") 102 + msg = "The `types` argument must be a non-empty iterable." 103 + raise ValueError(msg) 102 104 103 105 self.types = types 104 106 self.message = f"Invalid phone number. Must be one of the following types: {', '.join(self.types)}. Received: %(value)s" 105 107 self.code = "invalid_phone_number" 106 108 107 - self.regex = r"|".join([self.regex_map[type] for type in self.types]) 109 + self.regex = r"|".join([self.regex_map[t] for t in self.types]) 108 110 super().__init__(*args, **kwargs) 109 111 110 112 def __eq__(self, other): ··· 139 141 if not allow_floats and ( 140 142 isinstance(min_amount, float) or isinstance(max_amount, float) 141 143 ): 142 - raise ValueError( 144 + msg = ( 143 145 "If floats are not allowed, min_amount and max_amount must be integers" 144 146 ) 147 + raise ValueError(msg) 145 148 146 149 def __call__(self, value: str): 147 150 try: 148 151 amount, unit = value.split(" ", maxsplit=1) 149 152 if unit not in self.allowed_units: 150 - raise ValidationError( 151 - f"Unit must be one of {', '.join(self.allowed_units)}" 152 - ) 153 + msg = f"Unit must be one of {', '.join(self.allowed_units)}" 154 + raise ValidationError(msg) 153 155 154 156 amount_number: int | float = float(amount) 155 157 if amount_number.is_integer(): 156 158 amount_number = int(amount_number) 157 159 elif not self.allow_floats: 158 - raise ValidationError("Input amount must be an integer") 160 + msg = "Input amount must be an integer" 161 + raise ValidationError(msg) 159 162 elif len(str(amount_number).split(".")[1]) > self.precision: 160 - raise ValidationError("Input amount must have at most 4 decimal places") 163 + msg = "Input amount must have at most 4 decimal places" 164 + raise ValidationError(msg) 161 165 162 166 if len(amount) != len(str(amount_number)): 163 - raise ValidationError( 164 - f"Input amount must be a valid number without leading{' or trailing ' if self.allow_floats else ' '}zeroes" 165 - ) 167 + msg = f"Input amount must be a valid number without leading{' or trailing ' if self.allow_floats else ' '}zeroes" 168 + raise ValidationError(msg) 166 169 167 170 if self.min_amount > amount_number or amount_number > self.max_amount: 168 - raise ValidationError( 169 - f"Input amount must be between {self.min_amount} and {self.max_amount}" 170 - ) 171 - except ValueError: 172 - raise ValidationError( 173 - "Invalid Input, must be in the format: <amount> <unit>" 174 - ) 171 + msg = f"Input amount must be between {self.min_amount} and {self.max_amount}" 172 + raise ValidationError(msg) 173 + except ValueError as e: 174 + msg = "Invalid Input, must be in the format: <amount> <unit>" 175 + raise ValidationError(msg) from e 175 176 176 177 def clean(self, value: str): 177 178 if value is None: ··· 199 200 ) 200 201 201 202 203 + class MiddlewareDomainAddressValidator(RegexValidator): 204 + regex = r"^(?!https?:\/\/)[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*\.[a-zA-Z]{2,}$" 205 + code = "invalid_domain_name" 206 + message = _( 207 + "The domain name is invalid. " 208 + "It should not start with scheme and " 209 + "should not end with a trailing slash." 210 + ) 211 + 212 + 202 213 @deconstructible 203 214 class ImageSizeValidator: 204 215 message: dict[str, str] = { ··· 242 253 self.min_size = min_size 243 254 self.max_size = max_size 244 255 if aspect_ratio: 245 - self.aspect_ratio = set( 256 + self.aspect_ratio = { 246 257 Fraction(ratio).limit_denominator(10) for ratio in aspect_ratio 247 - ) 258 + } 248 259 self.aspect_ratio_str = ", ".join( 249 260 f"{ratio.numerator}:{ratio.denominator}" for ratio in self.aspect_ratio 250 261 ) ··· 317 328 ) 318 329 319 330 def _humanize_bytes(self, size: int) -> str: 331 + byte_size = 1024.0 320 332 for unit in ["B", "KB"]: 321 - if size < 1024.0: 333 + if size < byte_size: 322 334 return f"{f"{size:.2f}".rstrip(".0")} {unit}" 323 - size /= 1024.0 335 + size /= byte_size 324 336 return f"{f"{size:.2f}".rstrip(".0")} MB" 325 337 326 338
+24 -20
care/utils/notification_handler.py
··· 1 1 import json 2 + import logging 2 3 3 4 from celery import shared_task 4 5 from django.apps import apps ··· 16 17 ) 17 18 from care.facility.models.shifting import ShiftingRequest 18 19 from care.users.models import User 19 - from care.utils.sms.sendSMS import sendSMS 20 + from care.utils.sms.send_sms import send_sms 21 + 22 + logger = logging.getLogger(__name__) 20 23 21 24 22 - class NotificationCreationException(Exception): 25 + class NotificationCreationError(Exception): 23 26 pass 24 27 25 28 ··· 41 44 return apps.get_model(f"facility.{model_name}") 42 45 43 46 47 + # ruff: noqa: SIM102, PLR0912 rebuilding the notification generator would be easier 44 48 class NotificationGenerator: 45 49 generate_for_facility = False 46 50 generate_for_user = False ··· 64 68 ): 65 69 if not worker_initated: 66 70 if not isinstance(event_type, Notification.EventType): 67 - raise NotificationCreationException("Event Type Invalid") 71 + msg = "Event Type Invalid" 72 + raise NotificationCreationError(msg) 68 73 if not isinstance(event, Notification.Event): 69 - raise NotificationCreationException("Event Invalid") 74 + msg = "Event Invalid" 75 + raise NotificationCreationError(msg) 70 76 if not isinstance(caused_by, User): 71 - raise NotificationCreationException( 72 - "edited_by must be an instance of a user" 73 - ) 74 - if facility: 75 - if not isinstance(facility, Facility): 76 - raise NotificationCreationException( 77 - "facility must be an instance of Facility" 78 - ) 77 + msg = "edited_by must be an instance of a user" 78 + raise NotificationCreationError(msg) 79 + if facility and not isinstance(facility, Facility): 80 + msg = "facility must be an instance of Facility" 81 + raise NotificationCreationError(msg) 79 82 mediums = [] 80 83 if notification_mediums: 81 84 for medium in notification_mediums: 82 85 if not isinstance(medium, Notification.Medium): 83 - raise NotificationCreationException("Medium Type Invalid") 86 + msg = "Medium Type Invalid" 87 + raise NotificationCreationError(msg) 84 88 mediums.append(medium.value) 85 89 data = { 86 90 "event_type": event_type.value, ··· 102 106 self.worker_initiated = False 103 107 return 104 108 self.worker_initiated = True 105 - Model = get_model_class(caused_object) 106 - caused_object = Model.objects.get(id=caused_object_pk) 109 + caused_object = get_model_class(caused_object).objects.get(id=caused_object_pk) 107 110 caused_by = User.objects.get(id=caused_by) 108 111 facility = Facility.objects.get(id=facility) 109 112 self.notification_mediums = notification_mediums ··· 231 234 self.caused_object.patient.phone_number, 232 235 self.caused_object.patient.emergency_phone_number, 233 236 ] 237 + return None 234 238 235 239 def _get_default_medium(self): 236 240 return [Notification.Medium.SYSTEM.value] ··· 347 351 }, 348 352 ) 349 353 except WebPushException as ex: 350 - print("Web Push Failed with Exception: {}", repr(ex)) 354 + logger.info("Web Push Failed with Exception: %s", repr(ex)) 351 355 if ex.response and ex.response.json(): 352 356 extra = ex.response.json() 353 - print( 354 - "Remote service replied with a {}:{}, {}", 357 + logger.info( 358 + "Remote service replied with a %s:%s, %s", 355 359 extra.code, 356 360 extra.errno, 357 361 extra.message, 358 362 ) 359 363 except Exception as e: 360 - print("Error When Doing WebPush", e) 364 + logger.info("Error When Doing WebPush: %s", e) 361 365 362 366 def generate(self): 363 367 if not self.worker_initiated: ··· 367 371 medium == Notification.Medium.SMS.value 368 372 and settings.SEND_SMS_NOTIFICATION 369 373 ): 370 - sendSMS( 374 + send_sms( 371 375 self.generate_sms_phone_numbers(), 372 376 self.generate_sms_message(), 373 377 many=True,
care/utils/queryset/__init__.py

This is a binary file and will not be displayed.

+9 -9
care/utils/registries/feature_flag.py
··· 1 1 import enum 2 2 import logging 3 - from typing import TypeAlias 4 3 5 4 from django.core.exceptions import ValidationError 6 5 7 6 logger = logging.getLogger(__name__) 8 7 9 8 10 - class FlagNotFoundException(ValidationError): 9 + class FlagNotFoundError(ValidationError): 11 10 pass 12 11 13 12 ··· 16 15 FACILITY = "FACILITY" 17 16 18 17 19 - # TODO: convert to type in python 3.12 20 - FlagName = str 21 - FlagTypeRegistry: TypeAlias = dict[FlagType, dict[FlagName, bool]] 18 + type FlagName = str 19 + type FlagTypeRegistry = dict[FlagType, dict[FlagName, bool]] 22 20 23 21 24 22 class FlagRegistry: ··· 35 33 try: 36 34 del cls._flags[flag_type][flag_name] 37 35 except KeyError as e: 38 - logger.warning(f"Flag {flag_name} not found in {flag_type}: {e}") 36 + logger.warning("Flag %s not found in %s: %s", flag_name, flag_type, e) 39 37 40 38 @classmethod 41 39 def register_wrapper(cls, flag_type, flag_name) -> None: ··· 48 46 @classmethod 49 47 def validate_flag_type(cls, flag_type: FlagType) -> None: 50 48 if flag_type not in cls._flags: 51 - raise FlagNotFoundException("Invalid Flag Type") 49 + msg = "Invalid Flag Type" 50 + raise FlagNotFoundError(msg) 52 51 53 52 @classmethod 54 53 def validate_flag_name(cls, flag_type: FlagType, flag_name): 55 54 cls.validate_flag_type(flag_type) 56 55 if flag_name not in cls._flags[flag_type]: 57 - raise FlagNotFoundException("Flag not registered") 56 + msg = "Flag not registered" 57 + raise FlagNotFoundError(msg) 58 58 59 59 @classmethod 60 60 def get_all_flags(cls, flag_type: FlagType) -> list[FlagName]: ··· 65 65 def get_all_flags_as_choices( 66 66 cls, flag_type: FlagType 67 67 ) -> list[tuple[FlagName, FlagName]]: 68 - return ((x, x) for x in cls._flags.get(flag_type, {}).keys()) 68 + return ((x, x) for x in cls._flags.get(flag_type, {}))
care/utils/serializer/__init__.py care/utils/serializers/__init__.py
-39
care/utils/serializer/external_id_field.py
··· 1 - import uuid 2 - 3 - from django.core.exceptions import ObjectDoesNotExist 4 - from rest_framework import serializers 5 - from rest_framework.fields import empty 6 - 7 - 8 - class UUIDValidator: 9 - def __call__(self, value): 10 - try: 11 - return uuid.UUID(value) 12 - except ValueError: 13 - raise serializers.ValidationError("invalid uuid") 14 - 15 - 16 - class ExternalIdSerializerField(serializers.UUIDField): 17 - def __init__(self, queryset=None, *args, **kwargs): 18 - super().__init__(*args, **kwargs) 19 - self.queryset = queryset 20 - 21 - def get_validators(self): 22 - validators = super().get_validators() 23 - validators += [UUIDValidator()] 24 - return validators 25 - 26 - def to_internal_value(self, data): 27 - return data 28 - 29 - def to_representation(self, value): 30 - return value.external_id if value else None 31 - 32 - def run_validation(self, data=empty): 33 - value = super().run_validation(data) 34 - if value: 35 - try: 36 - value = self.queryset.get(external_id=value) 37 - except ObjectDoesNotExist: 38 - raise serializers.ValidationError("object with this id not found") 39 - return value
care/utils/serializer/history_serializer.py care/utils/serializers/history_serializer.py
care/utils/serializer/tests/__init__.py care/utils/sms/__init__.py
+57
care/utils/serializers/fields.py
··· 1 + import uuid 2 + 3 + from django.core.exceptions import ObjectDoesNotExist 4 + from rest_framework import serializers 5 + from rest_framework.fields import empty 6 + 7 + 8 + class UUIDValidator: 9 + def __call__(self, value): 10 + try: 11 + return uuid.UUID(value) 12 + except ValueError as e: 13 + msg = "invalid uuid" 14 + raise serializers.ValidationError(msg) from e 15 + 16 + 17 + class ExternalIdSerializerField(serializers.UUIDField): 18 + def __init__(self, queryset=None, *args, **kwargs): 19 + super().__init__(*args, **kwargs) 20 + self.queryset = queryset 21 + 22 + def get_validators(self): 23 + validators = super().get_validators() 24 + validators += [UUIDValidator()] 25 + return validators 26 + 27 + def to_internal_value(self, data): 28 + return data 29 + 30 + def to_representation(self, value): 31 + return value.external_id if value else None 32 + 33 + def run_validation(self, data=empty): 34 + value = super().run_validation(data) 35 + if value: 36 + try: 37 + value = self.queryset.get(external_id=value) 38 + except ObjectDoesNotExist as e: 39 + msg = "object with this id not found" 40 + raise serializers.ValidationError(msg) from e 41 + return value 42 + 43 + 44 + class ChoiceField(serializers.ChoiceField): 45 + def to_representation(self, obj): 46 + try: 47 + return self._choices[obj] 48 + except KeyError: 49 + key_type = type(next(iter(self.choices.keys()))) 50 + key = key_type(obj) 51 + return self._choices[key] 52 + 53 + def to_internal_value(self, data): 54 + if isinstance(data, str) and data not in self.choice_strings_to_values: 55 + choice_name_map = {v: k for k, v in self._choices.items()} 56 + data = choice_name_map.get(data) 57 + return super().to_internal_value(data)
+7 -1
care/utils/sms/sendSMS.py care/utils/sms/send_sms.py
··· 1 + import logging 2 + 1 3 import boto3 2 4 from django.conf import settings 3 5 4 6 from care.utils.models.validators import mobile_validator 5 7 8 + logger = logging.getLogger(__name__) 6 9 7 - def sendSMS(phone_numbers, message, many=False): 10 + 11 + def send_sms(phone_numbers, message, many=False): 8 12 if not many: 9 13 phone_numbers = [phone_numbers] 10 14 phone_numbers = list(set(phone_numbers)) ··· 12 16 try: 13 17 mobile_validator(phone) 14 18 except Exception: 19 + if settings.DEBUG: 20 + logger.error("Invalid Phone Number %s", phone) 15 21 continue 16 22 client = boto3.client( 17 23 "sns",
+5 -4
care/utils/tests/test_feature_flags.py
··· 1 1 from django.test import TestCase 2 2 3 3 from care.utils.registries.feature_flag import ( 4 - FlagNotFoundException, 4 + FlagNotFoundError, 5 5 FlagRegistry, 6 6 FlagType, 7 7 ) 8 8 9 9 10 + # ruff: noqa: SLF001 10 11 class FeatureFlagTestCase(TestCase): 11 12 def setUp(self): 12 13 FlagRegistry._flags = {} ··· 33 34 self.assertIsNone(FlagRegistry.validate_flag_type(FlagType.USER)) 34 35 35 36 def test_validate_flag_type_invalid(self): 36 - with self.assertRaises(FlagNotFoundException): 37 + with self.assertRaises(FlagNotFoundError): 37 38 FlagRegistry.validate_flag_type( 38 39 FlagType.USER 39 40 ) # FlagType.USER is not registered ··· 43 44 self.assertIsNone(FlagRegistry.validate_flag_name(FlagType.USER, "TEST_FLAG")) 44 45 45 46 def test_validate_flag_name_invalid(self): 46 - with self.assertRaises(FlagNotFoundException) as ectx: 47 + with self.assertRaises(FlagNotFoundError) as ectx: 47 48 FlagRegistry.validate_flag_name(FlagType.USER, "TEST_OTHER_FLAG") 48 49 self.assertEqual(ectx.exception.message, "Invalid Flag Type") 49 50 50 51 FlagRegistry.register(FlagType.USER, "TEST_FLAG") 51 - with self.assertRaises(FlagNotFoundException) as ectx: 52 + with self.assertRaises(FlagNotFoundError) as ectx: 52 53 FlagRegistry.validate_flag_name(FlagType.USER, "TEST_OTHER_FLAG") 53 54 self.assertEqual(ectx.exception.message, "Flag not registered") 54 55
+42 -44
care/utils/tests/test_utils.py
··· 56 56 fake = Faker() 57 57 58 58 59 - class override_cache(override_settings): 59 + class OverrideCache(override_settings): 60 60 """ 61 61 Overrides the cache settings for the test to use a 62 62 local memory cache instead of the redis cache ··· 85 85 mock_equal = EverythingEquals() 86 86 87 87 88 - def assert_equal_dicts(d1, d2, ignore_keys=[]): 88 + def assert_equal_dicts(d1, d2, ignore_keys=None): 89 + if ignore_keys is None: 90 + ignore_keys = [] 91 + 89 92 def check_equal(): 90 93 ignored = set(ignore_keys) 91 94 for k1, v1 in d1.items(): 92 95 if k1 not in ignored and (k1 not in d2 or d2[k1] != v1): 93 - print(k1, v1, d2[k1]) 96 + print(k1, v1, d2[k1]) # noqa: T201 94 97 return False 95 98 for k2, v2 in d2.items(): 96 99 if k2 not in ignored and k2 not in d1: 97 - print(k2, v2) 100 + print(k2, v2) # noqa: T201 98 101 return False 99 102 return True 100 103 ··· 142 145 return LocalBody.objects.create(**data) 143 146 144 147 @classmethod 145 - def get_user_data(cls, district: District, user_type: str = None): 148 + def get_user_data(cls, district: District, user_type: str | None = None): 146 149 """ 147 150 Returns the data to be used for API testing 148 151 ··· 174 177 @classmethod 175 178 def create_user( 176 179 cls, 177 - username: str = None, 180 + username: str | None = None, 178 181 district: District = None, 179 182 local_body: LocalBody = None, 180 183 **kwargs, ··· 265 268 "created_by": user, 266 269 } 267 270 data.update(kwargs) 268 - facility = Facility.objects.create(**data) 269 - return facility 271 + return Facility.objects.create(**data) 270 272 271 273 @classmethod 272 274 def get_patient_data(cls, district, state) -> dict: ··· 348 350 "discharge_date": None, 349 351 "consultation_notes": "", 350 352 "course_in_facility": "", 351 - "patient_no": int(datetime.now().timestamp() * 1000), 353 + "patient_no": int(now().timestamp() * 1000), 352 354 "route_to_facility": 10, 353 355 } 354 356 ··· 492 494 493 495 @classmethod 494 496 def clone_object(cls, obj, save=True): 495 - new_obj = obj._meta.model.objects.get(pk=obj.id) 497 + new_obj = obj._meta.model.objects.get(pk=obj.id) # noqa: SLF001 496 498 new_obj.pk = None 497 499 new_obj.id = None 498 500 try: ··· 625 627 def create_patient_investigation_group(cls, **kwargs) -> PatientInvestigationGroup: 626 628 data = cls.get_patient_investigation_group_data() 627 629 data.update(**kwargs) 628 - investigation_group = PatientInvestigationGroup.objects.create(**data) 629 - return investigation_group 630 + return PatientInvestigationGroup.objects.create(**data) 630 631 631 632 @classmethod 632 633 def get_patient_investigation_session_data(cls, user) -> dict: ··· 640 641 ) -> InvestigationSession: 641 642 data = cls.get_patient_investigation_session_data(user) 642 643 data.update(**kwargs) 643 - investigation_session = InvestigationSession.objects.create(**data) 644 - return investigation_session 644 + return InvestigationSession.objects.create(**data) 645 645 646 646 @classmethod 647 647 def get_investigation_value_data( ··· 694 694 return { 695 695 "consultation": consultation, 696 696 "prescription_type": "REGULAR", 697 - "medicine": None, # TODO : Create medibase medicine 697 + "medicine": None, 698 698 "medicine_old": "Sample old Medicine", 699 699 "route": "Oral", 700 700 "base_dosage": "500mg", ··· 761 761 def get_local_body_representation(self, local_body: LocalBody): 762 762 if local_body is None: 763 763 return {"local_body": None, "local_body_object": None} 764 - else: 765 - return { 766 - "local_body": local_body.id, 767 - "local_body_object": { 768 - "id": local_body.id, 769 - "name": local_body.name, 770 - "district": local_body.district.id, 771 - "localbody_code": local_body.localbody_code, 772 - "body_type": local_body.body_type, 773 - }, 774 - } 764 + return { 765 + "local_body": local_body.id, 766 + "local_body_object": { 767 + "id": local_body.id, 768 + "name": local_body.name, 769 + "district": local_body.district.id, 770 + "localbody_code": local_body.localbody_code, 771 + "body_type": local_body.body_type, 772 + }, 773 + } 775 774 776 775 def get_district_representation(self, district: District): 777 776 if district is None: ··· 795 794 return {k: to_matching_type(k, v) for k, v in d.items()} 796 795 797 796 def to_matching_type(name: str, value): 798 - if isinstance(value, (OrderedDict, dict)): 797 + if isinstance(value, OrderedDict | dict): 799 798 return dict_to_matching_type(dict(value)) 800 - elif isinstance(value, list): 799 + if isinstance(value, list): 801 800 return [to_matching_type("", v) for v in value] 802 - elif "date" in name and not isinstance( 803 - value, (type(None), EverythingEquals) 804 - ): 801 + if "date" in name and not isinstance(value, type(None) | EverythingEquals): 805 802 return_value = value 806 803 if isinstance(value, str): 807 - return_value = datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%fZ") 804 + return_value = datetime.strptime( 805 + value, "%Y-%m-%dT%H:%M:%S.%fZ" 806 + ).astimezone() 808 807 return ( 809 808 return_value.astimezone(tz=UTC) 810 809 if isinstance(return_value, datetime) ··· 824 823 def get_facility_representation(self, facility): 825 824 if facility is None: 826 825 return facility 827 - else: 828 - return { 829 - "id": str(facility.external_id), 830 - "name": facility.name, 831 - "facility_type": { 832 - "id": facility.facility_type, 833 - "name": facility.get_facility_type_display(), 834 - }, 835 - **self.get_local_body_district_state_representation(facility), 836 - } 826 + return { 827 + "id": str(facility.external_id), 828 + "name": facility.name, 829 + "facility_type": { 830 + "id": facility.facility_type, 831 + "name": facility.get_facility_type_display(), 832 + }, 833 + **self.get_local_body_district_state_representation(facility), 834 + } 837 835 838 836 def create_patient_note( 839 837 self, patient=None, note="Patient is doing find", created_by=None, **kwargs ··· 843 841 "note": note, 844 842 } 845 843 data.update(kwargs) 846 - patientId = patient.external_id 844 + patient_id = patient.external_id 847 845 848 846 refresh_token = RefreshToken.for_user(created_by) 849 847 self.client.credentials( 850 848 HTTP_AUTHORIZATION=f"Bearer {refresh_token.access_token}" 851 849 ) 852 850 853 - self.client.post(f"/api/v1/patient/{patientId}/notes/", data=data) 851 + self.client.post(f"/api/v1/patient/{patient_id}/notes/", data=data) 854 852 855 853 @classmethod 856 854 def create_patient_shift(
+2 -2
care/utils/ulid/models.py
··· 35 35 return None 36 36 try: 37 37 return ULID.parse(value) 38 - except (AttributeError, ValueError): 38 + except (AttributeError, ValueError) as e: 39 39 raise exceptions.ValidationError( 40 40 self.error_messages["invalid"], 41 41 code="invalid", 42 42 params={"value": value}, 43 - ) 43 + ) from e
+51 -23
care/utils/ulid/ulid.py
··· 1 1 from typing import Self 2 2 from uuid import UUID 3 3 4 - from ulid import ULID as BaseULID 4 + from ulid import ULID as BaseULID # noqa: N811 5 + 6 + UUID_LEN_WITHOUT_HYPHENS = 32 7 + UUID_LEN_WITH_HYPHENS = 36 8 + ULID_STR_LEN = 26 9 + ULID_BYTES_LEN = 16 10 + TIMESTAMP_STR_LEN = 10 5 11 6 12 7 13 class ULID(BaseULID): 8 14 @classmethod 9 15 def parse(cls, value) -> Self: 10 16 if isinstance(value, BaseULID): 11 - return value 17 + return cls.parse_baseulid(value) 12 18 if isinstance(value, UUID): 13 - return cls.from_uuid(value) 19 + return cls.parse_uuid(value) 14 20 if isinstance(value, str): 15 - len_value = len(value) 16 - if len_value == 32 or len_value == 36: 17 - return cls.from_uuid(UUID(value)) 18 - if len_value == 26: 19 - return cls.from_str(value) 20 - if len_value == 16: 21 - return cls.from_bytes(value) 22 - if len_value == 10: 23 - return cls.from_timestamp(int(value)) 24 - raise ValueError( 25 - f"Cannot create ULID from string of length {len_value}" 26 - ) 27 - if isinstance(value, (int, float)): 28 - return cls.from_int(int(value)) 29 - if isinstance(value, (bytes, bytearray)): 30 - return cls.from_bytes(value) 21 + return cls.parse_str(value) 22 + if isinstance(value, int | float): 23 + return cls.parse_int_float(value) 24 + if isinstance(value, bytes | bytearray): 25 + return cls.parse_bytes(value) 31 26 if isinstance(value, memoryview): 32 - return cls.from_bytes(value.tobytes()) 33 - raise ValueError( 34 - f"Cannot create ULID from type {value.__class__.__name__}" 35 - ) 27 + return cls.parse_memoryview(value) 28 + msg = f"Cannot create ULID from type {value.__class__.__name__}" 29 + raise ValueError(msg) 30 + 31 + @classmethod 32 + def parse_ulid(cls, value: BaseULID) -> Self: 33 + return value 34 + 35 + @classmethod 36 + def parse_uuid(cls, value: UUID) -> Self: 37 + return cls.from_uuid(value) 38 + 39 + @classmethod 40 + def parse_str(cls, value: str) -> Self: 41 + len_value = len(value) 42 + if len_value in (UUID_LEN_WITHOUT_HYPHENS, UUID_LEN_WITH_HYPHENS): 43 + return cls.from_uuid(UUID(value)) 44 + if len_value == ULID_STR_LEN: 45 + return cls.from_str(value) 46 + if len_value == ULID_BYTES_LEN: 47 + return cls.from_bytes(value.encode()) 48 + if len_value == TIMESTAMP_STR_LEN: 49 + return cls.from_timestamp(int(value)) 50 + msg = f"Cannot create ULID from string of length {len_value}" 51 + raise ValueError(msg) 52 + 53 + @classmethod 54 + def parse_int_float(cls, value: int | float) -> Self: 55 + return cls.from_int(int(value)) 56 + 57 + @classmethod 58 + def parse_bytes(cls, value: bytes | bytearray) -> Self: 59 + return cls.from_bytes(value) 60 + 61 + @classmethod 62 + def parse_memoryview(cls, value: memoryview) -> Self: 63 + return cls.from_bytes(value.tobytes())
-12
care/utils/validation/integer_validation.py
··· 1 - from rest_framework.exceptions import ValidationError 2 - 3 - 4 - def check_integer(vals): 5 - if not isinstance(vals, list): 6 - vals = [vals] 7 - for i in range(len(vals)): 8 - try: 9 - vals[i] = int(vals[i]) 10 - except Exception: 11 - raise ValidationError({"value": "Integer Required"}) 12 - return vals
+1 -2
config/adminlogin.py
··· 12 12 request, "Too many login attempts, please try again in 20 minutes" 13 13 ) 14 14 return redirect(reverse("admin:index")) 15 - else: 16 - return login_func(request, **kwargs) 15 + return login_func(request, **kwargs) 17 16 18 17 return admin_login
+1 -4
config/api_router.py
··· 104 104 from care.users.api.viewsets.users import UserViewSet 105 105 from care.users.api.viewsets.userskill import UserSkillViewSet 106 106 107 - if settings.DEBUG: 108 - router = DefaultRouter() 109 - else: 110 - router = SimpleRouter() 107 + router = DefaultRouter() if settings.DEBUG else SimpleRouter() 111 108 112 109 router.register("users", UserViewSet, basename="users") 113 110 user_nested_router = NestedSimpleRouter(router, r"users", lookup="users")
-41
config/asgi.py
··· 1 - """ 2 - ASGI config for care project. 3 - 4 - It exposes the ASGI callable as a module-level variable named ``application``. 5 - 6 - For more information on this file, see 7 - https://docs.djangoproject.com/en/dev/howto/deployment/asgi/ 8 - 9 - """ 10 - 11 - import os 12 - import sys 13 - from pathlib import Path 14 - 15 - from django.core.asgi import get_asgi_application 16 - 17 - # This allows easy placement of apps within the interior 18 - # care directory. 19 - BASE_DIR = Path(__file__).resolve(strict=True).parent.parent 20 - sys.path.append(str(BASE_DIR / "care")) 21 - 22 - # If DJANGO_SETTINGS_MODULE is unset, default to the local settings 23 - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") 24 - 25 - # This application object is used by any ASGI server configured to use this file. 26 - django_application = get_asgi_application() 27 - # Apply ASGI middleware here. 28 - # from helloworld.asgi import HelloWorldApplication 29 - # application = HelloWorldApplication(application) 30 - 31 - # Import websocket application here, so apps from django_application are loaded first 32 - from config.websocket import websocket_application # noqa isort:skip 33 - 34 - 35 - async def application(scope, receive, send): 36 - if scope["type"] == "http": 37 - await django_application(scope, receive, send) 38 - elif scope["type"] == "websocket": 39 - await websocket_application(scope, receive, send) 40 - else: 41 - raise NotImplementedError(f"Unknown scope type {scope['type']}")
+2 -3
config/auth_views.py
··· 73 73 74 74 @classmethod 75 75 def get_token(cls, user): 76 - raise NotImplementedError( 77 - "Must implement `get_token` method for `TokenObtainSerializer` subclasses" 78 - ) 76 + msg = "Must implement `get_token` method for `TokenObtainSerializer` subclasses" 77 + raise NotImplementedError(msg) 79 78 80 79 81 80 class TokenRefreshSerializer(serializers.Serializer):
+13 -8
config/authentication.py
··· 1 1 import json 2 2 import logging 3 - from datetime import datetime 4 3 5 4 import jwt 6 5 import requests ··· 8 7 from django.contrib.auth.models import AnonymousUser 9 8 from django.core.cache import cache 10 9 from django.core.exceptions import ValidationError 10 + from django.utils import timezone 11 11 from django.utils.translation import gettext_lazy as _ 12 12 from drf_spectacular.extensions import OpenApiAuthenticationExtension 13 13 from drf_spectacular.plumbing import build_bearer_security_scheme_object ··· 22 22 from care.users.models import User 23 23 24 24 logger = logging.getLogger(__name__) 25 + 26 + 27 + OPENID_REQUEST_TIMEOUT = 5 25 28 26 29 27 30 def jwk_response_cache_key(url: str) -> str: ··· 77 80 def get_public_key(self, url): 78 81 public_key_json = cache.get(jwk_response_cache_key(url)) 79 82 if not public_key_json: 80 - res = requests.get(url) 83 + res = requests.get(url, timeout=OPENID_REQUEST_TIMEOUT) 81 84 res.raise_for_status() 82 85 public_key_json = res.json() 83 86 cache.set(jwk_response_cache_key(url), public_key_json, timeout=60 * 5) ··· 136 139 # Assume the header does not contain a JSON web token 137 140 return None 138 141 139 - if len(parts) != 2: 142 + if len(parts) != 2: # noqa: PLR2004 140 143 raise AuthenticationFailed( 141 144 _("Authorization header must contain two space-delimited values"), 142 145 code="bad_authorization_header", ··· 190 193 user_type=User.TYPE_VALUE_MAP["Nurse"], 191 194 verified=True, 192 195 asset=asset_obj, 193 - date_of_birth=datetime.now().date(), 196 + date_of_birth=timezone.now().date(), 194 197 ) 195 198 asset_user.save() 196 199 return asset_user ··· 198 201 199 202 class ABDMAuthentication(JWTAuthentication): 200 203 def open_id_authenticate(self, url, token): 201 - public_key = requests.get(url) 204 + public_key = requests.get(url, timeout=OPENID_REQUEST_TIMEOUT) 202 205 jwk = public_key.json()["keys"][0] 203 206 public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(jwk)) 204 207 return jwt.decode( ··· 227 230 return self.open_id_authenticate(url, token) 228 231 except Exception as e: 229 232 logger.info(e, "Token: ", token) 230 - raise InvalidToken({"detail": f"Invalid Authorization token: {e}"}) 233 + raise InvalidToken({"detail": "Invalid Authorization token"}) from e 231 234 232 235 def get_user(self, validated_token): 233 236 user = User.objects.filter(username=settings.ABDM_USERNAME).first() ··· 241 244 phone_number="917777777777", 242 245 user_type=User.TYPE_VALUE_MAP["Volunteer"], 243 246 verified=True, 244 - date_of_birth=datetime.now().date(), 247 + date_of_birth=timezone.now().date(), 245 248 ) 246 249 user.save() 247 250 return user ··· 253 256 254 257 def get_security_definition(self, auto_schema): 255 258 return build_bearer_security_scheme_object( 256 - header_name="Authorization", token_prefix="Bearer", bearer_format="JWT" 259 + header_name="Authorization", 260 + token_prefix="Bearer", 261 + bearer_format="JWT", 257 262 ) 258 263 259 264
+2 -2
config/health_views.py
··· 9 9 10 10 11 11 class MiddlewareAuthenticationVerifyView(APIView): 12 - authentication_classes = [MiddlewareAuthentication] 12 + authentication_classes = (MiddlewareAuthentication,) 13 13 14 14 def get(self, request): 15 15 return Response(UserBaseMinimumSerializer(request.user).data) 16 16 17 17 18 18 class MiddlewareAssetAuthenticationVerifyView(APIView): 19 - authentication_classes = [MiddlewareAssetAuthentication] 19 + authentication_classes = (MiddlewareAssetAuthentication,) 20 20 21 21 def get(self, request): 22 22 return Response(UserBaseMinimumSerializer(request.user).data)
+1 -1
config/middleware.py config/middlewares.py
··· 11 11 request.start_time = time.time() 12 12 response = self.get_response(request) 13 13 duration = time.time() - request.start_time 14 - self.logger.info(f"Request to {request.path} took {duration:.4f} seconds") 14 + self.logger.info("Request to %s took %.4f seconds", request.path, duration) 15 15 return response
+17 -16
config/ratelimit.py
··· 2 2 from django.conf import settings 3 3 from django_ratelimit.core import is_ratelimited 4 4 5 + VALIDATE_CAPTCHA_REQUEST_TIMEOUT = 5 5 6 6 - def GETKEY(group, request): 7 + 8 + def get_ratelimit_key(group, request): 7 9 return "ratelimit" 8 10 9 11 ··· 16 18 "response": recaptcha_response, 17 19 } 18 20 captcha_response = requests.post( 19 - "https://www.google.com/recaptcha/api/siteverify", data=values 21 + "https://www.google.com/recaptcha/api/siteverify", 22 + data=values, 23 + timeout=VALIDATE_CAPTCHA_REQUEST_TIMEOUT, 20 24 ) 21 25 result = captcha_response.json() 22 26 23 - if result["success"] is True: 24 - return True 25 - return False 27 + return bool(result["success"]) 26 28 27 29 28 30 # refer https://django-ratelimit.readthedocs.io/en/stable/rates.html for rate 29 31 def ratelimit( 30 - request, group="", keys=[None], rate=settings.DJANGO_RATE_LIMIT, increment=True 32 + request, group="", keys=None, rate=settings.DJANGO_RATE_LIMIT, increment=True 31 33 ): 34 + if keys is None: 35 + keys = [None] 32 36 if settings.DISABLE_RATELIMIT: 33 37 return False 34 38 35 39 checkcaptcha = False 36 40 for key in keys: 37 41 if key == "ip": 38 - group = group 39 - key = "ip" 42 + _group = group 43 + _key = "ip" 40 44 else: 41 - group = group + f"-{key}" 42 - key = GETKEY 45 + _group = group + f"-{key}" 46 + _key = get_ratelimit_key 43 47 if is_ratelimited( 44 48 request, 45 - group=group, 46 - key=key, 49 + group=_group, 50 + key=_key, 47 51 rate=rate, 48 52 increment=increment, 49 53 ): 50 54 checkcaptcha = True 51 55 52 56 if checkcaptcha: 53 - if not validatecaptcha(request): 54 - return True 55 - else: 56 - return False 57 + return not validatecaptcha(request) 57 58 58 59 return False 59 60
-25
config/serializers.py
··· 1 - from rest_framework import serializers 2 - 3 - 4 - class ChoiceField(serializers.ChoiceField): 5 - def to_representation(self, obj): 6 - try: 7 - return self._choices[obj] 8 - except KeyError: 9 - key_type = type(list(self.choices.keys())[0]) 10 - key = key_type(obj) 11 - return self._choices[key] 12 - 13 - def to_internal_value(self, data): 14 - if isinstance(data, str) and data not in self.choice_strings_to_values: 15 - choice_name_map = {v: k for k, v in self._choices.items()} 16 - data = choice_name_map.get(data) 17 - return super(ChoiceField, self).to_internal_value(data) 18 - 19 - 20 - class MultipleChoiceField(serializers.MultipleChoiceField): 21 - def to_representation(self, value): 22 - return super(MultipleChoiceField, self).to_representation(value) 23 - 24 - def to_internal_value(self, data): 25 - return super(MultipleChoiceField, self).to_internal_value(data)
+11 -8
config/settings/base.py
··· 2 2 Base settings to build other settings files upon. 3 3 """ 4 4 5 + import logging 5 6 from datetime import datetime, timedelta 6 7 from pathlib import Path 7 8 ··· 15 16 16 17 from care.utils.csp import config as csp_config 17 18 from plug_config import manager 19 + 20 + logger = logging.getLogger(__name__) 18 21 19 22 BASE_DIR = Path(__file__).resolve(strict=True).parent.parent.parent 20 23 APPS_DIR = BASE_DIR / "care" ··· 197 200 198 201 # add RequestTimeLoggingMiddleware based on the environment variable 199 202 if env.bool("ENABLE_REQUEST_TIME_LOGGING", default=False): 200 - MIDDLEWARE.insert(0, "config.middleware.RequestTimeLoggingMiddleware") 203 + MIDDLEWARE.insert(0, "config.middlewares.RequestTimeLoggingMiddleware") 201 204 202 205 # STATIC 203 206 # ------------------------------------------------------------------------------ ··· 275 278 CSRF_TRUSTED_ORIGINS = env.json("CSRF_TRUSTED_ORIGINS", default=[]) 276 279 277 280 # https://github.com/adamchainz/django-cors-headers#cors_allowed_origin_regexes-sequencestr--patternstr 278 - # CORS_URLS_REGEX = r"^/api/.*$" 281 + # CORS_URLS_REGEX = r"^/api/.*$" # noqa: ERA001 279 282 280 283 # EMAIL 281 284 # ------------------------------------------------------------------------------ ··· 303 306 # https://docs.djangoproject.com/en/dev/ref/settings/#server-email 304 307 # SERVER_EMAIL = env("DJANGO_SERVER_EMAIL", default=DEFAULT_FROM_EMAIL) # noqa F405 305 308 # https://docs.djangoproject.com/en/dev/ref/settings/#admins 306 - # ADMINS = [("""👪""", "admin@ohc.network")] 309 + # ADMINS = [("""👪""", "admin@ohc.network")] # noqa: ERA001 307 310 # https://docs.djangoproject.com/en/dev/ref/settings/#managers 308 - # MANAGERS = ADMINS 311 + # MANAGERS = ADMINS # noqa: ERA001 309 312 310 313 # Django Admin URL. 311 314 ADMIN_URL = env("DJANGO_ADMIN_URL", default="admin") ··· 379 382 "TITLE": "Care API", 380 383 "DESCRIPTION": "Documentation of API endpoints of Care ", 381 384 "VERSION": "1.0.0", 382 - # "SERVE_PERMISSIONS": ["rest_framework.permissions.IsAdminUser"], 383 385 } 384 386 385 387 # Simple JWT (JWT Authentication) ··· 542 544 BUCKET_HAS_FINE_ACL = env.bool("BUCKET_HAS_FINE_ACL", default=False) 543 545 544 546 if BUCKET_PROVIDER not in csp_config.CSProvider.__members__: 545 - print(f"Warning Invalid CSP Found! {BUCKET_PROVIDER}") 547 + logger.error("invalid CSP found: %s", BUCKET_PROVIDER) 546 548 547 549 FILE_UPLOAD_BUCKET = env("FILE_UPLOAD_BUCKET", default="") 548 550 FILE_UPLOAD_REGION = env("FILE_UPLOAD_REGION", default=BUCKET_REGION) ··· 615 617 # for setting the shifting mode 616 618 PEACETIME_MODE = env.bool("PEACETIME_MODE", default=True) 617 619 620 + # we are making this tz aware in the app so no need to make it aware here 618 621 MIN_ENCOUNTER_DATE = env( 619 622 "MIN_ENCOUNTER_DATE", 620 - cast=lambda d: datetime.strptime(d, "%Y-%m-%d"), 621 - default=datetime(2020, 1, 1), 623 + cast=lambda d: datetime.strptime(d, "%Y-%m-%d"), # noqa: DTZ007 624 + default=datetime(2020, 1, 1), # noqa: DTZ001 622 625 ) 623 626 624 627 # for exporting csv
+1 -1
config/settings/local.py
··· 14 14 # WhiteNoise 15 15 # ------------------------------------------------------------------------------ 16 16 # http://whitenoise.evans.io/en/latest/django.html#using-whitenoise-in-development 17 - INSTALLED_APPS = ["whitenoise.runserver_nostatic"] + INSTALLED_APPS 17 + INSTALLED_APPS = ["whitenoise.runserver_nostatic", *INSTALLED_APPS] 18 18 19 19 # django-silk 20 20 # ------------------------------------------------------------------------------
+2 -3
config/urls.py
··· 72 72 path("health/", include("healthy_django.urls", namespace="healthy_django")), 73 73 # OpenID Connect 74 74 path(".well-known/jwks.json", PublicJWKsView.as_view(), name="jwks-json"), 75 - # TODO: Remove the config url as its not a standard implementation 76 - path(".well-known/openid-configuration", PublicJWKsView.as_view()), 77 - ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 75 + *static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT), 76 + ] 78 77 79 78 if settings.ENABLE_ABDM: 80 79 urlpatterns += abdm_urlpatterns
-2
config/utils.py
··· 1 - def get_psql_search_tokens(text, operator="&"): 2 - return f" {operator} ".join([f"{word}:*" for word in text.strip().split(" ")])
-69
config/validators.py
··· 1 - import re 2 - 3 - from django.core.exceptions import ValidationError 4 - from django.core.validators import RegexValidator 5 - from django.utils.translation import gettext_lazy as _ 6 - 7 - 8 - class NumberValidator: 9 - def validate(self, password, user=None): 10 - if not re.findall(r"\d", password): 11 - raise ValidationError( 12 - _("The password must contain at least 1 digit, 0-9."), 13 - code="password_no_number", 14 - ) 15 - 16 - def get_help_text(self): 17 - return _("Your password must contain at least 1 digit, 0-9.") 18 - 19 - 20 - class UppercaseValidator: 21 - def validate(self, password, user=None): 22 - if not re.findall("[A-Z]", password): 23 - raise ValidationError( 24 - _("The password must contain at least 1 uppercase letter, A-Z."), 25 - code="password_no_upper", 26 - ) 27 - 28 - def get_help_text(self): 29 - return _("Your password must contain at least 1 uppercase letter, A-Z.") 30 - 31 - 32 - class LowercaseValidator: 33 - def validate(self, password, user=None): 34 - if not re.findall("[a-z]", password): 35 - raise ValidationError( 36 - _("The password must contain at least 1 lowercase letter, a-z."), 37 - code="password_no_lower", 38 - ) 39 - 40 - def get_help_text(self): 41 - return _("Your password must contain at least 1 lowercase letter, a-z.") 42 - 43 - 44 - class SymbolValidator: 45 - def validate(self, password, user=None): 46 - if not re.findall(r"[()[\]{}|\\`~!@#$%^&*_\-+=;:'\",<>./?]", password): 47 - raise ValidationError( 48 - _( 49 - "The password must contain at least 1 symbol: " 50 - + r"()[]{}|\`~!@#$%^&*_-+=;:'\",<>./?" 51 - ), 52 - code="password_no_symbol", 53 - ) 54 - 55 - def get_help_text(self): 56 - return _( 57 - "Your password must contain at least 1 symbol: " 58 - + r"()[]{}|\`~!@#$%^&*_-+=;:'\",<>./?" 59 - ) 60 - 61 - 62 - class MiddlewareDomainAddressValidator(RegexValidator): 63 - regex = r"^(?!https?:\/\/)[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*\.[a-zA-Z]{2,}$" 64 - code = "invalid_domain_name" 65 - message = _( 66 - "The domain name is invalid. " 67 - "It should not start with scheme and " 68 - "should not end with a trailing slash." 69 - )
+2 -2
config/views.py
··· 3 3 from django.shortcuts import render 4 4 5 5 6 - def app_version(_): 6 + def app_version(request): 7 7 return JsonResponse({"version": settings.APP_VERSION}) 8 8 9 9 ··· 11 11 return render(request, "pages/home.html") 12 12 13 13 14 - def ping(_): 14 + def ping(request): 15 15 return JsonResponse({"status": "OK"})
-13
config/websocket.py
··· 1 - async def websocket_application(scope, receive, send): 2 - while True: 3 - event = await receive() 4 - 5 - if event["type"] == "websocket.connect": 6 - await send({"type": "websocket.accept"}) 7 - 8 - if event["type"] == "websocket.disconnect": 9 - break 10 - 11 - if event["type"] == "websocket.receive": 12 - if event["text"] == "ping": 13 - await send({"type": "websocket.send", "text": "pong!"})
+3 -3
config/wsgi.py
··· 27 27 # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks 28 28 # if running multiple sites in the same mod_wsgi process. To fix this, use 29 29 # mod_wsgi daemon mode with each site in its own daemon process, or use 30 - # os.environ["DJANGO_SETTINGS_MODULE"] = "config.settings.production" 30 + # os.environ["DJANGO_SETTINGS_MODULE"] = "config.settings.production" # noqa: ERA001 31 31 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production") 32 32 33 33 # This application object is used by any WSGI server configured to use this ··· 35 35 # setting points here. 36 36 application = get_wsgi_application() 37 37 # Apply WSGI middleware here. 38 - # from helloworld.wsgi import HelloWorldApplication 39 - # application = HelloWorldApplication(application) 38 + # from helloworld.wsgi import HelloWorldApplication # noqa: ERA001 39 + # application = HelloWorldApplication(application) # noqa: ERA001
+1 -9
docs/conf.py
··· 10 10 # add these directories to sys.path here. If the directory is relative to the 11 11 # documentation root, use os.path.abspath to make it absolute, like shown here. 12 12 # 13 - # import os 14 - # import sys 15 - 16 - # import django 17 - # sys.path.insert(0, os.path.abspath('..')) 18 - # os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") 19 - # django.setup() 20 - 21 13 22 14 # -- Project information ----------------------------------------------------- 23 15 24 16 project = "Care" 25 - copyright = """2023, Open Healthcare Network""" 17 + copyright = """2023, Open Healthcare Network""" # noqa: A001 26 18 author = "ohcnetwork" 27 19 28 20
+4 -2
manage.py
··· 1 1 #!/usr/bin/env python 2 2 """Django's command-line utility for administrative tasks.""" 3 + 3 4 import os 4 5 import sys 5 6 ··· 19 20 try: 20 21 from django.core.management import execute_from_command_line 21 22 except ImportError as exc: 22 - raise ImportError( 23 + msg = ( 23 24 "Couldn't import Django. Are you sure it's installed and " 24 25 "available on your PYTHONPATH environment variable? Did you " 25 26 "forget to activate a virtual environment?" 26 - ) from exc 27 + ) 28 + raise ImportError(msg) from exc 27 29 28 30 execute_from_command_line(sys.argv) 29 31
-30
merge_production_dotenvs_in_dotenv.py
··· 1 - import os 2 - from collections.abc import Sequence 3 - 4 - ROOT_DIR_PATH = os.path.dirname(os.path.realpath(__file__)) 5 - PRODUCTION_DOTENVS_DIR_PATH = os.path.join(ROOT_DIR_PATH, ".envs", ".production") 6 - PRODUCTION_DOTENV_FILE_PATHS = [ 7 - os.path.join(PRODUCTION_DOTENVS_DIR_PATH, ".django"), 8 - os.path.join(PRODUCTION_DOTENVS_DIR_PATH, ".postgres"), 9 - ] 10 - DOTENV_FILE_PATH = os.path.join(ROOT_DIR_PATH, ".env") 11 - 12 - 13 - def merge( 14 - output_file_path: str, merged_file_paths: Sequence[str], append_linesep: bool = True 15 - ) -> None: 16 - with open(output_file_path, "w") as output_file: 17 - for merged_file_path in merged_file_paths: 18 - with open(merged_file_path) as merged_file: 19 - merged_file_content = merged_file.read() 20 - output_file.write(merged_file_content) 21 - if append_linesep: 22 - output_file.write(os.linesep) 23 - 24 - 25 - def main(): 26 - merge(DOTENV_FILE_PATH, PRODUCTION_DOTENV_FILE_PATHS) 27 - 28 - 29 - if __name__ == "__main__": 30 - main()
+38 -31
pyproject.toml
··· 21 21 22 22 [tool.ruff] 23 23 target-version = "py312" 24 - extend-exclude = ["*/migrations_old/*"] 24 + extend-exclude = ["*/migrations*/*", "care/abdm/*"] 25 25 include = ["*.py", "pyproject.toml"] 26 26 27 27 [tool.ruff.lint] ··· 34 34 "N", # pep8-naming 35 35 "UP", # pyupgrade 36 36 # "ANN", # flake8-annotations 37 - "S", # flake8-bandit 38 - "FBT", # flake8-boolean-trap 39 - "B", # flake8-bugbear 40 - "A", # flake8-builtins 41 - "COM", # flake8-commas 42 - "C4", # flake8-comprehensions 43 - "DTZ", # flake8-datetimez 44 - "T10", # flake8-debugger 45 - "DJ", # flake8-django 46 - "EM", # flake8-errmsg 47 - "ISC", # flake8-import-conventions 48 - "ICN", # flake8-import-order 49 - "LOG", # flake8-logging 50 - "G", # flake8-logging-format 51 - "INP", # flake8-no-pep420 52 - "PIE", # flake8-pie 53 - "T20", # flake8-print 54 - "Q", # flake8-quotes 55 - "RSE", # flake8-raise 56 - "RET", # flake8-return 57 - "SLF", # flake8-self 58 - "SIM", # flake8-simplify 59 - "TID", # flake8-tidy-imports 60 - "TCH", # flake8-todo 61 - "INT", # flake8-gettext 62 - "ARG", # flake8-unused-arguments 37 + "S", # flake8-bandit 38 + "FBT", # flake8-boolean-trap 39 + "B", # flake8-bugbear 40 + "A", # flake8-builtins 41 + "COM", # flake8-commas 42 + "C4", # flake8-comprehensions 43 + "DTZ", # flake8-datetimez 44 + "T10", # flake8-debugger 45 + "DJ", # flake8-django 46 + "EM", # flake8-errmsg 47 + "ISC", # flake8-import-conventions 48 + "ICN", # flake8-import-order 49 + "LOG", # flake8-logging 50 + "G", # flake8-logging-format 51 + "INP", # flake8-no-pep420 52 + "PIE", # flake8-pie 53 + "T20", # flake8-print 54 + "Q", # flake8-quotes 55 + "RSE", # flake8-raise 56 + "RET", # flake8-return 57 + "SLF", # flake8-self 58 + "SIM", # flake8-simplify 59 + "TID", # flake8-tidy-imports 60 + "TCH", # flake8-todo 61 + "INT", # flake8-gettext 62 + # "ARG", # flake8-unused-arguments 63 63 "PTH", # flake8-use-pathlib 64 - "TD", # flake8-todo 64 + # "TD", # flake8-todo # disabling this for now 65 65 "ERA", # eradicate 66 66 "PL", # pylint 67 67 "FURB", # refurb ··· 79 79 "DJ001", # django-nullable-model-string-field 80 80 "ISC001", # conflicts with format 81 81 "COM812", # conflicts with format 82 + "RUF012", # Too hard 83 + "FBT001", # why not! 84 + "S106", 85 + "S105", 82 86 ] 83 87 84 88 ··· 86 90 line-ending = "lf" 87 91 88 92 93 + [tool.ruff.lint.per-file-ignores] 94 + "**/__init__.py" = ["E402", "F401"] # for imports 95 + "**/tests/**" = ["DTZ001"] 96 + 89 97 [tool.ruff.lint.flake8-builtins] 90 98 builtins-ignorelist = ["id", "list", "filter"] 91 99 ··· 94 102 docstring-quotes = "double" 95 103 96 104 97 - [tool.ruff.lint.per-file-ignores] 98 - "**/__init__.py" = ["E402", "F401"] # for imports 99 - "**/tests/**" = ["DTZ001"] 105 + [tool.ruff.lint.flake8-unused-arguments] 106 + ignore-variadic-names = true