diff --git a/.gitignore b/.gitignore index 32c4b3cf62..d61f63f4df 100644 --- a/.gitignore +++ b/.gitignore @@ -23,15 +23,11 @@ build *idea* -EquiTrack/static/* -EquiTrack/migration_errors.txt static/* media *.dump dump.rdb -.vagrant -vagrant_ansible_inventory_default *.cpg *.dbf @@ -42,7 +38,6 @@ packer_cache *.iso *.box -Vagrantfile saml/FederationMetadata.xml *.tar diff --git a/MANIFEST.in b/MANIFEST.in index 822d0c388b..2a2dd3771b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,8 +7,6 @@ include *.py recursive-include docs * recursive-include src/etools * -recursive-include src/requirements *.txt - exclude src/etools/settings/custom.*.py exclude .coverage exclude .coveragerc @@ -34,9 +32,7 @@ exclude docker-compose_v2.yml exclude docs exclude runtests.sh exclude screenshots -exclude src/requirements/input exclude wait-for-it.sh -exclude requirements.txt exclude refactor.sh exclude setup.in diff --git a/Pipfile b/Pipfile index eb950d28f5..6be9caee44 100644 --- a/Pipfile +++ b/Pipfile @@ -23,24 +23,24 @@ carto = "==1.6" celery = "==4.3" dj-database-url = "==0.5" dj-static = "==0.0.6" -Django = "==2.2.3" +Django = "==2.2.4" django-appconf = "==1.0.3" django_celery_beat = "==1.5" django-celery-email = "==2.0.2" django_celery_results = "==1.1.2" django-contrib-comments = "==1.9.1" django-cors-headers = "==3.0.2" -django-debug-toolbar = "==1.11" -django-extensions = "==2.0" +django-debug-toolbar = "==2.0" +django-extensions = "==2.2.1" django-easy-pdf = "==0.1.1" -django-filter = "==2.1" +django-filter = "==2.2" django-fsm = "==2.6.1" django-import-export = "==1.2" django-js-asset = "==1.2.2" django-leaflet = "==0.24" -django-logentry-admin = "==1.0.4" -django-model-utils = "==3.1.2" -django-ordered-model = "==3.1.1" +django-logentry-admin = "==1.0.5" +django-model-utils = "==3.2" +django-ordered-model = "==3.3" django-post_office = "==3.2.1" django-redis-cache = "==2.0" django-rest-swagger = "==2.2" @@ -53,7 +53,7 @@ djangorestframework-gis = "==0.14" djangorestframework-jwt = "==1.11.0" djangorestframework-recursive = "==0.1.2" djangorestframework-xml = "==1.4" -djangorestframework = "==3.9.4" +djangorestframework = "==3.10.1" drf-nested-routers = "==0.91" drf-querystringfilter = "==1.0.0" etools-validator = "==0.3.2" @@ -63,12 +63,12 @@ gunicorn = "==19.9" newrelic = "==4.20.1.121" Pillow = "==5.4.1" psycopg2-binary = "==2.8.3" -sentry-sdk = "==0.9.5" +sentry-sdk = "==0.10.2" requests = "==2.22" social-auth-app-django = "==3.1" social-auth-core = {extras = ["azuread"],version = "==3.2"} tenant-schemas-celery = "==0.2.1" -unicef_attachments = "==0.5.1" +unicef_attachments = "==0.5.2" unicef-djangolib = "==0.5.3" unicef-locations = "==1.7" unicef_notification = "==0.2.1" @@ -77,6 +77,7 @@ unicef_snapshot = "==0.2.3" unicef-rest-export = "==0.5.3" xhtml2pdf = "==0.2.3" unicef-vision = "*" +docutils = "==0.15" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 5d64ed3027..8eea242965 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "3af1c5bbab618934db17912bab1694a7379b7a20a65e2fc72c4842e5f6afa991" + "sha256": "b483629161fdc06780a5628ced505836398b8829c6a7566966401ac1866d907d" }, "pipfile-spec": 6, "requires": { - "python_version": "3.6" + "python_version": "3.7" }, "sources": [ { @@ -25,10 +25,10 @@ }, "amqp": { "hashes": [ - "sha256:aa4409446139676943a2eaa27d5f58caf750f4ca5a89f888c452afd86be6a67d", - "sha256:cbb6f87d53cac612a594f982b717cc1c54c6a1e17943a0a0d32dc6cc9e2120c8" + "sha256:19a917e260178b8d410122712bac69cb3e6db010d68f6101e7307508aded5e68", + "sha256:19d851b879a471fcfdcf01df9936cff924f422baa77653289f7095dedd5fb26a" ], - "version": "==2.5.0" + "version": "==2.5.1" }, "asn1crypto": { "hashes": [ @@ -81,10 +81,10 @@ }, "azure-datalake-store": { "hashes": [ - "sha256:10696c76716762739e5e77f25c1268314786cf8e999d6d8366af7245c82fc660", - "sha256:5bc560ee108a1824e03ab926fbe5302b84c1e8d5742fa3a31e4cc0733ab58543" + "sha256:55bcbec99a35a52ea291518dadfd94c3b645e66d05ce927228289b93979ad3a5", + "sha256:d8e72b60b52b7875c23e386af6a90a2db7f580f75627aa5ffbda18e186180d39" ], - "version": "==0.0.46" + "version": "==0.0.47" }, "azure-eventgrid": { "hashes": [ @@ -630,9 +630,10 @@ }, "billiard": { "hashes": [ - "sha256:756bf323f250db8bf88462cd042c992ba60d8f5e07fc5636c24ba7d6f4261d84" + "sha256:01afcb4e7c4fd6480940cfbd4d9edc19d7a7509d6ada533984d0d0f49901ec82", + "sha256:b8809c74f648dfe69b973c8e660bcec00603758c9db8ba89d7719f88d5f01f26" ], - "version": "==3.6.0.0" + "version": "==3.6.1.0" }, "carto": { "hashes": [ @@ -762,11 +763,11 @@ }, "django": { "hashes": [ - "sha256:4d23f61b26892bac785f07401bc38cbf8fa4cec993f400e9cd9ddf28fd51c0ea", - "sha256:6e974d4b57e3b29e4882b244d40171d6a75202ab8d2402b8e8adbd182e25cf0c" + "sha256:16a5d54411599780ac9dfe3b9b38f90f785c51259a584e0b24b6f14a7f69aae8", + "sha256:9a2f98211ab474c710fcdad29c82f30fc14ce9917c7a70c3682162a624de4035" ], "index": "pypi", - "version": "==2.2.3" + "version": "==2.2.4" }, "django-appconf": { "hashes": [ @@ -778,9 +779,9 @@ }, "django-autocomplete-light": { "hashes": [ - "sha256:69a34f97a5b8ab3aac6baaea36f627ef09eaa5791c60f881b2000b1b813268d0" + "sha256:29ce2626a11eab2333e5aa9f95166a6d4400f11b5a05e8f23fa77017b1a9089a" ], - "version": "==3.3.5" + "version": "==3.4.1" }, "django-celery-beat": { "hashes": [ @@ -824,11 +825,11 @@ }, "django-debug-toolbar": { "hashes": [ - "sha256:89d75b60c65db363fb24688d977e5fbf0e73386c67acf562d278402a10fc3736", - "sha256:c2b0134119a624f4ac9398b44f8e28a01c7686ac350a12a74793f3dd57a9eea0" + "sha256:17c53cd6bf4e7d69902aedf9a1d26c5d3b7369b54c5718744704f27b5a72f35d", + "sha256:9a23ada2e43cd989195db3c18710b5d7451134a0d48127ab64c1d2ad81700342" ], "index": "pypi", - "version": "==1.11" + "version": "==2.0" }, "django-easy-pdf": { "hashes": [ @@ -839,19 +840,19 @@ }, "django-extensions": { "hashes": [ - "sha256:308e4aa61b6accc249c67a0fb99daef6f8b233179dd50a52f408fae4f58b71ee", - "sha256:7c0db3b1a249bcfdd50c4314a617f8fb0aec979e2c01466e68ec0d60337313a9" + "sha256:4aafdb865104eaa5d681b9976b36c52c9d441be89b7d782e40808f1c5c0c8f93", + "sha256:8a2552fdeb222b23895ef52cdc28fc56efba976f6da07ca92937f6f5e626e345" ], "index": "pypi", - "version": "==2.0" + "version": "==2.2.1" }, "django-filter": { "hashes": [ - "sha256:3dafb7d2810790498895c22a1f31b2375795910680ac9c1432821cbedb1e176d", - "sha256:a3014de317bef0cd43075a0f08dfa1d319a7ccc5733c3901fb860da70b0dda68" + "sha256:558c727bce3ffa89c4a7a0b13bc8976745d63e5fd576b3a9a851650ef11c401b", + "sha256:c3deb57f0dd7ff94d7dce52a047516822013e2b441bed472b722a317658cfd14" ], "index": "pypi", - "version": "==2.1" + "version": "==2.2" }, "django-fsm": { "hashes": [ @@ -886,18 +887,18 @@ }, "django-logentry-admin": { "hashes": [ - "sha256:0033fa146b5c3d1195a1d306e5b665a22b901450e55715f3dec50c2b4076f8f6" + "sha256:e47e94f7778be85eae91f1f1bc8ac56475f38d297f800df3f7afacd780a9ff24" ], "index": "pypi", - "version": "==1.0.4" + "version": "==1.0.5" }, "django-model-utils": { "hashes": [ - "sha256:2c057f3bf0859aba27f04389f0cedd2d48f8c9b3848acb86fd9970794e58f477", - "sha256:8cd377744aa45f9f131d652ec460c57d1aaa88d3e9b586c8e27eb709341b9084" + "sha256:3f130a262e45d73e0950d2be76af4bf4ee86804dd60e5f90afc5cd948fcfe760", + "sha256:682f58c1de330cedcda58cc85d5232c5b47a9e2cb67bef4541fb43fdaeb18e96" ], "index": "pypi", - "version": "==3.1.2" + "version": "==3.2.0" }, "django-mptt": { "hashes": [ @@ -908,10 +909,11 @@ }, "django-ordered-model": { "hashes": [ - "sha256:14aeacca5a4d41de92b89432a7665b6be5ef254e72418cbeb32258348ad05352" + "sha256:0931f498008f91a00a32c4e0ae08a662ef608a1092bf6e6ec9af9b1a83f08acf", + "sha256:abf0d963f7e607a994baf6bc300e50af647b3d243c3e592c6cc8f8b924b6d427" ], "index": "pypi", - "version": "==3.1.1" + "version": "==3.3" }, "django-post-office": { "hashes": [ @@ -971,11 +973,11 @@ }, "djangorestframework": { "hashes": [ - "sha256:376f4b50340a46c15ae15ddd0c853085f4e66058f97e4dbe7d43ed62f5e60651", - "sha256:c12869cfd83c33d579b17b3cb28a2ae7322a53c3ce85580c2a2ebe4e3f56c4fb" + "sha256:1ca4a5599a5ec31f3d6238a687fcc1dd4c41b1d90edab9ad398fcbf87872b7ba", + "sha256:c3c5edfdbc5dd33f9121bb84305bfd603d2c791f20cff9782772f44a7684a4e4" ], "index": "pypi", - "version": "==3.9.4" + "version": "==3.10.1" }, "djangorestframework-csv": { "hashes": [ @@ -1016,6 +1018,14 @@ "index": "pypi", "version": "==1.4" }, + "docutils": { + "hashes": [ + "sha256:54a349c622ff31c91cbec43b0b512f113b5b24daf00e2ea530bb1bd9aac14849", + "sha256:d2ddba74835cb090a1b627d3de4e7835c628d07ee461f7b4480f51af2fe4d448" + ], + "index": "pypi", + "version": "==0.15" + }, "drf-nested-routers": { "hashes": [ "sha256:46e5c3abc15c782cafafd7d75028e8f9121bbc6228e3599bbb48a3daa4585034", @@ -1086,6 +1096,13 @@ ], "version": "==2.8" }, + "importlib-metadata": { + "hashes": [ + "sha256:9ff1b1c5a354142de080b8a4e9803e5d0d59283c93aed808617c787d16768375", + "sha256:b7143592e374e50584564794fcb8aaf00a23025f9db866627f89a21491847a8d" + ], + "version": "==0.20" + }, "isodate": { "hashes": [ "sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8", @@ -1122,39 +1139,37 @@ }, "kombu": { "hashes": [ - "sha256:55b71d3785def3470a16217fe0780f9e6f95e61bf9ad39ef8dce0177224eab77", - "sha256:eb365ea795cd7e629ba2f1f398e0c3ba354b91ef4de225ffdf6ab45fdfc7d581" + "sha256:55274dc75eb3c3994538b0973a0fadddb236b698a4bc135b8aa4981e0a710b8f", + "sha256:e5f0312dfb9011bebbf528ccaf118a6c2b5c3b8244451f08381fb23e7715809b" ], - "version": "==4.6.3" + "version": "==4.6.4" }, "lxml": { "hashes": [ - "sha256:06c7616601430aa140a69f97e3116308fffe0848f543b639a5ec2e8920ae72fd", - "sha256:177202792f9842374a8077735c69c41a4282183f7851443d2beb8ee310720819", - "sha256:19317ad721ceb9e39847d11131903931e2794e447d4751ebb0d9236f1b349ff2", - "sha256:36d206e62f3e5dbaafd4ec692b67157e271f5da7fd925fda8515da675eace50d", - "sha256:387115b066c797c85f9861a9613abf50046a15aac16759bc92d04f94acfad082", - "sha256:3ce1c49d4b4a7bc75fb12acb3a6247bb7a91fe420542e6d671ba9187d12a12c2", - "sha256:4d2a5a7d6b0dbb8c37dab66a8ce09a8761409c044017721c21718659fa3365a1", - "sha256:58d0a1b33364d1253a88d18df6c0b2676a1746d27c969dc9e32d143a3701dda5", - "sha256:62a651c618b846b88fdcae0533ec23f185bb322d6c1845733f3123e8980c1d1b", - "sha256:69ff21064e7debc9b1b1e2eee8c2d686d042d4257186d70b338206a80c5bc5ea", - "sha256:7060453eba9ba59d821625c6af6a266bd68277dce6577f754d1eb9116c094266", - "sha256:7d26b36a9c4bce53b9cfe42e67849ae3c5c23558bc08363e53ffd6d94f4ff4d2", - "sha256:83b427ad2bfa0b9705e02a83d8d607d2c2f01889eb138168e462a3a052c42368", - "sha256:923d03c84534078386cf50193057aae98fa94cace8ea7580b74754493fda73ad", - "sha256:b773715609649a1a180025213f67ffdeb5a4878c784293ada300ee95a1f3257b", - "sha256:baff149c174e9108d4a2fee192c496711be85534eab63adb122f93e70aa35431", - "sha256:bca9d118b1014b4c2d19319b10a3ebed508ff649396ce1855e1c96528d9b2fa9", - "sha256:ce580c28845581535dc6000fc7c35fdadf8bea7ccb57d6321b044508e9ba0685", - "sha256:d34923a569e70224d88e6682490e24c842907ba2c948c5fd26185413cbe0cd96", - "sha256:dd9f0e531a049d8b35ec5e6c68a37f1ba6ec3a591415e6804cbdf652793d15d7", - "sha256:ecb805cbfe9102f3fd3d2ef16dfe5ae9e2d7a7dfbba92f4ff1e16ac9784dbfb0", - "sha256:ede9aad2197a0202caff35d417b671f5f91a3631477441076082a17c94edd846", - "sha256:ef2d1fc370400e0aa755aab0b20cf4f1d0e934e7fd5244f3dd4869078e4942b9", - "sha256:f2fec194a49bfaef42a548ee657362af5c7a640da757f6f452a35da7dd9f923c" - ], - "version": "==4.3.4" + "sha256:02ca7bf899da57084041bb0f6095333e4d239948ad3169443f454add9f4e9cb4", + "sha256:096b82c5e0ea27ce9138bcbb205313343ee66a6e132f25c5ed67e2c8d960a1bc", + "sha256:0a920ff98cf1aac310470c644bc23b326402d3ef667ddafecb024e1713d485f1", + "sha256:17cae1730a782858a6e2758fd20dd0ef7567916c47757b694a06ffafdec20046", + "sha256:17e3950add54c882e032527795c625929613adbd2ce5162b94667334458b5a36", + "sha256:1f4f214337f6ee5825bf90a65d04d70aab05526c08191ab888cb5149501923c5", + "sha256:2e8f77db25b0a96af679e64ff9bf9dddb27d379c9900c3272f3041c4d1327c9d", + "sha256:4dffd405390a45ecb95ab5ab1c1b847553c18b0ef8ed01e10c1c8b1a76452916", + "sha256:6b899931a5648862c7b88c795eddff7588fb585e81cecce20f8d9da16eff96e0", + "sha256:726c17f3e0d7a7200718c9a890ccfeab391c9133e363a577a44717c85c71db27", + "sha256:760c12276fee05c36f95f8040180abc7fbebb9e5011447a97cdc289b5d6ab6fc", + "sha256:796685d3969815a633827c818863ee199440696b0961e200b011d79b9394bbe7", + "sha256:891fe897b49abb7db470c55664b198b1095e4943b9f82b7dcab317a19116cd38", + "sha256:a471628e20f03dcdfde00770eeaf9c77811f0c331c8805219ca7b87ac17576c5", + "sha256:a63b4fd3e2cabdcc9d918ed280bdde3e8e9641e04f3c59a2a3109644a07b9832", + "sha256:b0b84408d4eabc6de9dd1e1e0bc63e7731e890c0b378a62443e5741cfd0ae90a", + "sha256:be78485e5d5f3684e875dab60f40cddace2f5b2a8f7fede412358ab3214c3a6f", + "sha256:c27eaed872185f047bb7f7da2d21a7d8913457678c9a100a50db6da890bc28b9", + "sha256:c81cb40bff373ab7a7446d6bbca0190bccc5be3448b47b51d729e37799bb5692", + "sha256:d11874b3c33ee441059464711cd365b89fa1a9cf19ae75b0c189b01fbf735b84", + "sha256:e9c028b5897901361d81a4718d1db217b716424a0283afe9d6735fe0caf70f79", + "sha256:fe489d486cd00b739be826e8c1be188ddb74c7a1ca784d93d06fda882a6a1681" + ], + "version": "==4.4.1" }, "markupsafe": { "hashes": [ @@ -1189,12 +1204,19 @@ ], "version": "==1.1.1" }, + "more-itertools": { + "hashes": [ + "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", + "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" + ], + "version": "==7.2.0" + }, "msrest": { "hashes": [ - "sha256:2c0909570913785a4408a17286e151f3b28d39277113e5c63378572f7395c660", - "sha256:c9e9cbb0c47745f9f5c82cce60849d7c3ec9e33fc6fad9e2987b7657ad1ba479" + "sha256:56b8b5b4556fb2a92cac640df267d560889bdc9e2921187772d4691d97bc4e8d", + "sha256:f5153bfe60ee757725816aedaa0772cbfe0bddb52cd2d6db4cb8b4c3c6c6f928" ], - "version": "==0.6.8" + "version": "==0.6.10" }, "msrestazure": { "hashes": [ @@ -1212,10 +1234,10 @@ }, "oauthlib": { "hashes": [ - "sha256:40a63637707e9163eda62d0f5345120c65e001a790480b8256448543c1f78f66", - "sha256:b4d99ae8ccfb7d33ba9591b59355c64eef5241534aa3da2e4c0435346b84bc8e" + "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", + "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea" ], - "version": "==3.0.2" + "version": "==3.1.0" }, "odfpy": { "hashes": [ @@ -1231,9 +1253,9 @@ }, "openpyxl": { "hashes": [ - "sha256:1d2af392cef8c8227bd2ac3ebe3a28b25aba74fd4fa473ce106065f0b73bfe2e" + "sha256:72d1ed243972cad0b3c236230083cac00d9c72804e64a2ae93d7901aec1a8f1c" ], - "version": "==2.6.2" + "version": "==2.6.3" }, "pillow": { "hashes": [ @@ -1342,15 +1364,16 @@ }, "pyrestcli": { "hashes": [ - "sha256:ec07eb04a6a0088a88719c8d9acf47e51095fa4079cfee7a95dbd23d4b859176" + "sha256:67c37fe8d1d01ecd8fa213bca8414dbb4b9a065f0c0999ad93c02e908475593b", + "sha256:ea159468e689ecccb1c2b4fc42237ff073edb0a36def89a7c408c4130c6c0c69" ], - "version": "==0.6.11" + "version": "==0.6.12" }, "python-crontab": { "hashes": [ - "sha256:21bf01edd59a7357cdc9b1d911b16430499bf51172dd8c72d0c985c652f14057" + "sha256:11488bbac026bf77a659fee0bf8e20de9651753ad0fdf6649237e3b9e04b866a" ], - "version": "==2.3.7" + "version": "==2.3.8" }, "python-dateutil": { "hashes": [ @@ -1382,33 +1405,35 @@ }, "pytz": { "hashes": [ - "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", - "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" + "sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", + "sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7" ], - "version": "==2019.1" + "version": "==2019.2" }, "pyyaml": { "hashes": [ - "sha256:57acc1d8533cbe51f6662a55434f0dbecfa2b9eaf115bede8f6fd00115a0c0d3", - "sha256:588c94b3d16b76cfed8e0be54932e5729cc185caffaa5a451e7ad2f7ed8b4043", - "sha256:68c8dd247f29f9a0d09375c9c6b8fdc64b60810ebf07ba4cdd64ceee3a58c7b7", - "sha256:70d9818f1c9cd5c48bb87804f2efc8692f1023dac7f1a1a5c61d454043c1d265", - "sha256:86a93cccd50f8c125286e637328ff4eef108400dd7089b46a7be3445eecfa391", - "sha256:a0f329125a926876f647c9fa0ef32801587a12328b4a3c741270464e3e4fa778", - "sha256:a3c252ab0fa1bb0d5a3f6449a4826732f3eb6c0270925548cac342bc9b22c225", - "sha256:b4bb4d3f5e232425e25dda21c070ce05168a786ac9eda43768ab7f3ac2770955", - "sha256:cd0618c5ba5bda5f4039b9398bb7fb6a317bb8298218c3de25c47c4740e4b95e", - "sha256:ceacb9e5f8474dcf45b940578591c7f3d960e82f926c707788a570b51ba59190", - "sha256:fe6a88094b64132c4bb3b631412e90032e8cfe9745a58370462240b8cb7553cd" - ], - "version": "==5.1.1" + "sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", + "sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4", + "sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8", + "sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696", + "sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34", + "sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9", + "sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73", + "sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299", + "sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b", + "sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae", + "sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681", + "sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41", + "sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8" + ], + "version": "==5.1.2" }, "redis": { "hashes": [ - "sha256:6946b5dca72e86103edc8033019cc3814c031232d339d5f4533b02ea85685175", - "sha256:8ca418d2ddca1b1a850afa1680a7d2fd1f3322739271de4b704e0d4668449273" + "sha256:98a22fb750c9b9bb46e75e945dc3f61d0ab30d06117cbb21ff9cd1d315fedd3b", + "sha256:c504251769031b0dd7dd5cf786050a6050197c6de0d37778c80c08cb04ae8275" ], - "version": "==3.2.1" + "version": "==3.3.8" }, "reportlab": { "hashes": [ @@ -1460,11 +1485,11 @@ }, "sentry-sdk": { "hashes": [ - "sha256:692aa93637273365166041ee8e06ccd6b61d5f06560765d78828edbaac83fae1", - "sha256:7c9db0e419fb0fb31c1b1d2ec9247667d2b77bd4f3136119ee6f1464e9b088a4" + "sha256:d491aa6399eaa3eded433972751a9770180730fd8b4c225b0b7f49c4fa2af70b", + "sha256:d68003cdffbbfcadaa2c445b72e1050b0a44406f94199866f192986c016b23f5" ], "index": "pypi", - "version": "==0.9.5" + "version": "==0.10.2" }, "simplejson": { "hashes": [ @@ -1550,20 +1575,12 @@ ], "version": "==5.1.1" }, - "typing": { - "hashes": [ - "sha256:38566c558a0a94d6531012c8e917b1b8518a41e418f7f15f00e129cc80162ad3", - "sha256:53765ec4f83a2b720214727e319607879fec4acde22c4fbb54fa2604e79e44ce", - "sha256:84698954b4e6719e912ef9a42a2431407fe3755590831699debda6fba92aac55" - ], - "version": "==3.7.4" - }, "unicef-attachments": { "hashes": [ - "sha256:6d6bd2e606caf06d2b78762ab5319e6f39d569f123e665473e23a874669b8a7f" + "sha256:80c60906bb9b5e86b8eff86f9b3a702166e7fcd5681dd864b1d651a98345cf62" ], "index": "pypi", - "version": "==0.5.1" + "version": "==0.5.2" }, "unicef-djangolib": { "hashes": [ @@ -1671,6 +1688,13 @@ "sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88" ], "version": "==1.3.0" + }, + "zipp": { + "hashes": [ + "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", + "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" + ], + "version": "==0.6.0" } }, "develop": { @@ -1681,6 +1705,13 @@ ], "version": "==0.7.12" }, + "attrs": { + "hashes": [ + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" + ], + "version": "==19.1.0" + }, "babel": { "hashes": [ "sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", @@ -1711,40 +1742,41 @@ }, "coverage": { "hashes": [ - "sha256:3684fabf6b87a369017756b551cef29e505cb155ddb892a7a29277b978da88b9", - "sha256:39e088da9b284f1bd17c750ac672103779f7954ce6125fd4382134ac8d152d74", - "sha256:3c205bc11cc4fcc57b761c2da73b9b72a59f8d5ca89979afb0c1c6f9e53c7390", - "sha256:465ce53a8c0f3a7950dfb836438442f833cf6663d407f37d8c52fe7b6e56d7e8", - "sha256:48020e343fc40f72a442c8a1334284620f81295256a6b6ca6d8aa1350c763bbe", - "sha256:5296fc86ab612ec12394565c500b412a43b328b3907c0d14358950d06fd83baf", - "sha256:5f61bed2f7d9b6a9ab935150a6b23d7f84b8055524e7be7715b6513f3328138e", - "sha256:68a43a9f9f83693ce0414d17e019daee7ab3f7113a70c79a3dd4c2f704e4d741", - "sha256:6b8033d47fe22506856fe450470ccb1d8ba1ffb8463494a15cfc96392a288c09", - "sha256:7ad7536066b28863e5835e8cfeaa794b7fe352d99a8cded9f43d1161be8e9fbd", - "sha256:7bacb89ccf4bedb30b277e96e4cc68cd1369ca6841bde7b005191b54d3dd1034", - "sha256:839dc7c36501254e14331bcb98b27002aa415e4af7ea039d9009409b9d2d5420", - "sha256:8f9a95b66969cdea53ec992ecea5406c5bd99c9221f539bca1e8406b200ae98c", - "sha256:932c03d2d565f75961ba1d3cec41ddde00e162c5b46d03f7423edcb807734eab", - "sha256:988529edadc49039d205e0aa6ce049c5ccda4acb2d6c3c5c550c17e8c02c05ba", - "sha256:998d7e73548fe395eeb294495a04d38942edb66d1fa61eb70418871bc621227e", - "sha256:9de60893fb447d1e797f6bf08fdf0dbcda0c1e34c1b06c92bd3a363c0ea8c609", - "sha256:9e80d45d0c7fcee54e22771db7f1b0b126fb4a6c0a2e5afa72f66827207ff2f2", - "sha256:a545a3dfe5082dc8e8c3eb7f8a2cf4f2870902ff1860bd99b6198cfd1f9d1f49", - "sha256:a5d8f29e5ec661143621a8f4de51adfb300d7a476224156a39a392254f70687b", - "sha256:aca06bfba4759bbdb09bf52ebb15ae20268ee1f6747417837926fae990ebc41d", - "sha256:bb23b7a6fd666e551a3094ab896a57809e010059540ad20acbeec03a154224ce", - "sha256:bfd1d0ae7e292105f29d7deaa9d8f2916ed8553ab9d5f39ec65bcf5deadff3f9", - "sha256:c62ca0a38958f541a73cf86acdab020c2091631c137bd359c4f5bddde7b75fd4", - "sha256:c709d8bda72cf4cd348ccec2a4881f2c5848fd72903c185f363d361b2737f773", - "sha256:c968a6aa7e0b56ecbd28531ddf439c2ec103610d3e2bf3b75b813304f8cb7723", - "sha256:df785d8cb80539d0b55fd47183264b7002077859028dfe3070cf6359bf8b2d9c", - "sha256:f406628ca51e0ae90ae76ea8398677a921b36f0bd71aab2099dfed08abd0322f", - "sha256:f46087bbd95ebae244a0eda01a618aff11ec7a069b15a3ef8f6b520db523dcf1", - "sha256:f8019c5279eb32360ca03e9fac40a12667715546eed5c5eb59eb381f2f501260", - "sha256:fc5f4d209733750afd2714e9109816a29500718b32dd9a5db01c0cb3a019b96a" - ], - "index": "pypi", - "version": "==4.5.3" + "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", + "sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650", + "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5", + "sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d", + "sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351", + "sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755", + "sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef", + "sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca", + "sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca", + "sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9", + "sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc", + "sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5", + "sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f", + "sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe", + "sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888", + "sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5", + "sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce", + "sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5", + "sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e", + "sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e", + "sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9", + "sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437", + "sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1", + "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c", + "sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24", + "sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47", + "sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2", + "sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28", + "sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c", + "sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7", + "sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0", + "sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025" + ], + "index": "pypi", + "version": "==4.5.4" }, "decorator": { "hashes": [ @@ -1755,26 +1787,26 @@ }, "djangorestframework": { "hashes": [ - "sha256:376f4b50340a46c15ae15ddd0c853085f4e66058f97e4dbe7d43ed62f5e60651", - "sha256:c12869cfd83c33d579b17b3cb28a2ae7322a53c3ce85580c2a2ebe4e3f56c4fb" + "sha256:1ca4a5599a5ec31f3d6238a687fcc1dd4c41b1d90edab9ad398fcbf87872b7ba", + "sha256:c3c5edfdbc5dd33f9121bb84305bfd603d2c791f20cff9782772f44a7684a4e4" ], "index": "pypi", - "version": "==3.9.4" + "version": "==3.10.1" }, "docutils": { "hashes": [ - "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", - "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274", - "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6" + "sha256:54a349c622ff31c91cbec43b0b512f113b5b24daf00e2ea530bb1bd9aac14849", + "sha256:d2ddba74835cb090a1b627d3de4e7835c628d07ee461f7b4480f51af2fe4d448" ], - "version": "==0.14" + "index": "pypi", + "version": "==0.15" }, "drf-api-checker": { "hashes": [ - "sha256:db4045afe4585120ab298464c37887d8567a0ecadbe8eaa6a0e348c364d745bb" + "sha256:606861d85a3f0bfda7b3789a6bc4ed1c92724662d2e0756b5a2bb9841a21d4df" ], "index": "pypi", - "version": "==0.4.1" + "version": "==0.7.0" }, "entrypoints": { "hashes": [ @@ -1793,10 +1825,10 @@ }, "faker": { "hashes": [ - "sha256:1c0a5e7bb54d2c54569986a27124715c83899e592d8d61d4e372dbff6c699573", - "sha256:60477f757a80f665bbe1fb3d1cfe5d205ec7b99d5240114de7b27b4c25d236ca" + "sha256:1d3f700e8dfcefd6e657118d71405d53e86974448aba78884f119bbd84c0cddf", + "sha256:d5366e120191c5610fceeebfe1c298dc46da0277096f639c6dd7e2eaee0fa547" ], - "version": "==1.0.7" + "version": "==2.0.1" }, "fancycompleter": { "hashes": [ @@ -1813,11 +1845,11 @@ }, "flake8": { "hashes": [ - "sha256:859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661", - "sha256:a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8" + "sha256:19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548", + "sha256:8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696" ], "index": "pypi", - "version": "==3.7.7" + "version": "==3.7.8" }, "freezegun": { "hashes": [ @@ -1843,18 +1875,18 @@ }, "importlib-metadata": { "hashes": [ - "sha256:6dfd58dfe281e8d240937776065dd3624ad5469c835248219bd16cf2e12dbeb7", - "sha256:cb6ee23b46173539939964df59d3d72c3e0c1b5d54b84f1d8a7e912fe43612db" + "sha256:9ff1b1c5a354142de080b8a4e9803e5d0d59283c93aed808617c787d16768375", + "sha256:b7143592e374e50584564794fcb8aaf00a23025f9db866627f89a21491847a8d" ], - "version": "==0.18" + "version": "==0.20" }, "ipython": { "hashes": [ - "sha256:11067ab11d98b1e6c7f0993506f7a5f8a91af420f7e82be6575fcb7a6ca372a0", - "sha256:60bc55c2c1d287161191cc2469e73c116d9b634cff25fe214a43cba7cec94c79" + "sha256:c4ab005921641e40a68e405e286e7a1fcc464497e14d81b6914b4fd95e5dee9b", + "sha256:dd76831f065f17bddd7eaa5c781f5ea32de5ef217592cf019e34043b56895aa1" ], "index": "pypi", - "version": "==7.6.1" + "version": "==7.8.0" }, "ipython-genutils": { "hashes": [ @@ -1873,10 +1905,10 @@ }, "jedi": { "hashes": [ - "sha256:49ccb782651bb6f7009810d17a3316f8867dde31654c750506970742e18b553d", - "sha256:79d0f6595f3846dffcbe667cc6dc821b96e5baa8add125176c31a3917eb19d58" + "sha256:786b6c3d80e2f06fd77162a07fed81b8baa22dde5d62896a790a331d6ac21a27", + "sha256:ba859c74fa3c966a22f2aeebe1b74ee27e2a462f56d3f5f7ca4a59af61bfe42e" ], - "version": "==0.14.0" + "version": "==0.15.1" }, "jinja2": { "hashes": [ @@ -1933,6 +1965,13 @@ "index": "pypi", "version": "==3.0.5" }, + "more-itertools": { + "hashes": [ + "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", + "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" + ], + "version": "==7.2.0" + }, "multidict": { "hashes": [ "sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f", @@ -1969,17 +2008,17 @@ }, "packaging": { "hashes": [ - "sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af", - "sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3" + "sha256:a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", + "sha256:c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe" ], - "version": "==19.0" + "version": "==19.1" }, "parso": { "hashes": [ - "sha256:5052bb33be034cba784193e74b1cde6ebf29ae8b8c1e4ad94df0c4209bfc4826", - "sha256:db5881df1643bf3e66c097bfd8935cf03eae73f4cb61ae4433c9ea4fb6613446" + "sha256:63854233e1fadb5da97f2744b6b24346d2750b85965e7e399bec1620232797dc", + "sha256:666b0ee4a7a1220f65d367617f2cd3ffddff3e205f3f16a0284df30e774c2a9c" ], - "version": "==0.5.0" + "version": "==0.5.1" }, "pdbpp": { "hashes": [ @@ -2055,10 +2094,10 @@ }, "pyparsing": { "hashes": [ - "sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a", - "sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03" + "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", + "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" ], - "version": "==2.4.0" + "version": "==2.4.2" }, "python-dateutil": { "hashes": [ @@ -2069,26 +2108,28 @@ }, "pytz": { "hashes": [ - "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", - "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" + "sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", + "sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7" ], - "version": "==2019.1" + "version": "==2019.2" }, "pyyaml": { "hashes": [ - "sha256:57acc1d8533cbe51f6662a55434f0dbecfa2b9eaf115bede8f6fd00115a0c0d3", - "sha256:588c94b3d16b76cfed8e0be54932e5729cc185caffaa5a451e7ad2f7ed8b4043", - "sha256:68c8dd247f29f9a0d09375c9c6b8fdc64b60810ebf07ba4cdd64ceee3a58c7b7", - "sha256:70d9818f1c9cd5c48bb87804f2efc8692f1023dac7f1a1a5c61d454043c1d265", - "sha256:86a93cccd50f8c125286e637328ff4eef108400dd7089b46a7be3445eecfa391", - "sha256:a0f329125a926876f647c9fa0ef32801587a12328b4a3c741270464e3e4fa778", - "sha256:a3c252ab0fa1bb0d5a3f6449a4826732f3eb6c0270925548cac342bc9b22c225", - "sha256:b4bb4d3f5e232425e25dda21c070ce05168a786ac9eda43768ab7f3ac2770955", - "sha256:cd0618c5ba5bda5f4039b9398bb7fb6a317bb8298218c3de25c47c4740e4b95e", - "sha256:ceacb9e5f8474dcf45b940578591c7f3d960e82f926c707788a570b51ba59190", - "sha256:fe6a88094b64132c4bb3b631412e90032e8cfe9745a58370462240b8cb7553cd" - ], - "version": "==5.1.1" + "sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", + "sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4", + "sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8", + "sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696", + "sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34", + "sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9", + "sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73", + "sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299", + "sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b", + "sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae", + "sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681", + "sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41", + "sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8" + ], + "version": "==5.1.2" }, "requests": { "hashes": [ @@ -2121,11 +2162,11 @@ }, "sphinx": { "hashes": [ - "sha256:22538e1bbe62b407cf5a8aabe1bb15848aa66bb79559f42f5202bbce6b757a69", - "sha256:f9a79e746b87921cabc3baa375199c6076d1270cee53915dbd24fdbeaaacc427" + "sha256:0d586b0f8c2fc3cc6559c5e8fd6124628110514fda0e5d7c82e682d749d2e845", + "sha256:839a3ed6f6b092bb60f492024489cc9e6991360fb9f52ed6361acd510d261069" ], "index": "pypi", - "version": "==2.1.2" + "version": "==2.2.0" }, "sphinxcontrib-applehelp": { "hashes": [ @@ -2185,11 +2226,11 @@ }, "tox": { "hashes": [ - "sha256:dab0b0160dd187b654fc33d690ee1d7bf328bd5b8dc6ef3bb3cc468969c659ba", - "sha256:ee35ffce74933a6c6ac10c9a0182e41763140a5a5070e21b114feca56eaccdcd" + "sha256:0bc216b6a2e6afe764476b4a07edf2c1dab99ed82bb146a1130b2e828f5bff5e", + "sha256:c4f6b319c20ba4913dbfe71ebfd14ff95d1853c4231493608182f66e566ecfe1" ], "index": "pypi", - "version": "==3.13.2" + "version": "==3.14.0" }, "traitlets": { "hashes": [ @@ -2207,18 +2248,18 @@ }, "vcrpy": { "hashes": [ - "sha256:127e79cf7b569d071d1bd761b83f7b62b2ce2a2eb63ceca7aa67cba8f2602ea3", - "sha256:57be64aa8e9883a4117d0b15de28af62275c001abcdb00b6dc2d4406073d9a4f" + "sha256:0e79239441fb4c731da9f05aecbd062223ef1f4ab668d2400c63c347a7071414", + "sha256:b5bcdd4450d03acd623515c8b9fbf3f1f7fa3a44fd0c01c674b0f1166a1949f8" ], "index": "pypi", - "version": "==2.0.1" + "version": "==2.1.0" }, "virtualenv": { "hashes": [ - "sha256:b7335cddd9260a3dd214b73a2521ffc09647bde3e9457fcca31dc3be3999d04a", - "sha256:d28ca64c0f3f125f59cabf13e0a150e1c68e5eea60983cc4395d88c584495783" + "sha256:680af46846662bb38c5504b78bad9ed9e4f3ba2d54f54ba42494fdf94337fe30", + "sha256:f78d81b62d3147396ac33fc9d77579ddc42cc2a98dd9ea38886f616b33bc7fb2" ], - "version": "==16.6.1" + "version": "==16.7.5" }, "wcwidth": { "hashes": [ @@ -2253,15 +2294,15 @@ "sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8", "sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1" ], - "markers": "python_version >= '3.4'", + "markers": "python_version >= '3.5'", "version": "==1.3.0" }, "zipp": { "hashes": [ - "sha256:8c1019c6aad13642199fbe458275ad6a84907634cc9f0989877ccc4a2840139d", - "sha256:ca943a7e809cc12257001ccfb99e3563da9af99d52f261725e96dfe0f9275bc3" + "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", + "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" ], - "version": "==0.5.1" + "version": "==0.6.0" } } } diff --git a/setup.py b/setup.py index 808b294b7f..080fef7b41 100644 --- a/setup.py +++ b/setup.py @@ -40,8 +40,8 @@ 'Framework :: Django', 'Operating System :: POSIX :: Linux', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Framework :: Django', - 'Framework :: Django :: 2.1', + 'Framework :: Django :: 2.2', ], ) diff --git a/src/etools/__init__.py b/src/etools/__init__.py index 74299a1ed9..2d564128c4 100644 --- a/src/etools/__init__.py +++ b/src/etools/__init__.py @@ -1,2 +1,2 @@ -VERSION = __version__ = '7.2.1' +VERSION = __version__ = '7.3' NAME = 'eTools' diff --git a/src/etools/applications/action_points/views.py b/src/etools/applications/action_points/views.py index 9190a8c059..0689547a70 100644 --- a/src/etools/applications/action_points/views.py +++ b/src/etools/applications/action_points/views.py @@ -88,6 +88,7 @@ class ActionPointViewSet( )} filter_fields.update({ 'status': ['exact', 'in'], + 'section': ['exact', 'in'], 'due_date': ['exact', 'lte', 'gte'] }) diff --git a/src/etools/applications/audit/filters.py b/src/etools/applications/audit/filters.py index 3f0824af2c..1ebb3012e1 100644 --- a/src/etools/applications/audit/filters.py +++ b/src/etools/applications/audit/filters.py @@ -73,4 +73,5 @@ class Meta: 'joint_audit': ['exact'], 'agreement__auditor_firm__unicef_users_allowed': ['exact'], 'staff_members__user': ['exact', 'in'], + 'partner_contacted_at': ['lte', 'gte', 'gt', 'lt'], } diff --git a/src/etools/applications/audit/serializers/engagement.py b/src/etools/applications/audit/serializers/engagement.py index 1d5c4fa75c..0813051aa4 100644 --- a/src/etools/applications/audit/serializers/engagement.py +++ b/src/etools/applications/audit/serializers/engagement.py @@ -3,9 +3,9 @@ from django.utils.translation import ugettext as _ from rest_framework import serializers -from unicef_attachments.fields import FileTypeModelChoiceField -from unicef_attachments.models import FileType -from unicef_attachments.serializers import BaseAttachmentSerializer +from unicef_attachments.fields import AttachmentSingleFileField, FileTypeModelChoiceField +from unicef_attachments.models import Attachment, FileType +from unicef_attachments.serializers import AttachmentSerializerMixin from unicef_restlib.fields import SeparatedReadWriteField from unicef_restlib.serializers import WritableNestedParentSerializerMixin, WritableNestedSerializerMixin @@ -65,30 +65,57 @@ class Meta(PartnerOrganizationListSerializer.Meta): } -class EngagementAttachmentSerializer(BaseAttachmentSerializer): +class AttachmentField(serializers.Field): + def to_representation(self, value): + if not value: + return None + + attachment = Attachment.objects.get(pk=value) + if not getattr(attachment.file, "url", None): + return None + + url = attachment.file.url + request = self.context.get('request', None) + if request is not None: + return request.build_absolute_uri(url) + return url + + def to_internal_value(self, data): + return data + + +class EngagementAttachmentSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(read_only=True) + attachment = AttachmentField(source="pk") file_type = FileTypeModelChoiceField( - label=_('Document Type'), queryset=FileType.objects.filter(code='audit_engagement') + label=_('Document Type'), + queryset=FileType.objects.filter(code='audit_engagement'), ) - class Meta(BaseAttachmentSerializer.Meta): - pass + class Meta: + model = Attachment + fields = ("id", "attachment", "file_type", "created") - def create(self, validated_data): + def update(self, instance, validated_data): validated_data['code'] = 'audit_engagement' - return super().create(validated_data) + return super().update(instance, validated_data) -class ReportAttachmentSerializer(BaseAttachmentSerializer): +class ReportAttachmentSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(read_only=True) + attachment = AttachmentField(source="pk") file_type = FileTypeModelChoiceField( - label=_('Document Type'), queryset=FileType.objects.filter(code='audit_report') + label=_('Document Type'), + queryset=FileType.objects.filter(code='audit_report'), ) - class Meta(BaseAttachmentSerializer.Meta): - pass + class Meta: + model = Attachment + fields = ("id", "attachment", "file_type", "created") - def create(self, validated_data): + def update(self, instance, validated_data): validated_data['code'] = 'audit_report' - return super().create(validated_data) + return super().update(instance, validated_data) class EngagementActionPointSerializer(PermissionsBasedSerializerMixin, ActionPointBaseSerializer): @@ -220,9 +247,12 @@ class Meta(WritableNestedSerializerMixin.Meta): ] -class EngagementSerializer(EngagementDatesValidation, - WritableNestedParentSerializerMixin, - EngagementListSerializer): +class EngagementSerializer( + AttachmentSerializerMixin, + EngagementDatesValidation, + WritableNestedParentSerializerMixin, + EngagementListSerializer +): staff_members = SeparatedReadWriteField( read_field=AuditorStaffMemberSerializer(many=True, required=False), label=_('Audit Staff Team Members') ) @@ -235,6 +265,9 @@ class EngagementSerializer(EngagementDatesValidation, ) specific_procedures = SpecificProcedureSerializer(many=True, label=_('Specific Procedure To Be Performed')) + engagement_attachments = AttachmentSingleFileField(required=False) + report_attachments = AttachmentSingleFileField(required=False) + final_report = AttachmentSingleFileField(required=False) class Meta(EngagementListSerializer.Meta): fields = EngagementListSerializer.Meta.fields + [ @@ -249,6 +282,9 @@ class Meta(EngagementListSerializer.Meta): 'date_of_draft_report_to_unicef', 'date_of_comments_by_unicef', 'date_of_report_submit', 'date_of_final_report', 'date_of_cancel', 'cancel_comment', 'specific_procedures', + 'engagement_attachments', + 'report_attachments', + 'final_report', ] extra_kwargs = { field: {'required': True} for field in [ diff --git a/src/etools/applications/audit/tests/test_views.py b/src/etools/applications/audit/tests/test_views.py index 7dd6e8454b..1d4c0bc773 100644 --- a/src/etools/applications/audit/tests/test_views.py +++ b/src/etools/applications/audit/tests/test_views.py @@ -1,13 +1,15 @@ import datetime +import json import random -from django.core.files.uploadedfile import SimpleUploadedFile +from django.contrib.contenttypes.models import ContentType from django.core.management import call_command from django.urls import reverse from factory import fuzzy from mock import Mock, patch from rest_framework import status +from unicef_attachments.models import Attachment from etools.applications.action_points.tests.factories import ActionPointCategoryFactory, ActionPointFactory from etools.applications.attachments.tests.factories import AttachmentFactory, AttachmentFileTypeFactory @@ -488,6 +490,30 @@ def test_government_partner_without_active_pd(self): self.assertEquals(response.status_code, status.HTTP_201_CREATED) + def test_attachments(self): + file_type_engagement = AttachmentFileTypeFactory( + code="audit_engagement", + ) + attachment_engagement = AttachmentFactory( + file="test_engagement.pdf", + file_type=None, + code="", + ) + self.create_data["engagement_attachments"] = attachment_engagement.pk + + response = self._do_create(self.unicef_focal_point, self.create_data) + + self.assertEquals(response.status_code, status.HTTP_201_CREATED) + + data = json.loads(response.content) + engagement_val = data["engagement_attachments"] + self.assertIsNotNone(engagement_val) + self.assertTrue( + engagement_val.endswith(attachment_engagement.file.name) + ) + attachment_engagement.refresh_from_db() + self.assertEqual(attachment_engagement.file_type, file_type_engagement) + class TestMicroAssessmentCreateViewSet(TestEngagementCreateActivePDViewSet, BaseTestEngagementsCreateViewSet, BaseTenantTestCase): @@ -1184,28 +1210,103 @@ def test_get(self): class TestEngagementAttachmentsView(MATransitionsTestCaseMixin, BaseTenantTestCase): + @classmethod + def setUpTestData(cls): + super().setUpTestData() + cls.file_type = AttachmentFileTypeFactory(code='audit_engagement') + def test_list(self): + attachment = AttachmentFactory( + file="sample.pdf", + file_type=self.file_type, + content_type=ContentType.objects.get_for_model(Engagement), + object_id=self.engagement.pk, + code="audit_engagement", + ) + self.engagement.engagement_attachments.add(attachment) attachments_num = self.engagement.engagement_attachments.count() + response = self.forced_auth_req( + 'get', + reverse( + 'audit:engagement-attachments-list', + args=[self.engagement.pk], + ), + user=self.unicef_focal_point + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data['results']), attachments_num) - create_response = self.forced_auth_req( + def test_post(self): + attachment = AttachmentFactory(file="sample.pdf") + self.assertIsNone(attachment.object_id) + self.assertNotEqual(attachment.code, "audit_engagement") + + response = self.forced_auth_req( 'post', - reverse('audit:engagement-attachments-list', args=[self.engagement.id]), + reverse( + 'audit:engagement-attachments-list', + args=[self.engagement.pk], + ), user=self.unicef_focal_point, - request_format='multipart', data={ - 'file_type': AttachmentFileTypeFactory(code='audit_engagement').id, - 'file': SimpleUploadedFile('hello_world.txt', 'hello world!'.encode('utf-8')), + 'file_type': self.file_type.pk, + 'attachment': attachment.pk, } ) - self.assertEqual(create_response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + attachment.refresh_from_db() + self.assertEqual(attachment.object_id, self.engagement.pk) + self.assertEqual(attachment.code, "audit_engagement") + + def test_patch(self): + file_type_old = AttachmentFileTypeFactory(code="different_engagement") + attachment = AttachmentFactory( + file="sample.pdf", + file_type=file_type_old, + content_type=ContentType.objects.get_for_model(Engagement), + object_id=self.engagement.pk, + code="audit_engagement", + ) + self.assertNotEqual(attachment.file_type, self.file_type) response = self.forced_auth_req( - 'get', - reverse('audit:engagement-attachments-list', args=[self.engagement.id]), - user=self.unicef_focal_point + 'patch', + reverse( + 'audit:engagement-attachments-detail', + args=[self.engagement.pk, attachment.pk], + ), + user=self.unicef_focal_point, + data={ + "file_type": self.file_type.pk, + } ) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data['results']), attachments_num + 1) + attachment.refresh_from_db() + self.assertEqual(attachment.file_type, self.file_type) + + def test_delete(self): + attachment = AttachmentFactory( + file="sample.pdf", + file_type=self.file_type, + content_type=ContentType.objects.get_for_model(Engagement), + object_id=self.engagement.pk, + code="audit_engagement", + ) + self.engagement.engagement_attachments.add(attachment) + attachment_qs = self.engagement.engagement_attachments + attachments_num = attachment_qs.count() + + response = self.forced_auth_req( + 'delete', + reverse( + 'audit:engagement-attachments-detail', + args=[self.engagement.pk, attachment.pk], + ), + user=self.unicef_focal_point, + ) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + self.assertEqual(attachment_qs.count(), attachments_num - 1) + self.assertFalse(Attachment.objects.filter(pk=attachment.pk).exists()) def test_create_meta_focal_point(self): response = self.forced_auth_req( @@ -1229,25 +1330,101 @@ def test_create_meta_unicef_user(self): class TestEngagementReportAttachmentsView(MATransitionsTestCaseMixin, BaseTenantTestCase): + @classmethod + def setUpTestData(cls): + super().setUpTestData() + cls.file_type = AttachmentFileTypeFactory(code='audit_report') + def test_list(self): + attachment = AttachmentFactory( + file="sample.pdf", + file_type=self.file_type, + content_type=ContentType.objects.get_for_model(Engagement), + object_id=self.engagement.pk, + code="audit_report", + ) + self.engagement.report_attachments.add(attachment) attachments_num = self.engagement.report_attachments.count() - create_response = self.forced_auth_req( + response = self.forced_auth_req( + 'get', + reverse( + 'audit:report-attachments-list', + args=[self.engagement.pk], + ), + user=self.auditor + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data['results']), attachments_num) + + def test_post(self): + attachment = AttachmentFactory(file="sample.pdf") + self.assertIsNone(attachment.object_id) + self.assertNotEqual(attachment.code, "audit_report") + + response = self.forced_auth_req( 'post', - reverse('audit:report-attachments-list', args=[self.engagement.id]), - user=self.auditor, - request_format='multipart', + reverse( + 'audit:report-attachments-list', + args=[self.engagement.pk], + ), + user=self.unicef_focal_point, data={ - 'file_type': AttachmentFileTypeFactory(code='audit_report').id, - 'file': SimpleUploadedFile('hello_world.txt', 'hello world!'.encode('utf-8')), + 'file_type': self.file_type.pk, + 'attachment': attachment.pk, } ) - self.assertEqual(create_response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + attachment.refresh_from_db() + self.assertEqual(attachment.object_id, self.engagement.pk) + self.assertEqual(attachment.code, "audit_report") + + def test_patch(self): + file_type_old = AttachmentFileTypeFactory(code="different_report") + attachment = AttachmentFactory( + file="sample.pdf", + file_type=file_type_old, + content_type=ContentType.objects.get_for_model(Engagement), + object_id=self.engagement.pk, + code="audit_report", + ) + self.assertNotEqual(attachment.file_type, self.file_type) response = self.forced_auth_req( - 'get', - reverse('audit:report-attachments-list', args=[self.engagement.id]), - user=self.auditor + 'patch', + reverse( + 'audit:report-attachments-detail', + args=[self.engagement.pk, attachment.pk], + ), + user=self.unicef_focal_point, + data={ + "file_type": self.file_type.pk, + } ) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data['results']), attachments_num + 1) + attachment.refresh_from_db() + self.assertEqual(attachment.file_type, self.file_type) + + def test_delete(self): + attachment = AttachmentFactory( + file="sample.pdf", + file_type=self.file_type, + content_type=ContentType.objects.get_for_model(Engagement), + object_id=self.engagement.pk, + code="audit_report", + ) + self.engagement.engagement_attachments.add(attachment) + attachment_qs = self.engagement.report_attachments + attachments_num = attachment_qs.count() + + response = self.forced_auth_req( + 'delete', + reverse( + 'audit:report-attachments-detail', + args=[self.engagement.pk, attachment.pk], + ), + user=self.unicef_focal_point, + ) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + self.assertEqual(attachment_qs.count(), attachments_num - 1) + self.assertFalse(Attachment.objects.filter(pk=attachment.pk).exists()) diff --git a/src/etools/applications/audit/views.py b/src/etools/applications/audit/views.py index b81a37a7c8..6e184b7ff7 100644 --- a/src/etools/applications/audit/views.py +++ b/src/etools/applications/audit/views.py @@ -591,7 +591,20 @@ def get_parent_filter(self): 'object_id': parent.pk } + def get_object(self, pk=None): + if pk: + return self.queryset.get(pk=pk) + elif self.kwargs.get("pk"): + return self.queryset.get(pk=self.kwargs.get("pk")) + return super().get_object() + def perform_create(self, serializer): + serializer.instance = self.get_object( + pk=serializer.validated_data.get("pk") + ) + serializer.save(content_object=self.get_parent_object().get_subclass()) + + def perform_update(self, serializer): serializer.save(content_object=self.get_parent_object().get_subclass()) diff --git a/src/etools/applications/funds/synchronizers.py b/src/etools/applications/funds/synchronizers.py index 65f80478ef..87ffa63528 100644 --- a/src/etools/applications/funds/synchronizers.py +++ b/src/etools/applications/funds/synchronizers.py @@ -5,6 +5,7 @@ from django.db.models import Sum +from unicef_vision.exceptions import VisionException from unicef_vision.synchronizers import FileDataSynchronizer, ManualVisionSynchronizer from unicef_vision.utils import comp_decimals @@ -14,7 +15,6 @@ FundsReservationHeader, FundsReservationItem, ) -from etools.applications.vision.exceptions import VisionSyncException from etools.applications.vision.synchronizers import VisionDataTenantSynchronizer @@ -124,7 +124,7 @@ def _convert_records(self, records): json_records = json.loads(records)["ROWSET"]["ROW"] except json.decoder.JSONDecodeError: if "No data exists" in records: - raise VisionSyncException("Vision error 400: No data could be found") + raise VisionException("Vision error 400: No data could be found") else: raise # Since our counterpart APIs are designed in a way that can surprise us what data structure we might encounter diff --git a/src/etools/applications/funds/tests/test_views.py b/src/etools/applications/funds/tests/test_views.py index 90e06803a1..9c48b53492 100644 --- a/src/etools/applications/funds/tests/test_views.py +++ b/src/etools/applications/funds/tests/test_views.py @@ -6,7 +6,6 @@ from django.utils import timezone from rest_framework import status -from unicef_vision.exceptions import VisionException from etools.applications.core.tests.cases import BaseTenantTestCase from etools.applications.funds.tests.factories import ( @@ -112,15 +111,25 @@ def test_get_fail_with_no_values(self): @VCR.use_cassette(str(Path(__file__).parent / 'vcr_cassettes/fund_reservation_invalid.yml')) def test_get_fail_with_non_existant_values(self): - data = {'values': ','.join(['im a bad value', 'another bad value'])} - with self.assertRaises(VisionException): - status_code, result = self.run_request(data) - self.assertEqual(status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(result['error'], 'One or more of the FRs are used by another PD/SSFA ' - 'or could not be found in eTools.') + data = {'values': ','.join(['another bad value', 'im a bad value', ])} + status_code, result = self.run_request(data) + self.assertEqual(status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(result['error'], 'The FR another bad value could not be found in eTools and could not be ' + 'synced from Vision. Vision error 400: No data could be found') # TODO: add tests to cover, frs correctly brought in from mock. with correct vendor numbers, FR missing from vision, # FR with multiple line items, and FR with only one line item. + @VCR.use_cassette(str(Path(__file__).parent / 'vcr_cassettes/fund_reservation.yml')) + def test_get_fail_with_already_used_fr(self): + new_intervention = InterventionFactory() + self.fr_1.intervention = self.intervention + self.fr_1.save() + data = {'values': ','.join(['9999', self.fr_1.fr_number]), + 'intervention': new_intervention.pk} + status_code, result = self.run_request(data) + self.assertEqual(status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(result['error'], f'FR #{self.fr_1} is already being used by PD/SSFA ref [{self.intervention}]') + @VCR.use_cassette(str(Path(__file__).parent / 'vcr_cassettes/fund_reservation.yml')) def test_get_success_sync_vision(self): self.fr_1.intervention = self.intervention diff --git a/src/etools/applications/funds/tests/vcr_cassettes/fund_reservation.yml b/src/etools/applications/funds/tests/vcr_cassettes/fund_reservation.yml index 591db14fc9..9cdc2f6b8d 100644 --- a/src/etools/applications/funds/tests/vcr_cassettes/fund_reservation.yml +++ b/src/etools/applications/funds/tests/vcr_cassettes/fund_reservation.yml @@ -11,69 +11,40 @@ interactions: Content-Type: - application/json User-Agent: - - python-requests/2.21.0 + - python-requests/2.22.0 method: GET uri: http://invalid_vision_url/GetFundsReservationInfo_JSON/9999 response: body: - string: '"{ \"ROWSET\": { \"ROW\": [ {\"ACTUAL_CASH_TRANSFER\": \"50197.02\", - \"ACTUAL_CASH_TRANSFER_DC\": \"2689656.62\", \"COMPLETED_FLAG\": \"N\", \"CURRENCY\": - \"PHP\", \"CURRENT_FR_AMOUNT\": \"50197.02\", \"DONOR_CODE\": \"CODE X\", - \"DONOR_NAME\": \"DONOR X\", \"DUE_DATE\": \"04-FEB-19\", \"FR_DOC_DATE\": - \"17-OCT-18\", \"FR_DOCUMENT_TEXT\": \"FR FOR MRM PD NP\", \"FR_END_DATE\": - \"31-MAR-19\", \"FR_LINE_ITEM_TEXT\": \"FR FOR NP\", \"FR_NUMBER\": \"9999\", - \"FR_OVERALL_AMOUNT\": \"179643.93\", \"FR_START_DATE\": \"17-OCT-18\", \"FR_TYPE\": - \"Programme Document Against PCA\", \"FUND\": \"SC\", \"GRANT_NBR\": \"GRANT W\", - \"LINE_ITEM\": \"4\", \"MULTI_CURR_FLAG\": \"N\", \"OUTSTANDING_DCT\": \"0\", - \"OUTSTANDING_DCT_DC\": \"0\", \"OVERALL_AMOUNT\": \"26425.53\", \"OVERALL_AMOUNT_DC\": - \"1430942.38\", \"VENDOR_CODE\": \"PVN\", \"WBS_ELEMENT\": \"9999\\\/A0\\\/06\\\/003\\\/006\\\/005\" - }, {\"ACTUAL_CASH_TRANSFER\": \"50197.02\", \"ACTUAL_CASH_TRANSFER_DC\": \"2689656.62\", - \"COMPLETED_FLAG\": \"N\", \"CURRENCY\": \"PHP\", \"CURRENT_FR_AMOUNT\": \"50197.02\", - \"DONOR_CODE\": \"CODE X\", \"DONOR_NAME\": \"DONOR X\", \"DUE_DATE\": - \"17-OCT-18\", \"FR_DOC_DATE\": \"17-OCT-18\", \"FR_DOCUMENT_TEXT\": \"FR - FOR MRM PD NP\", \"FR_END_DATE\": \"31-MAR-19\", \"FR_LINE_ITEM_TEXT\": \"FR - FOR NP\", \"FR_NUMBER\": \"9999\", \"FR_OVERALL_AMOUNT\": \"179643.93\", - \"FR_START_DATE\": \"17-OCT-18\", \"FR_TYPE\": \"Programme Document Against - PCA\", \"FUND\": \"SC\", \"GRANT_NBR\": \"GRANT W\", \"LINE_ITEM\": \"2\", - \"MULTI_CURR_FLAG\": \"N\", \"OUTSTANDING_DCT\": \"0\", \"OUTSTANDING_DCT_DC\": - \"0\", \"OVERALL_AMOUNT\": \"30081.45\", \"OVERALL_AMOUNT_DC\": \"1628910.25\", - \"VENDOR_CODE\": \"PVN\", \"WBS_ELEMENT\": \"9999\\\/A0\\\/05\\\/803\\\/005\\\/004\" - }, {\"ACTUAL_CASH_TRANSFER\": \"50197.02\", \"ACTUAL_CASH_TRANSFER_DC\": \"2689656.62\", - \"COMPLETED_FLAG\": \"N\", \"CURRENCY\": \"PHP\", \"CURRENT_FR_AMOUNT\": \"50197.02\", - \"DONOR_CODE\": \"CODE X\", \"DONOR_NAME\": \"DONOR Y\", \"DUE_DATE\": \"04-FEB-19\", - \"FR_DOC_DATE\": \"17-OCT-18\", \"FR_DOCUMENT_TEXT\": \"FR FOR MRM PD NP\", - \"FR_END_DATE\": \"31-MAR-19\", \"FR_LINE_ITEM_TEXT\": \"FR FOR NP\", \"FR_NUMBER\": - \"9999\", \"FR_OVERALL_AMOUNT\": \"179643.93\", \"FR_START_DATE\": \"17-OCT-18\", - \"FR_TYPE\": \"Programme Document Against PCA\", \"FUND\": \"SC\", \"GRANT_NBR\": - \"GRANT W\", \"LINE_ITEM\": \"5\", \"MULTI_CURR_FLAG\": \"N\", \"OUTSTANDING_DCT\": - \"0\", \"OUTSTANDING_DCT_DC\": \"0\", \"OVERALL_AMOUNT\": \"48014.77\", \"OVERALL_AMOUNT_DC\": - \"2600000\", \"VENDOR_CODE\": \"PVN\", \"WBS_ELEMENT\": \"9999\\\/A0\\\/06\\\/003\\\/006\\\/005\" - }, {\"ACTUAL_CASH_TRANSFER\": \"50197.02\", \"ACTUAL_CASH_TRANSFER_DC\": \"2689656.62\", - \"COMPLETED_FLAG\": \"N\", \"CURRENCY\": \"PHP\", \"CURRENT_FR_AMOUNT\": \"50197.02\", - \"DONOR_CODE\": \"N\\\/A\", \"DONOR_NAME\": \"N\\\/A\", \"DUE_DATE\": \"17-OCT-18\", - \"FR_DOC_DATE\": \"17-OCT-18\", \"FR_DOCUMENT_TEXT\": \"FR FOR MRM PD NP\", - \"FR_END_DATE\": \"31-MAR-19\", \"FR_LINE_ITEM_TEXT\": \"FR FOR NP\", \"FR_NUMBER\": - \"9999\", \"FR_OVERALL_AMOUNT\": \"179643.93\", \"FR_START_DATE\": \"17-OCT-18\", - \"FR_TYPE\": \"Programme Document Against PCA\", \"FUND\": \"GC\", \"GRANT_NBR\": - \"NON-GRANT\", \"LINE_ITEM\": \"1\", \"MULTI_CURR_FLAG\": \"N\", \"OUTSTANDING_DCT\": - \"0\", \"OUTSTANDING_DCT_DC\": \"0\", \"OVERALL_AMOUNT\": \"50000\", \"OVERALL_AMOUNT_DC\": - \"2707500\", \"VENDOR_CODE\": \"PVN\", \"WBS_ELEMENT\": \"9999\\\/A0\\\/05\\\/803\\\/005\\\/004\" - }, {\"ACTUAL_CASH_TRANSFER\": \"50197.02\", \"ACTUAL_CASH_TRANSFER_DC\": \"2689656.62\", - \"COMPLETED_FLAG\": \"N\", \"CURRENCY\": \"PHP\", \"CURRENT_FR_AMOUNT\": \"50197.02\", - \"DONOR_CODE\": \"CODE X\", \"DONOR_NAME\": \"DONOR Z\", \"DUE_DATE\": \"17-OCT-18\", - \"FR_DOC_DATE\": \"17-OCT-18\", \"FR_DOCUMENT_TEXT\": \"FR FOR MRM PD NP\", - \"FR_END_DATE\": \"31-MAR-19\", \"FR_LINE_ITEM_TEXT\": \"FR FOR NP\", \"FR_NUMBER\": - \"9999\", \"FR_OVERALL_AMOUNT\": \"179643.93\", \"FR_START_DATE\": \"17-OCT-18\", - \"FR_TYPE\": \"Programme Document Against PCA\", \"FUND\": \"SC\", \"GRANT_NBR\": - \"GRANT W\", \"LINE_ITEM\": \"3\", \"MULTI_CURR_FLAG\": \"N\", \"OUTSTANDING_DCT\": - \"0\", \"OUTSTANDING_DCT_DC\": \"0\", \"OVERALL_AMOUNT\": \"25125.48\", \"OVERALL_AMOUNT_DC\": - \"1360545.62\", \"VENDOR_CODE\": \"PVN\", \"WBS_ELEMENT\": \"9999\\\/A0\\\/05\\\/803\\\/005\\\/004\" - } ] }}"' + string: '"{ \"ROWSET\": { \"ROW\": [ {\"ACTUAL_CASH_TRANSFER\": \"383131\", + \"ACTUAL_CASH_TRANSFER_DC\": \"383131\", \"COMPLETED_FLAG\": \"N\", \"CURRENCY\": + \"USD\", \"CURRENT_FR_AMOUNT\": \"383131\", \"DONOR_CODE\": \"G45301\", \"DONOR_NAME\": + \"Macioce\", \"DUE_DATE\": \"01-FEB-17\", \"FR_DOC_DATE\": \"17-JAN-17\", + \"FR_DOCUMENT_TEXT\": \"WASH NZ:EM LIF SAVIMG IN KHAZER \\\/ACF 1ST TRANCHE\", + \"FR_END_DATE\": \"31-MAR-17\", \"FR_LINE_ITEM_TEXT\": \"WASH NZ:EM LIF SAVIMG + IN KHAZER \\\/ACF 1ST TRANCHE\", \"FR_NUMBER\": \"9999\", \"FR_OVERALL_AMOUNT\": + \"383131\", \"FR_START_DATE\": \"17-JAN-17\", \"FR_TYPE\": \"Programme Document + Against PCA\", \"FUND\": \"SM\", \"GRANT_NBR\": \"XYZ\", \"LINE_ITEM\": + \"2\", \"MULTI_CURR_FLAG\": \"N\", \"OUTSTANDING_DCT\": \"0\", \"OUTSTANDING_DCT_DC\": + \"0\", \"OVERALL_AMOUNT\": \"246176\", \"OVERALL_AMOUNT_DC\": \"246176\", + \"VENDOR_CODE\": \"PVN\", \"WBS_ELEMENT\": \"2130\\\/A0\\\/08\\\/002\\\/005\\\/312\" + }, {\"ACTUAL_CASH_TRANSFER\": \"383131\", \"ACTUAL_CASH_TRANSFER_DC\": \"383131\", + \"COMPLETED_FLAG\": \"N\", \"CURRENCY\": \"USD\", \"CURRENT_FR_AMOUNT\": \"383131\", + \"DONOR_CODE\": \"G45605\", \"DONOR_NAME\": \"USA (USAID) OFDA\", \"DUE_DATE\": + \"01-FEB-17\", \"FR_DOC_DATE\": \"17-JAN-17\", \"FR_DOCUMENT_TEXT\": \"XYA + NZ:EM LIF ZAOJ \\\/ACF 1ST TRANCHE\", \"FR_END_DATE\": \"31-MAR-17\", + \"FR_LINE_ITEM_TEXT\": \"WASH NZ:EM LIF SAVIMG IN KHAZER \\\/ACF 1ST TRANCHE\", + \"FR_NUMBER\": \"9999\", \"FR_OVERALL_AMOUNT\": \"383131\", \"FR_START_DATE\": + \"17-JAN-17\", \"FR_TYPE\": \"Programme Document Against PCA\", \"FUND\": + \"SM\", \"GRANT_NBR\": \"SM160255\", \"LINE_ITEM\": \"1\", \"MULTI_CURR_FLAG\": + \"N\", \"OUTSTANDING_DCT\": \"0\", \"OUTSTANDING_DCT_DC\": \"0\", \"OVERALL_AMOUNT\": + \"136955\", \"OVERALL_AMOUNT_DC\": \"136955\", \"VENDOR_CODE\": \"PVN\", + \"WBS_ELEMENT\": \"2130\\\/A0\\\/08\\\/002\\\/005\\\/312\" } ] }}"' headers: Content-Type: - application/json; charset=utf-8 Date: - - Wed, 19 Jun 2019 20:40:54 GMT + - Mon, 22 Jul 2019 19:02:08 GMT Server: - Microsoft-IIS/8.5 Microsoft-HTTPAPI/2.0 Vary: @@ -81,7 +52,7 @@ interactions: X-Powered-By: - ASP.NET content-length: - - '4433' + - '1934' status: code: 200 message: OK diff --git a/src/etools/applications/funds/tests/vcr_cassettes/fund_reservation_invalid.yml b/src/etools/applications/funds/tests/vcr_cassettes/fund_reservation_invalid.yml index ccad3bf237..939ccc1c3a 100644 --- a/src/etools/applications/funds/tests/vcr_cassettes/fund_reservation_invalid.yml +++ b/src/etools/applications/funds/tests/vcr_cassettes/fund_reservation_invalid.yml @@ -22,7 +22,7 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Wed, 19 Jun 2019 20:09:02 GMT + - Tue, 13 Aug 2019 17:44:46 GMT Server: - Microsoft-IIS/8.5 Microsoft-HTTPAPI/2.0 Vary: diff --git a/src/etools/applications/funds/views.py b/src/etools/applications/funds/views.py index fc925d9788..74cea96f45 100644 --- a/src/etools/applications/funds/views.py +++ b/src/etools/applications/funds/views.py @@ -8,6 +8,7 @@ from rest_framework.views import APIView from rest_framework_csv.renderers import CSVRenderer, JSONRenderer from unicef_restlib.views import QueryStringFilterMixin +from unicef_vision.exceptions import VisionException from etools.applications.core.mixins import ExportModelMixin from etools.applications.core.renderers import CSVFlatRenderer @@ -42,7 +43,6 @@ from etools.applications.partners.filters import PartnerScopeFilter from etools.applications.partners.models import Intervention from etools.applications.partners.permissions import PartnershipManagerPermission -from etools.applications.vision.exceptions import VisionSyncException class FRsView(APIView): @@ -63,20 +63,20 @@ def get(self, request, format=None): if len(values) > len(set(values)): return self.bad_request('You have duplicate records of the same FR, please make sure to add' ' each FR only one time') - qs = FundsReservationHeader.objects.filter(fr_number__in=values) - not_found = set(values) - set(qs.values_list('fr_number', flat=True)) if not_found: nf = list(not_found) + nf.sort() with transaction.atomic(): for delegated_fr in nf: # try to get this fr from vision try: sync_single_delegated_fr(request.user.profile.country.business_area_code, delegated_fr) - except VisionSyncException as e: + except VisionException as e: return self.bad_request('The FR {} could not be found in eTools and could not be synced ' 'from Vision. {}'.format(delegated_fr, e)) + qs._result_cache = None if intervention_id: diff --git a/src/etools/applications/hact/models.py b/src/etools/applications/hact/models.py index 4e0b9f62d8..8886bc2ea4 100644 --- a/src/etools/applications/hact/models.py +++ b/src/etools/applications/hact/models.py @@ -231,7 +231,8 @@ def get_assurance_activities(self): Q(partner__reported_cy__gt=0) | Q(partner__total_ct_cy__gt=0), partner__hidden=False, date_of_draft_report_to_ip__year=datetime.now().year).exclude( status=Engagement.CANCELLED).count(), - 'min_required': sum([p.min_req_spot_checks for p in self.get_queryset()]), + 'required': sum([p.planned_engagement.spot_check_required for p in self.get_queryset().filter( + planned_engagement__isnull=False)]), 'follow_up': self.get_queryset().aggregate(total=Coalesce(Sum( 'planned_engagement__spot_check_follow_up'), 0))['total'] }, diff --git a/src/etools/applications/hact/tests/test_models.py b/src/etools/applications/hact/tests/test_models.py index e0e833afc7..a974bcbf0c 100644 --- a/src/etools/applications/hact/tests/test_models.py +++ b/src/etools/applications/hact/tests/test_models.py @@ -1,4 +1,3 @@ - from datetime import datetime from etools.applications.audit.models import Audit, Engagement @@ -12,7 +11,7 @@ from etools.applications.core.tests.cases import BaseTenantTestCase from etools.applications.hact.tests.factories import AggregateHactFactory from etools.applications.partners.models import PartnerOrganization, PartnerType -from etools.applications.partners.tests.factories import PartnerFactory +from etools.applications.partners.tests.factories import PartnerFactory, PlannedEngagementFactory class TestAggregateHact(BaseTenantTestCase): @@ -45,6 +44,8 @@ def setUpTestData(cls): reported_cy=52000.0, total_ct_ytd=550000.0, ) + PlannedEngagementFactory(partner=cls.partner, spot_check_follow_up=3) + PlannedEngagementFactory(partner=cls.partner2, spot_check_follow_up=2) AuditFactory( status=Engagement.FINAL, @@ -126,7 +127,7 @@ def test_get_assurance_activities(self): self.assertEqual(assurance_activities['programmatic_visits']['completed'], 0) self.assertEqual(assurance_activities['programmatic_visits']['min_required'], 5) self.assertEqual(assurance_activities['spot_checks']['completed'], 0) - self.assertEqual(assurance_activities['spot_checks']['min_required'], 6) + self.assertEqual(assurance_activities['spot_checks']['required'], 6) self.assertEqual(assurance_activities['scheduled_audit'], 1) self.assertEqual(assurance_activities['special_audit'], 1) self.assertEqual(assurance_activities['micro_assessment'], 1) diff --git a/src/etools/applications/hact/views.py b/src/etools/applications/hact/views.py index 5de7241bdc..ded38abf11 100644 --- a/src/etools/applications/hact/views.py +++ b/src/etools/applications/hact/views.py @@ -86,8 +86,8 @@ def get(self, request, *args, **kwargs): partner_values['assurance_activities']['programmatic_visits']['min_required']), ('Assessment and Assurance Activities', 'Spot Checks: Completed', partner_values['assurance_activities']['spot_checks']['completed']), - ('Assessment and Assurance Activities', 'Spot Checks: Minimum Required', - partner_values['assurance_activities']['spot_checks']['min_required']), + ('Assessment and Assurance Activities', 'Spot Checks: Required', + partner_values['assurance_activities']['spot_checks']['required']), ('Assessment and Assurance Activities', 'Micro Assessments: Completed', partner_values['assurance_activities']['micro_assessment']), ('Assessment and Assurance Activities', 'Micro Assessments: Expiring', diff --git a/src/etools/applications/management/handlers.py b/src/etools/applications/management/handlers.py new file mode 100644 index 0000000000..6967c2e369 --- /dev/null +++ b/src/etools/applications/management/handlers.py @@ -0,0 +1,246 @@ +from django.db.transaction import atomic + +from etools.applications.action_points.models import ActionPoint +from etools.applications.management.models import SectionHistory +from etools.applications.partners.models import Intervention +from etools.applications.reports.models import AppliedIndicator, Section +from etools.applications.t2f.models import Travel +from etools.applications.tpm.models import TPMActivity, TPMVisit + + +class MigrationException(Exception): + """Exception thrown when migration is failing due validation""" + + +class NotDeactivatedException(MigrationException): + """Exception thrown when an active objects is still referenced by a inactive section""" + + +class IndicatorSectionInconsistentException(MigrationException): + """Exception thrown when indicator's section is not in the intervention""" + + +class SectionHandler: + + FK = 'fk' + M2M = 'm2m' + + intervention_updatable_status = [ + Intervention.DRAFT, + Intervention.SIGNED, + Intervention.ACTIVE, + Intervention.ENDED, + Intervention.SUSPENDED + ] + travel_updatable_status = [ + Travel.PLANNED, + Travel.SUBMITTED, + Travel.REJECTED, + Travel.APPROVED + ] + tpm_visit_updatable_status = [ + TPMVisit.DRAFT, + TPMVisit.ASSIGNED, + TPMVisit.ACCEPTED, + TPMVisit.REJECTED, + TPMVisit.REPORTED, + TPMVisit.REPORT_REJECTED + ] + + # dictionary to mark instances, for each model that has a m2m relationship to Sections, + # in order to follow up later and clean (remove references to old sections) them. + queryset_migration_mapping = { + 'interventions': ( + Intervention.objects.filter(status__in=intervention_updatable_status), + 'sections', + M2M + ), + 'applied_indicators': ( + AppliedIndicator.objects.filter(lower_result__result_link__intervention__status__in=intervention_updatable_status), + 'section', + FK + ), + 'travels': ( + Travel.objects.filter(status__in=travel_updatable_status), + 'section', + FK + ), + 'tpm_activities': ( + TPMActivity.objects.filter(tpm_visit__status__in=tpm_visit_updatable_status), + 'section', + FK + ), + 'action_points': ( + ActionPoint.objects.filter(status=ActionPoint.STATUS_OPEN), + 'section', + FK + ) + } + + @staticmethod + @atomic + def new(new_section_name): + """Create new section""" + section = Section.objects.create(name=new_section_name) + + # history + section_history = SectionHistory.objects.create(history_type=SectionHistory.CREATE) + section_history.to_sections.set([section]) + return section + + @staticmethod + @atomic + def merge(new_section_name, sections_to_merge): + """Merge two or more sections into a newly create section and migrating active objects""" + from_instances = Section.objects.filter(pk__in=sections_to_merge) + to_instance = Section.objects.create(name=new_section_name) + from_instances.update(active=False) + + # m2m relation need to be cleaned at the end + m2m_to_clean = { + } + for from_instance in from_instances: + m2m_to_clean = SectionHandler.__update_objects(from_instance, to_instance, m2m_to_clean) + SectionHandler.__clean_m2m(from_instances, m2m_to_clean) + + # history + section_history = SectionHistory.objects.create(history_type=SectionHistory.MERGE) + section_history.from_sections.set(from_instances) + section_history.to_sections.set([to_instance, ]) + + return to_instance + + @staticmethod + @atomic + def close(from_instance_pk, new_section_2_new_querysets): + """ + Close a section and split the active objects according the mapping dictionary + new_section_2_new_querysets has a mapping of the instances to be mapped to new section + { + "Nutrition": { + 'interventions': [1, 3], + 'applied_indicators': [1], + 'travels': [2, 4 ], + 'tpm_activities': [], + 'action_points': [3], + }, + "Health": { + 'interventions': [1, 5], + 'applied_indicators': [2], + 'travels': [4 ], + 'tpm_activities': [], + 'action_points': [1], + } + } + """ + + from_instance = Section.objects.get(pk=from_instance_pk) + from_instance.active = False + from_instance.save() + + new_sections = [] + # m2m relation need to be cleaned at the end. + # It's a dictionary to mark instances for each model in a Many to Many Relationshipm that we follow up for cleaning at the end of + m2m_to_clean = {} + for new_section_name, queryset_mapping_dict in new_section_2_new_querysets.items(): + to_instance, _ = Section.objects.get_or_create(name=new_section_name) + new_sections.append(to_instance) + m2m_to_clean = SectionHandler.__update_objects(from_instance, to_instance, m2m_to_clean, queryset_mapping_dict) + + SectionHandler.__clean_m2m([from_instance], m2m_to_clean) + + # history + section_history = SectionHistory.objects.create(history_type=SectionHistory.CLOSE) + section_history.from_sections.set([from_instance]) + section_history.to_sections.set(new_sections) + + SectionHandler.__disabled_section_check(from_instance) + SectionHandler.__consistent_indicators_check(new_sections) + + return new_sections + + @staticmethod + def __update_objects(from_instance, to_instance, m2m_to_clean, section_split_dict=None): + """ + It updates sections (from from_instance to to_instance) for all instances of all models defined in + queryset_migration_mapping as keys, following the filters defined in that mapping. + + Params: + from_instance: Section, instance of section to map from. + to_instance: Section, instance of Section model that is desired to map all instances of models defined in + queryset_migration_mapping + + m2m_to_clean: dict, key: model, value: list of pk of objects marked for removing the to_instance Section + + section_split_dict: dict, key: name of new section, value dict with model and pk of instances to migrate + "Nutrition": { + 'interventions': [1, 3], + 'applied_indicators': [1], + 'travels': [2, 4 ], + 'tpm_activities': [], + 'action_points': [3], + }, + """ + for model_key in SectionHandler.queryset_migration_mapping.keys(): + qs, section_attribute, relation = SectionHandler.queryset_migration_mapping[model_key] + + if section_split_dict: # if it's a close we filter the queryset + instance_pks = section_split_dict[model_key] if section_split_dict and model_key in section_split_dict else [] + qs = qs.filter(pk__in=instance_pks) + + if relation == SectionHandler.M2M: + to_update = SectionHandler.__update_m2m(qs, section_attribute, from_instance, to_instance) + if model_key not in m2m_to_clean: + m2m_to_clean[model_key] = [] + m2m_to_clean[model_key].extend(to_update) + + elif relation == SectionHandler.FK: + SectionHandler.__update_fk(qs, section_attribute, from_instance, to_instance) + return m2m_to_clean + + @staticmethod + def __update_fk(qs, section_attribute, old_section, new_section): + """Updates qs when section is a ForeignKey""" + qs.filter(**{section_attribute: old_section}).update(**{section_attribute: new_section}) + + @staticmethod + def __update_m2m(qs, section_attribute, old_section, new_section): + """Updates qs when section is a ManyToManyField""" + qs = qs.filter(**{section_attribute: old_section}) + for instance in qs: + attr = getattr(instance, section_attribute) + attr.add(new_section) + return qs.values_list('pk', flat=True) + + @staticmethod + def __clean_m2m(from_instances, m2m_to_clean): + """ + Clean m2m fields from old sections + NOTE: This step has to be done when the add of all section has been completed + """ + for model_key, to_clean in m2m_to_clean.items(): + qs, section_attribute, relation = SectionHandler.queryset_migration_mapping[model_key] + for instance in qs.filter(pk__in=to_clean): + attr = getattr(instance, section_attribute) + for from_instance in from_instances: + attr.remove(from_instance) + + @staticmethod + def __disabled_section_check(old_section): + """Checks that the old section is not referenced by any active objects""" + for _, (qs, attribute_name, _) in SectionHandler.queryset_migration_mapping.items(): + active_references = qs.filter(**{attribute_name: old_section}) + if active_references.exists(): + pks = ' '.join(str(pk) for pk in active_references.values_list('pk', flat=True)) + raise NotDeactivatedException(f'{qs.model.__name__} in active status exists {pks}') + + @staticmethod + def __consistent_indicators_check(new_sections): + """Checks that the old section is not referenced by any active objects""" + interventions = Intervention.objects.filter(sections__in=new_sections) + for intervention in interventions: + applied_indicator_sections = set(AppliedIndicator.objects.filter( + lower_result__result_link__intervention=intervention).values_list('section', flat=True).distinct()) + intervention_sections = set(intervention.sections.values_list('pk', flat=True)) + if not applied_indicator_sections.issubset(intervention_sections): + raise IndicatorSectionInconsistentException(f'Intervention {intervention.pk} has inconsistent indicators') diff --git a/src/etools/applications/management/migrations/0003_sectionhistory.py b/src/etools/applications/management/migrations/0003_sectionhistory.py new file mode 100644 index 0000000000..ef6e508596 --- /dev/null +++ b/src/etools/applications/management/migrations/0003_sectionhistory.py @@ -0,0 +1,33 @@ +# Generated by Django 2.2.3 on 2019-07-09 19:39 + +from django.db import migrations, models +import django.utils.timezone +import model_utils.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('reports', '0018_section_active'), + ('management', '0002_auto_20190326_1500'), + ] + + operations = [ + migrations.CreateModel( + name='SectionHistory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('history_type', models.CharField(max_length=500, verbose_name='Name')), + ('permission_type', models.CharField(choices=[('create', 'Create'), ('merge', 'Merge'), ('split', 'Split'), ('close', 'Close')], max_length=10)), + ('from_sections', models.ManyToManyField(blank=True, related_name='history_from', to='reports.Section', verbose_name='From')), + ('to_sections', models.ManyToManyField(blank=True, related_name='history_to', to='reports.Section', verbose_name='To')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/src/etools/applications/management/migrations/0004_auto_20190715_2047.py b/src/etools/applications/management/migrations/0004_auto_20190715_2047.py new file mode 100644 index 0000000000..e64688d940 --- /dev/null +++ b/src/etools/applications/management/migrations/0004_auto_20190715_2047.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.3 on 2019-07-15 20:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('management', '0003_sectionhistory'), + ] + + operations = [ + migrations.RemoveField( + model_name='sectionhistory', + name='permission_type', + ), + migrations.AlterField( + model_name='sectionhistory', + name='history_type', + field=models.CharField(choices=[('create', 'Create'), ('merge', 'Merge'), ('split', 'Split'), ('close', 'Close')], max_length=10, verbose_name='Name'), + ), + ] diff --git a/src/etools/applications/management/migrations/0005_auto_20190806_1400.py b/src/etools/applications/management/migrations/0005_auto_20190806_1400.py new file mode 100644 index 0000000000..84d0e15bd8 --- /dev/null +++ b/src/etools/applications/management/migrations/0005_auto_20190806_1400.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-08-06 14:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('management', '0004_auto_20190715_2047'), + ] + + operations = [ + migrations.AlterField( + model_name='sectionhistory', + name='history_type', + field=models.CharField(choices=[('create', 'Create'), ('merge', 'Merge'), ('close', 'Close')], max_length=10, verbose_name='Name'), + ), + ] diff --git a/src/etools/applications/management/models.py b/src/etools/applications/management/models.py new file mode 100644 index 0000000000..0ec8e0352e --- /dev/null +++ b/src/etools/applications/management/models.py @@ -0,0 +1,24 @@ +from django.db import models +from django.utils.translation import ugettext as _ + +from model_utils.models import TimeStampedModel + +from etools.applications.reports.models import Section + + +class SectionHistory(TimeStampedModel): + """ Model to keep history of Section changes""" + CREATE = 'create' + MERGE = 'merge' + CLOSE = 'close' + + TYPES = ( + (CREATE, 'Create'), + (MERGE, 'Merge'), + (CLOSE, 'Close'), + ) + + from_sections = models.ManyToManyField(Section, blank=True, verbose_name=_('From'), related_name='history_from') + to_sections = models.ManyToManyField(Section, blank=True, verbose_name=_('To'), related_name='history_to') + + history_type = models.CharField(verbose_name=_("Name"), max_length=10, choices=TYPES) diff --git a/src/etools/applications/management/tests/test_handlers.py b/src/etools/applications/management/tests/test_handlers.py new file mode 100644 index 0000000000..b6b644dc52 --- /dev/null +++ b/src/etools/applications/management/tests/test_handlers.py @@ -0,0 +1,177 @@ +from etools.applications.action_points.models import ActionPoint +from etools.applications.action_points.tests.factories import ActionPointFactory +from etools.applications.core.tests.cases import BaseTenantTestCase +from etools.applications.management.handlers import ( + IndicatorSectionInconsistentException, + NotDeactivatedException, + SectionHandler, +) +from etools.applications.management.models import SectionHistory +from etools.applications.partners.models import Intervention +from etools.applications.partners.tests.factories import InterventionFactory, InterventionResultLinkFactory +from etools.applications.reports.models import AppliedIndicator, Section +from etools.applications.reports.tests.factories import AppliedIndicatorFactory, SectionFactory +from etools.applications.t2f.models import Travel +from etools.applications.t2f.tests.factories import TravelFactory + + +class TestSectionHandler(BaseTenantTestCase): + @classmethod + def setUpTestData(cls): + cls.health = SectionFactory(name='Health') + cls.nutrition = SectionFactory(name='Nutrition') + cls.ict = SectionFactory(name='ICT') + + cls.intervention1 = InterventionFactory(status=Intervention.SIGNED) + cls.intervention2 = InterventionFactory(status=Intervention.CLOSED) + cls.intervention3 = InterventionFactory(status=Intervention.SIGNED) + + cls.intervention1.sections.set([cls.health, cls.nutrition]) + cls.intervention2.sections.set([cls.nutrition, cls.health]) + cls.intervention3.sections.set([cls.health, cls.nutrition, cls.ict]) + + cls.applied_indicator1 = AppliedIndicatorFactory( + lower_result__result_link=InterventionResultLinkFactory(intervention=cls.intervention1), + section=cls.health) + cls.applied_indicator2 = AppliedIndicatorFactory( + lower_result__result_link=InterventionResultLinkFactory(intervention=cls.intervention1), + section=cls.nutrition) + cls.applied_indicator3 = AppliedIndicatorFactory( + lower_result__result_link=InterventionResultLinkFactory(intervention=cls.intervention2), + section=cls.health) + cls.applied_indicator4 = AppliedIndicatorFactory( + lower_result__result_link=InterventionResultLinkFactory(intervention=cls.intervention3), + section=cls.ict) + cls.applied_indicator5 = AppliedIndicatorFactory( + lower_result__result_link=InterventionResultLinkFactory(intervention=cls.intervention3), + section=cls.health) + + cls.travel1 = TravelFactory(status=Travel.SUBMITTED, section=cls.health) + cls.travel2 = TravelFactory(status=Travel.REJECTED, section=cls.health) + cls.travel3 = TravelFactory(status=Travel.SUBMITTED, section=cls.ict) + TravelFactory(status=Travel.COMPLETED, section=cls.nutrition) + TravelFactory(status=Travel.COMPLETED, section=cls.health) + + cls.action_point = ActionPointFactory(status=ActionPoint.STATUS_OPEN, section=cls.health) + ActionPointFactory(status=ActionPoint.STATUS_COMPLETED, section=cls.nutrition) + + def __check_section(self, section, counts): + """Utility method to make more thin and readable tests""" + interventions, applied_indicators, travels, action_points = counts + self.assertEqual(Intervention.objects.filter(sections=section).count(), interventions) + self.assertEqual(AppliedIndicator.objects.filter(section=section).count(), applied_indicators) + self.assertEqual(Travel.objects.filter(section=section).count(), travels) + self.assertEqual(ActionPoint.objects.filter(section=section).count(), action_points) + + def __check_before(self): + """Checks the status before running the test""" + self.__check_section(self.health, (3, 3, 3, 1)) + self.__check_section(self.nutrition, (3, 1, 1, 1)) + self.__check_section(self.ict, (1, 1, 1, 0)) + + def __check_history(self, sections, deactivated, history_count, history_type, from_count, to_count): + """Checks the status before running the test""" + self.assertEqual(Section.objects.count(), sections) + self.assertEqual(Section.objects.filter(active=False).count(), deactivated) + self.assertEqual(SectionHistory.objects.count(), history_count) + if history_count: + self.assertEqual(SectionHistory.objects.first().history_type, history_type) + self.assertEqual(SectionHistory.objects.first().from_sections.count(), from_count) + self.assertEqual(SectionHistory.objects.first().to_sections.count(), to_count) + + def test_new(self): + self.__check_history(3, 0, 0, None, None, None) + SectionHandler.new('New Section') + self.__check_history(4, 0, 1, SectionHistory.CREATE, 0, 1) + self.assertEqual(SectionHistory.objects.first().to_sections.first().name, 'New Section') + + def test_merge(self): + self.__check_history(3, 0, 0, None, None, None) + self.__check_before() + + SectionHandler.merge('Health and Nutrition', [self.health.pk, self.nutrition.pk]) + + health_and_nutrition = Section.objects.get(name='Health and Nutrition') + self.__check_history(4, 2, 1, SectionHistory.MERGE, 2, 1) + self.__check_section(self.health, (1, 1, 1, 0)) + self.__check_section(self.nutrition, (1, 0, 1, 1)) + self.__check_section(health_and_nutrition, (2, 3, 2, 1)) + self.__check_section(self.ict, (1, 1, 1, 0)) + + def test_close_ok(self): + self.__check_history(3, 0, 0, None, None, None) + self.__check_before() + close_dict = { + "Health 1": { + 'interventions': [self.intervention1.pk, self.intervention3.pk], + 'applied_indicators': [self.applied_indicator5.pk], + 'travels': [self.travel2.pk, ], + 'tpm_activities': [], + 'action_points': [self.action_point.pk], + }, + "Health 2": { + 'interventions': [self.intervention1.pk], + 'applied_indicators': [self.applied_indicator1.pk], + 'travels': [self.travel1.pk, self.travel2.pk], + 'tpm_activities': [], + 'action_points': [], + } + } + + SectionHandler.close(self.health.pk, close_dict) + + hea = Section.objects.get(name='Health 1') + lth = Section.objects.get(name='Health 2') + + self.__check_history(5, 1, 1, SectionHistory.CLOSE, 1, 2) + self.__check_section(self.health, (1, 1, 1, 0)) + self.__check_section(self.nutrition, (3, 1, 1, 1)) + self.__check_section(hea, (2, 1, 1, 1)) + self.__check_section(lth, (1, 1, 1, 0)) # travel2 is updated twice last one stays + self.__check_section(self.ict, (1, 1, 1, 0)) + + def test_close_fail_not_migrated(self): + self.__check_history(3, 0, 0, None, None, None) + self.__check_before() + close_dict = { + "Health 1": { + 'interventions': [self.intervention1.pk], + 'applied_indicators': [self.applied_indicator5.pk], + 'travels': [self.travel2.pk, ], + 'tpm_activities': [], + 'action_points': [self.action_point.pk], + }, + "Health 2": { + 'interventions': [self.intervention1.pk], + 'applied_indicators': [self.applied_indicator1.pk], + 'travels': [self.travel1.pk, self.travel2.pk], + 'tpm_activities': [], + 'action_points': [], + } + } + with self.assertRaises(NotDeactivatedException): + SectionHandler.close(self.health.pk, close_dict) + self.__check_before() + + def test_close_fail_inconsistent_section(self): + self.__check_history(3, 0, 0, None, None, None) + self.__check_before() + close_dict = { + "Health 1": { + 'interventions': [self.intervention1.pk, self.intervention3.pk], + 'applied_indicators': [self.applied_indicator1.pk], + 'travels': [self.travel2.pk, ], + 'tpm_activities': [], + 'action_points': [self.action_point.pk], + }, + "Health 2": { + 'interventions': [self.intervention1.pk], + 'applied_indicators': [self.applied_indicator5.pk], + 'travels': [self.travel1.pk, self.travel2.pk], + 'tpm_activities': [], + 'action_points': [], + } + } + with self.assertRaises(IndicatorSectionInconsistentException): + SectionHandler.close(self.health.pk, close_dict) + self.__check_before() diff --git a/src/etools/applications/management/urls.py b/src/etools/applications/management/urls.py index 95b35251a5..661594eeed 100644 --- a/src/etools/applications/management/urls.py +++ b/src/etools/applications/management/urls.py @@ -1,7 +1,11 @@ from django.conf.urls import url +from django.urls import include + +from rest_framework import routers from etools.applications.management.views.general import InvalidateCache, SyncCountries, SyncFRs from etools.applications.management.views.reports import LoadResultStructure +from etools.applications.management.views.sections import SectionsManagementView from etools.applications.management.views.tasks_endpoints import ( PMPIndicatorsReportView, SyncAllUsers, @@ -13,6 +17,9 @@ ) from etools.applications.management.views.v1 import ActiveUsers, AgreementsStatisticsView, PortalDashView +router = routers.DefaultRouter() +router.register(r'sections', SectionsManagementView, base_name='sections_management') + app_name = 'management' @@ -32,4 +39,5 @@ url(r'^tasks/send_test_email/$', TestSendEmailAPIView.as_view(), name='tasks_send_test_email'), url(r'^reports/users/$', UsersReportView.as_view(), name='reports_users'), url(r'^reports/pmp_indicators/$', PMPIndicatorsReportView.as_view(), name='reports_pmp_indicators'), + url(r'^', include(router.urls)), ), 'management') diff --git a/src/etools/applications/management/views/sections.py b/src/etools/applications/management/views/sections.py new file mode 100644 index 0000000000..c9ac892095 --- /dev/null +++ b/src/etools/applications/management/views/sections.py @@ -0,0 +1,65 @@ +import logging + +from django.db import IntegrityError +from django.utils.translation import ugettext as _ + +from rest_framework import status, viewsets +from rest_framework.decorators import action +from rest_framework.response import Response + +from etools.applications.management.handlers import SectionHandler + +logger = logging.getLogger(__name__) + + +class SectionsManagementView(viewsets.ViewSet): + """Class for handling session creation, merging and closing""" + + @action(detail=False, methods=['post']) + def new(self, request): + try: + new_section_name = request.data['new_section_name'] + except KeyError: + return Response(_('Unable to unpack'), status=status.HTTP_400_BAD_REQUEST) + + try: + section = SectionHandler.new(new_section_name) + except IntegrityError as e: + return Response(str(e), status=status.HTTP_400_BAD_REQUEST) + + return Response({ + 'id': section.id, + 'name': section.name, + }) + + @action(detail=False, methods=['post']) + def merge(self, request): + try: + new_section_name = request.data['new_section_name'] + sections_to_merge = request.data['sections_to_merge'] + logger.info('Section to Merge', sections_to_merge) + except KeyError: + return Response(_('Unable to unpack'), status=status.HTTP_400_BAD_REQUEST) + try: + section = SectionHandler.merge(new_section_name, sections_to_merge) + except IntegrityError as e: + return Response(str(e), status=status.HTTP_400_BAD_REQUEST) + + return Response({ + 'id': section.id, + 'name': section.name, + }) + + @action(detail=False, methods=['post']) + def close(self, request): + try: + old_section = request.data['old_section'] + objects_dict = request.data['new_sections'] + + logger.info('Section Migrate', objects_dict) + except KeyError: + return Response(_('Unable to unpack'), status=status.HTTP_400_BAD_REQUEST) + + sections = SectionHandler.close(old_section, objects_dict) + data = [{'id': section.id, 'name': section.name} for section in sections] + return Response(data) diff --git a/src/etools/applications/partners/serializers/interventions_v2.py b/src/etools/applications/partners/serializers/interventions_v2.py index 211ee7a5e8..598a5f9fbc 100644 --- a/src/etools/applications/partners/serializers/interventions_v2.py +++ b/src/etools/applications/partners/serializers/interventions_v2.py @@ -28,6 +28,7 @@ from etools.applications.partners.permissions import InterventionPermissions from etools.applications.reports.models import AppliedIndicator, LowerResult, ReportingRequirement from etools.applications.reports.serializers.v2 import ( + AppliedIndicatorBasicSerializer, IndicatorSerializer, LowerResultCUSerializer, LowerResultSerializer, @@ -265,6 +266,27 @@ class Meta: ) +class InterventionToIndicatorsListSerializer(serializers.ModelSerializer): + partner_name = serializers.CharField(source='agreement.partner.name') + indicators = serializers.SerializerMethodField() + + def get_indicators(self, obj): + qs = [ai for rl in obj.result_links.all() for ll in rl.ll_results.all() for ai in ll.applied_indicators.all()] + return AppliedIndicatorBasicSerializer(qs, many=True).data + + class Meta: + model = Intervention + fields = ( + 'id', + 'title', + 'number', + 'partner_name', + 'status', + 'indicators', + 'sections' + ) + + class InterventionAttachmentSerializer(AttachmentSerializerMixin, serializers.ModelSerializer): attachment_file = serializers.FileField(source="attachment", read_only=True) attachment_document = AttachmentSingleFileField( diff --git a/src/etools/applications/partners/serializers/prp_v1.py b/src/etools/applications/partners/serializers/prp_v1.py index 6fb6b2ea89..e27e6b7bce 100644 --- a/src/etools/applications/partners/serializers/prp_v1.py +++ b/src/etools/applications/partners/serializers/prp_v1.py @@ -222,7 +222,6 @@ class Meta: class PRPInterventionListSerializer(serializers.ModelSerializer): - # todo: do these need to be lowercased? amendments = InterventionAmendmentSerializer(read_only=True, many=True) offices = serializers.SlugRelatedField(many=True, read_only=True, slug_field='name') business_area_code = serializers.SerializerMethodField() @@ -253,6 +252,25 @@ class PRPInterventionListSerializer(serializers.ModelSerializer): sections = SectionSerializer(many=True, read_only=True) locations = PRPLocationSerializer(source="flat_locations", many=True, read_only=True) + disbursement = serializers.DecimalField(source='frs__actual_amt_local__sum', read_only=True, + max_digits=20, + decimal_places=2) + + disbursement_percent = serializers.SerializerMethodField() + + def fr_currencies_ok(self, obj): + return obj.frs__currency__count == 1 if obj.frs__currency__count else None + + def get_disbursement_percent(self, obj): + if obj.frs__actual_amt_local__sum is None: + return None + + if not (self.fr_currencies_ok(obj) and obj.max_fr_currency == obj.planned_budget.currency): + return "%.1f" % -1.0 + percent = obj.frs__actual_amt_local__sum / obj.total_unicef_cash * 100 \ + if obj.total_unicef_cash and obj.total_unicef_cash > 0 else 0 + return "%.1f" % percent + def get_unicef_budget_currency(self, obj): # Intervention.planned_budget isn't a real field, it's a related # name from an InterventionBudget, and there might not be one. @@ -280,6 +298,7 @@ class Meta: fields = ( 'id', 'title', + 'document_type', 'business_area_code', 'offices', 'number', @@ -300,5 +319,7 @@ class Meta: 'amendments', 'locations', 'unicef_budget_cash', - 'unicef_budget_supplies' + 'unicef_budget_supplies', + 'disbursement', + 'disbursement_percent' ) diff --git a/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/fixtures.json b/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/fixtures.json index 2129ba09e8..e58ec5c14d 100644 --- a/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/fixtures.json +++ b/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/fixtures.json @@ -4,19 +4,19 @@ "model": "partners.intervention", "pk": 101, "fields": { - "created": "2018-12-05T16:22:57.668Z", - "modified": "2018-12-05T16:22:57.681Z", + "created": "2019-07-01T20:32:43.418Z", + "modified": "2019-07-01T20:32:43.421Z", "document_type": "", - "agreement": 599, + "agreement": 1572, "country_programme": null, - "number": "TST/PCA2018599/2018101", - "title": "Intervention Title 1", + "number": "TST/PCA20191572/2019101", + "title": "Intervention Title 0", "status": "draft", "start": null, "end": null, - "submission_date": "2018-12-05", + "submission_date": "2019-07-01", "submission_date_prc": null, - "reference_number_year": 2018, + "reference_number_year": 2019, "review_date_prc": null, "prc_review_document": "", "signed_pd_document": "", @@ -40,22 +40,22 @@ "deps": [ { "model": "partners.agreement", - "pk": 599, + "pk": 1572, "fields": { - "created": "2018-12-05T16:22:57.653Z", - "modified": "2018-12-05T16:22:57.666Z", - "partner": 572, - "country_programme": 546, + "created": "2019-07-01T20:32:43.395Z", + "modified": "2019-07-01T20:32:43.417Z", + "partner": 1496, + "country_programme": 1448, "agreement_type": "PCA", - "agreement_number": "TST/PCA2018599", + "agreement_number": "TST/PCA20191572", "attached_agreement": "", - "start": "2018-12-05", - "end": "2018-12-31", - "reference_number_year": 2018, + "start": "2019-07-01", + "end": "2019-12-31", + "reference_number_year": 2019, "special_conditions_pca": false, - "signed_by_unicef_date": "2018-12-05", + "signed_by_unicef_date": "2019-07-01", "signed_by": null, - "signed_by_partner_date": "2018-12-05", + "signed_by_partner_date": "2019-07-01", "partner_manager": null, "status": "signed", "authorized_officers": [] @@ -63,13 +63,13 @@ }, { "model": "partners.partnerorganization", - "pk": 572, + "pk": 1496, "fields": { - "created": "2018-12-05T16:22:57.648Z", - "modified": "2018-12-05T16:22:57.651Z", + "created": "2019-07-01T20:32:43.381Z", + "modified": "2019-07-01T20:32:43.387Z", "partner_type": "", "cso_type": null, - "name": "Partner 5", + "name": "Partner 0", "short_name": "", "description": "", "shared_with": null, @@ -138,13 +138,13 @@ }, { "model": "reports.countryprogramme", - "pk": 546, + "pk": 1448, "fields": { - "name": "Country Programme 5", - "wbs": "0000/A0/05", + "name": "Country Programme 0", + "wbs": "0000/A0/00", "invalid": false, - "from_date": "2018-01-01", - "to_date": "2018-12-31" + "from_date": "2019-01-01", + "to_date": "2019-12-31" } } ] @@ -152,36 +152,36 @@ "amendment": { "master": { "model": "partners.interventionamendment", - "pk": 35, + "pk": 42, "fields": { - "created": "2018-12-05T16:22:57.709Z", - "modified": "2018-12-05T16:22:57.726Z", - "intervention": 508, - "types": "[\"Change IP name\"]", - "other_description": "VJppZjLpmvqgiEiisnLUDfGzhjjeCDBpaJYLdRdZTNYwevzYtd", - "signed_date": "2018-12-05", + "created": "2019-07-01T20:32:43.465Z", + "modified": "2019-07-01T20:32:43.501Z", + "intervention": 1455, + "types": "[\"Change authorized officer\"]", + "other_description": "pxRKBufTiSdqiGAOyXwghKOpNAMJCXkyyvpGCFZerSqsemMYaY", + "signed_date": "2019-07-01", "amendment_number": 1, - "signed_amendment": "test/file_attachments/partner_organization/573/573/agreements/600/interventions/508/amendments/None/test_file.pdf" + "signed_amendment": "test/file_attachments/partner_organization/1497/1497/agreements/1573/interventions/1455/amendments/None/test_file.pdf" } }, "deps": [ { "model": "partners.intervention", - "pk": 508, + "pk": 1455, "fields": { - "created": "2018-12-05T16:22:57.707Z", - "modified": "2018-12-05T16:22:57.725Z", + "created": "2019-07-01T20:32:43.453Z", + "modified": "2019-07-01T20:32:43.497Z", "document_type": "", - "agreement": 600, + "agreement": 1573, "country_programme": null, - "number": "TST/PCA2018600/2018508", - "title": "Intervention Title 2", + "number": "TST/PCA20191573/20191455", + "title": "Intervention Title 1", "status": "draft", "start": null, "end": null, - "submission_date": "2018-12-05", + "submission_date": "2019-07-01", "submission_date_prc": null, - "reference_number_year": 2018, + "reference_number_year": 2019, "review_date_prc": null, "prc_review_document": "", "signed_pd_document": "", @@ -204,22 +204,22 @@ }, { "model": "partners.agreement", - "pk": 600, + "pk": 1573, "fields": { - "created": "2018-12-05T16:22:57.692Z", - "modified": "2018-12-05T16:22:57.706Z", - "partner": 573, - "country_programme": 547, + "created": "2019-07-01T20:32:43.437Z", + "modified": "2019-07-01T20:32:43.451Z", + "partner": 1497, + "country_programme": 1449, "agreement_type": "PCA", - "agreement_number": "TST/PCA2018600", + "agreement_number": "TST/PCA20191573", "attached_agreement": "", - "start": "2018-12-05", - "end": "2018-12-31", - "reference_number_year": 2018, + "start": "2019-07-01", + "end": "2019-12-31", + "reference_number_year": 2019, "special_conditions_pca": false, - "signed_by_unicef_date": "2018-12-05", + "signed_by_unicef_date": "2019-07-01", "signed_by": null, - "signed_by_partner_date": "2018-12-05", + "signed_by_partner_date": "2019-07-01", "partner_manager": null, "status": "signed", "authorized_officers": [] @@ -227,13 +227,13 @@ }, { "model": "partners.partnerorganization", - "pk": 573, + "pk": 1497, "fields": { - "created": "2018-12-05T16:22:57.683Z", - "modified": "2018-12-05T16:22:57.687Z", + "created": "2019-07-01T20:32:43.426Z", + "modified": "2019-07-01T20:32:43.432Z", "partner_type": "", "cso_type": null, - "name": "Partner 6", + "name": "Partner 1", "short_name": "", "description": "", "shared_with": null, @@ -302,13 +302,13 @@ }, { "model": "reports.countryprogramme", - "pk": 547, + "pk": 1449, "fields": { - "name": "Country Programme 6", - "wbs": "0000/A0/06", + "name": "Country Programme 1", + "wbs": "0000/A0/01", "invalid": false, - "from_date": "2018-01-01", - "to_date": "2018-12-31" + "from_date": "2019-01-01", + "to_date": "2019-12-31" } } ] @@ -316,33 +316,33 @@ "result": { "master": { "model": "partners.interventionresultlink", - "pk": 93, + "pk": 889, "fields": { - "created": "2018-12-05T16:22:57.747Z", - "modified": "2018-12-05T16:22:57.748Z", - "intervention": 509, - "cp_output": 137, + "created": "2019-07-01T20:32:43.555Z", + "modified": "2019-07-01T20:32:43.555Z", + "intervention": 1456, + "cp_output": 953, "ram_indicators": [] } }, "deps": [ { "model": "partners.intervention", - "pk": 509, + "pk": 1456, "fields": { - "created": "2018-12-05T16:22:57.741Z", - "modified": "2018-12-05T16:22:57.742Z", + "created": "2019-07-01T20:32:43.538Z", + "modified": "2019-07-01T20:32:43.541Z", "document_type": "", - "agreement": 601, + "agreement": 1574, "country_programme": null, - "number": "TST/PCA2018601/2018509", - "title": "Intervention Title 3", + "number": "TST/PCA20191574/20191456", + "title": "Intervention Title 2", "status": "draft", "start": null, "end": null, - "submission_date": "2018-12-05", + "submission_date": "2019-07-01", "submission_date_prc": null, - "reference_number_year": 2018, + "reference_number_year": 2019, "review_date_prc": null, "prc_review_document": "", "signed_pd_document": "", @@ -365,22 +365,22 @@ }, { "model": "partners.agreement", - "pk": 601, + "pk": 1574, "fields": { - "created": "2018-12-05T16:22:57.732Z", - "modified": "2018-12-05T16:22:57.740Z", - "partner": 574, - "country_programme": 548, + "created": "2019-07-01T20:32:43.510Z", + "modified": "2019-07-01T20:32:43.537Z", + "partner": 1498, + "country_programme": 1450, "agreement_type": "PCA", - "agreement_number": "TST/PCA2018601", + "agreement_number": "TST/PCA20191574", "attached_agreement": "", - "start": "2018-12-05", - "end": "2018-12-31", - "reference_number_year": 2018, + "start": "2019-07-01", + "end": "2019-12-31", + "reference_number_year": 2019, "special_conditions_pca": false, - "signed_by_unicef_date": "2018-12-05", + "signed_by_unicef_date": "2019-07-01", "signed_by": null, - "signed_by_partner_date": "2018-12-05", + "signed_by_partner_date": "2019-07-01", "partner_manager": null, "status": "signed", "authorized_officers": [] @@ -388,13 +388,13 @@ }, { "model": "partners.partnerorganization", - "pk": 574, + "pk": 1498, "fields": { - "created": "2018-12-05T16:22:57.728Z", - "modified": "2018-12-05T16:22:57.730Z", + "created": "2019-07-01T20:32:43.504Z", + "modified": "2019-07-01T20:32:43.507Z", "partner_type": "", "cso_type": null, - "name": "Partner 7", + "name": "Partner 2", "short_name": "", "description": "", "shared_with": null, @@ -463,28 +463,30 @@ }, { "model": "reports.countryprogramme", - "pk": 548, + "pk": 1450, "fields": { - "name": "Country Programme 7", - "wbs": "0000/A0/07", + "name": "Country Programme 2", + "wbs": "0000/A0/02", "invalid": false, - "from_date": "2018-01-01", - "to_date": "2018-12-31" + "from_date": "2019-01-01", + "to_date": "2019-12-31" } }, { "model": "reports.result", - "pk": 137, + "pk": 953, "fields": { "country_programme": null, - "result_type": 118, + "result_type": 915, "sector": null, "name": "Result 0", "code": null, - "from_date": "2018-01-01", - "to_date": "2018-12-31", + "from_date": "2019-01-01", + "to_date": "2019-12-31", "parent": null, "humanitarian_tag": false, + "humanitarian_marker_code": null, + "humanitarian_marker_name": null, "wbs": null, "vision_id": null, "gic_code": null, @@ -495,8 +497,8 @@ "activity_focus_name": null, "hidden": false, "ram": false, - "created": "2018-12-05T16:22:57.745Z", - "modified": "2018-12-05T16:22:57.746Z", + "created": "2019-07-01T20:32:43.549Z", + "modified": "2019-07-01T20:32:43.552Z", "lft": 1, "rght": 2, "tree_id": 1, @@ -505,7 +507,7 @@ }, { "model": "reports.resulttype", - "pk": 118, + "pk": 915, "fields": { "name": "ResultType 0" } diff --git a/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_.response.json b/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_.response.json index f4c5c938fe..fddeeabe97 100644 --- a/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_.response.json +++ b/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_.response.json @@ -19,17 +19,17 @@ ], "content-length": [ "Content-Length", - "2404" + "2411" ] }, "data": [ { - "id": 509, - "number": "TST/PCA2018601/2018509", + "id": 1456, + "number": "TST/PCA20191574/20191456", "document_type": "", - "partner_name": "Partner 7", + "partner_name": "Partner 2", "status": "draft", - "title": "Intervention Title 3", + "title": "Intervention Title 2", "start": null, "end": null, "frs_total_frs_amt": null, @@ -41,7 +41,7 @@ "sections": [], "section_names": [], "cp_outputs": [ - 137 + 953 ], "unicef_focal_points": [], "frs_total_intervention_amt": null, @@ -64,12 +64,12 @@ "grants": [] }, { - "id": 508, - "number": "TST/PCA2018600/2018508", + "id": 1455, + "number": "TST/PCA20191573/20191455", "document_type": "", - "partner_name": "Partner 6", + "partner_name": "Partner 1", "status": "draft", - "title": "Intervention Title 2", + "title": "Intervention Title 1", "start": null, "end": null, "frs_total_frs_amt": null, @@ -103,11 +103,11 @@ }, { "id": 101, - "number": "TST/PCA2018599/2018101", + "number": "TST/PCA20191572/2019101", "document_type": "", - "partner_name": "Partner 5", + "partner_name": "Partner 0", "status": "draft", - "title": "Intervention Title 1", + "title": "Intervention Title 0", "start": null, "end": null, "frs_total_frs_amt": null, diff --git a/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_101_.response.json b/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_101_.response.json index 2c2fdba8f2..cc4f91c2f7 100644 --- a/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_101_.response.json +++ b/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_101_.response.json @@ -19,16 +19,16 @@ ], "content-length": [ "Content-Length", - "5533" + "5536" ] }, "data": { "id": 101, "frs": [], - "partner": "Partner 5", - "agreement": 599, + "partner": "Partner 0", + "agreement": 1572, "document_type": "", - "number": "TST/PCA2018599/2018101", + "number": "TST/PCA20191572/2019101", "prc_review_document_file": null, "frs_details": { "frs": [], @@ -42,13 +42,13 @@ "multi_curr_flag": false }, "signed_pd_document_file": null, - "title": "Intervention Title 1", + "title": "Intervention Title 0", "status": "draft", "start": null, "end": null, "submission_date_prc": null, "review_date_prc": null, - "submission_date": "2018-12-05", + "submission_date": "2019-07-01", "prc_review_document": null, "submitted_to_prc": false, "signed_pd_document": null, @@ -60,8 +60,8 @@ "offices": [], "population_focus": null, "signed_by_partner_date": null, - "created": "2018-12-05T16:22:57.668000Z", - "modified": "2018-12-05T16:22:57.681000Z", + "created": "2019-07-01T20:32:43.418000Z", + "modified": "2019-07-01T20:32:43.421000Z", "planned_budget": null, "result_links": [], "country_programme": null, @@ -71,175 +71,175 @@ "attachments": [], "permissions": { "edit": { - "reporting_requirements": false, - "signed_pd_attachment": false, - "activation_letter_attachment": false, - "partner_authorized_officer_signatory_id": true, - "metadata": true, - "reference_number_year": true, - "prc_review_attachment": false, - "attachments": true, - "modified": true, - "status": true, - "result_links": false, - "planned_budget": false, - "review_date_prc": false, - "in_amendment": false, - "end": false, - "amendments": false, - "prc_review_document": false, - "unicef_signatory_id": true, - "agreement": false, - "actionpoint": true, "start": false, - "sections": false, - "partner_authorized_officer_signatory": false, - "signed_by_partner_date": false, - "title": false, + "document_type": false, + "activation_letter": false, + "offices": false, + "engagement": true, + "agreement": false, + "submission_date": false, + "signed_by_unicef_date": false, + "in_amendment": false, + "planned_budget": false, + "result_links": false, + "number": false, + "prc_review_attachment": false, + "activation_letter_attachment": false, "submission_date_prc": false, "id": true, + "unicef_focal_points": false, "special_reporting_requirements": true, - "country_programme": false, - "contingency_pd": false, - "termination_doc_attachment": true, - "unicef_signatory": false, + "reporting_periods": true, + "title": false, + "termination_doc": false, "frs": false, + "partner_authorized_officer_signatory": false, + "sections": false, + "signed_pd_document": false, + "unicef_signatory": false, + "termination_doc_attachment": true, + "signed_by_partner_date": false, + "planned_visits": false, + "actionpoint": true, "created": true, + "contingency_pd": false, + "partner_authorized_officer_signatory_id": true, + "prc_review_document": false, + "end": false, + "activity": true, + "country_programme": false, "country_programme_id": true, - "submission_date": false, - "planned_visits": false, - "termination_doc": false, - "flat_locations": true, - "unicef_focal_points": false, - "activation_letter": false, - "signed_by_unicef_date": false, - "reporting_periods": true, + "amendments": false, + "reference_number_year": true, + "partner_focal_points": false, + "unicef_signatory_id": true, "travel_activities": true, - "population_focus": true, + "metadata": true, + "modified": true, + "attachments": true, "agreement_id": true, - "activity": true, - "partner_focal_points": false, - "offices": false, - "signed_pd_document": false, - "document_type": false, - "number": false, - "engagement": true, + "flat_locations": true, + "reporting_requirements": false, + "review_date_prc": false, + "signed_pd_attachment": false, + "status": true, + "population_focus": true, "sections_present": true }, "required": { - "reporting_requirements": false, - "signed_pd_attachment": false, - "activation_letter_attachment": false, - "partner_authorized_officer_signatory_id": false, - "metadata": false, - "reference_number_year": true, - "prc_review_attachment": false, - "attachments": false, - "modified": false, - "status": false, - "result_links": false, - "planned_budget": false, - "review_date_prc": false, - "in_amendment": false, - "end": false, - "amendments": false, - "prc_review_document": false, - "unicef_signatory_id": false, - "agreement": true, - "actionpoint": false, "start": false, - "sections": false, - "partner_authorized_officer_signatory": false, - "signed_by_partner_date": false, - "title": true, + "document_type": true, + "activation_letter": false, + "offices": false, + "engagement": false, + "agreement": true, + "submission_date": false, + "signed_by_unicef_date": false, + "in_amendment": false, + "planned_budget": false, + "result_links": false, + "number": true, + "prc_review_attachment": false, + "activation_letter_attachment": false, "submission_date_prc": false, "id": false, + "unicef_focal_points": false, "special_reporting_requirements": false, - "country_programme": false, - "contingency_pd": false, - "termination_doc_attachment": false, - "unicef_signatory": false, + "reporting_periods": false, + "title": true, + "termination_doc": false, "frs": false, + "partner_authorized_officer_signatory": false, + "sections": false, + "signed_pd_document": false, + "unicef_signatory": false, + "termination_doc_attachment": false, + "signed_by_partner_date": false, + "planned_visits": false, + "actionpoint": false, "created": false, + "contingency_pd": false, + "partner_authorized_officer_signatory_id": false, + "prc_review_document": false, + "end": false, + "activity": false, + "country_programme": false, "country_programme_id": false, - "submission_date": false, - "planned_visits": false, - "termination_doc": false, - "flat_locations": false, - "unicef_focal_points": false, - "activation_letter": false, - "signed_by_unicef_date": false, - "reporting_periods": false, + "amendments": false, + "reference_number_year": true, + "partner_focal_points": false, + "unicef_signatory_id": false, "travel_activities": false, - "population_focus": false, + "metadata": false, + "modified": false, + "attachments": false, "agreement_id": false, - "activity": false, - "partner_focal_points": false, - "offices": false, - "signed_pd_document": false, - "document_type": true, - "number": true, - "engagement": false, + "flat_locations": false, + "reporting_requirements": false, + "review_date_prc": false, + "signed_pd_attachment": false, + "status": false, + "population_focus": false, "sections_present": false }, "view": { - "reporting_requirements": false, - "signed_pd_attachment": false, - "activation_letter_attachment": false, - "partner_authorized_officer_signatory_id": true, - "metadata": true, - "reference_number_year": true, - "prc_review_attachment": false, - "attachments": true, - "modified": true, - "status": true, - "result_links": true, - "planned_budget": false, - "review_date_prc": false, - "in_amendment": false, - "end": false, - "amendments": false, - "prc_review_document": false, - "unicef_signatory_id": true, - "agreement": false, - "actionpoint": true, "start": false, - "sections": false, - "partner_authorized_officer_signatory": false, - "signed_by_partner_date": false, - "title": false, + "document_type": false, + "activation_letter": false, + "offices": false, + "engagement": true, + "agreement": false, + "submission_date": false, + "signed_by_unicef_date": false, + "in_amendment": false, + "planned_budget": false, + "result_links": true, + "number": false, + "prc_review_attachment": false, + "activation_letter_attachment": false, "submission_date_prc": false, "id": true, + "unicef_focal_points": false, "special_reporting_requirements": true, - "country_programme": false, - "contingency_pd": false, - "termination_doc_attachment": true, - "unicef_signatory": false, + "reporting_periods": true, + "title": false, + "termination_doc": false, "frs": false, + "partner_authorized_officer_signatory": false, + "sections": false, + "signed_pd_document": false, + "unicef_signatory": false, + "termination_doc_attachment": true, + "signed_by_partner_date": false, + "planned_visits": false, + "actionpoint": true, "created": true, + "contingency_pd": false, + "partner_authorized_officer_signatory_id": true, + "prc_review_document": false, + "end": false, + "activity": true, + "country_programme": false, "country_programme_id": true, - "submission_date": false, - "planned_visits": false, - "termination_doc": false, - "flat_locations": false, - "unicef_focal_points": false, - "activation_letter": false, - "signed_by_unicef_date": false, - "reporting_periods": true, + "amendments": false, + "reference_number_year": true, + "partner_focal_points": false, + "unicef_signatory_id": true, "travel_activities": true, - "population_focus": true, + "metadata": true, + "modified": true, + "attachments": true, "agreement_id": true, - "activity": true, - "partner_focal_points": false, - "offices": false, - "signed_pd_document": false, - "document_type": false, - "number": false, - "engagement": true, + "flat_locations": false, + "reporting_requirements": false, + "review_date_prc": false, + "signed_pd_attachment": false, + "status": true, + "population_focus": true, "sections_present": false } }, - "partner_id": "572", + "partner_id": "1496", "sections": [], "planned_visits": [], "locations": [], @@ -258,7 +258,7 @@ "days_from_submission_to_signed": "Not fully signed", "days_from_review_to_signed": "Not Reviewed", "partner_vendor": null, - "reference_number_year": 2018, + "reference_number_year": 2019, "activation_letter_file": null, "activation_letter_attachment": null, "termination_doc_file": null, diff --git a/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_amendments_.response.json b/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_amendments_.response.json index f323c35028..f5e618e5ba 100644 --- a/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_amendments_.response.json +++ b/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_amendments_.response.json @@ -19,25 +19,25 @@ ], "content-length": [ "Content-Length", - "646" + "666" ] }, "data": [ { - "id": 35, + "id": 42, "amendment_number": "1", - "signed_amendment_file": "http://testserver/media/test/file_attachments/partner_organization/573/573/agreements/600/interventions/508/amendments/None/test_file.pdf", + "signed_amendment_file": "http://testserver/media/test/file_attachments/partner_organization/1497/1497/agreements/1573/interventions/1455/amendments/None/test_file.pdf", "signed_amendment_attachment": null, "internal_prc_review": null, - "created": "2018-12-05T16:22:57.709000Z", - "modified": "2018-12-05T16:22:57.726000Z", + "created": "2019-07-01T20:32:43.465000Z", + "modified": "2019-07-01T20:32:43.501000Z", "types": [ - "Change IP name" + "Change authorized officer" ], - "other_description": "VJppZjLpmvqgiEiisnLUDfGzhjjeCDBpaJYLdRdZTNYwevzYtd", - "signed_date": "2018-12-05", - "signed_amendment": "http://testserver/media/test/file_attachments/partner_organization/573/573/agreements/600/interventions/508/amendments/None/test_file.pdf", - "intervention": 508 + "other_description": "pxRKBufTiSdqiGAOyXwghKOpNAMJCXkyyvpGCFZerSqsemMYaY", + "signed_date": "2019-07-01", + "signed_amendment": "http://testserver/media/test/file_attachments/partner_organization/1497/1497/agreements/1573/interventions/1455/amendments/None/test_file.pdf", + "intervention": 1455 } ], "content_type": null diff --git a/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_applied-indicators_.response.json b/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_applied-indicators_.response.json new file mode 100644 index 0000000000..fe26545715 --- /dev/null +++ b/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_applied-indicators_.response.json @@ -0,0 +1,55 @@ +{ + "status_code": 200, + "headers": { + "content-type": [ + "Content-Type", + "application/json" + ], + "vary": [ + "Vary", + "Accept, Origin, Cookie" + ], + "allow": [ + "Allow", + "GET" + ], + "x-frame-options": [ + "X-Frame-Options", + "SAMEORIGIN" + ], + "content-length": [ + "Content-Length", + "458" + ] + }, + "data": [ + { + "id": 1456, + "title": "Intervention Title 2", + "number": "TST/PCA20191574/20191456", + "partner_name": "Partner 2", + "status": "draft", + "indicators": [], + "sections": [] + }, + { + "id": 1455, + "title": "Intervention Title 1", + "number": "TST/PCA20191573/20191455", + "partner_name": "Partner 1", + "status": "draft", + "indicators": [], + "sections": [] + }, + { + "id": 101, + "title": "Intervention Title 0", + "number": "TST/PCA20191572/2019101", + "partner_name": "Partner 0", + "status": "draft", + "indicators": [], + "sections": [] + } + ], + "content_type": null +} \ No newline at end of file diff --git a/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_indicators_.response.json b/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_indicators_.response.json index bbccbb0bed..72bca7da1e 100644 --- a/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_indicators_.response.json +++ b/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_indicators_.response.json @@ -19,12 +19,12 @@ ], "content-length": [ "Content-Length", - "42" + "43" ] }, "data": [ { - "intervention": 509, + "intervention": 1456, "ram_indicators": [] } ], diff --git a/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_map_.response.json b/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_map_.response.json index 8c7d907b24..7214c79013 100644 --- a/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_map_.response.json +++ b/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api/TestAPIIntervention/get__api_v2_interventions_map_.response.json @@ -19,18 +19,18 @@ ], "content-length": [ "Content-Length", - "918" + "931" ] }, "data": [ { - "id": 509, - "partner_id": "574", - "partner_name": "Partner 7", - "agreement": 601, + "id": 1456, + "partner_id": "1498", + "partner_name": "Partner 2", + "agreement": 1574, "document_type": "", - "number": "TST/PCA2018601/2018509", - "title": "Intervention Title 3", + "number": "TST/PCA20191574/20191456", + "title": "Intervention Title 2", "status": "draft", "start": null, "end": null, @@ -43,13 +43,13 @@ "frs": [] }, { - "id": 508, - "partner_id": "573", - "partner_name": "Partner 6", - "agreement": 600, + "id": 1455, + "partner_id": "1497", + "partner_name": "Partner 1", + "agreement": 1573, "document_type": "", - "number": "TST/PCA2018600/2018508", - "title": "Intervention Title 2", + "number": "TST/PCA20191573/20191455", + "title": "Intervention Title 1", "status": "draft", "start": null, "end": null, @@ -63,12 +63,12 @@ }, { "id": 101, - "partner_id": "572", - "partner_name": "Partner 5", - "agreement": 599, + "partner_id": "1496", + "partner_name": "Partner 0", + "agreement": 1572, "document_type": "", - "number": "TST/PCA2018599/2018101", - "title": "Intervention Title 1", + "number": "TST/PCA20191572/2019101", + "title": "Intervention Title 0", "status": "draft", "start": null, "end": null, diff --git a/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api_prp/TestAPIPRP/fixtures.json b/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api_prp/TestAPIPRP/fixtures.json new file mode 100644 index 0000000000..9d7c2db94b --- /dev/null +++ b/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api_prp/TestAPIPRP/fixtures.json @@ -0,0 +1,2382 @@ +{ + "unicef_staff": { + "master": { + "model": "users.user", + "pk": 6664, + "fields": { + "created": "2019-09-06T09:50:26.851Z", + "modified": "2019-09-06T09:50:26.862Z", + "username": "QvnLzWzvHUSg", + "email": "staff@unicef.org", + "password": "md5$EcJak3vWmN4B$97409c09f89ec7a732d1938241774a34", + "first_name": "", + "middle_name": "", + "last_name": "", + "date_joined": "2019-09-06T09:50:26.851Z", + "last_login": null, + "is_active": true, + "is_staff": true, + "is_superuser": false, + "groups": [ + 410 + ], + "user_permissions": [] + } + }, + "deps": [ + { + "model": "auth.group", + "pk": 410, + "fields": { + "name": "UNICEF User", + "permissions": [] + } + } + ] + }, + "partnership_manager_user": { + "master": { + "model": "users.user", + "pk": 6663, + "fields": { + "created": "2019-09-06T09:50:26.785Z", + "modified": "2019-09-06T09:50:26.801Z", + "username": "HdBYgeVGdsOP", + "email": "partner@unicef.org", + "password": "md5$5iBjdXIbedGz$8c4962c3a7a2637d84054af33c0ba9f7", + "first_name": "", + "middle_name": "", + "last_name": "", + "date_joined": "2019-09-06T09:50:26.785Z", + "last_login": null, + "is_active": true, + "is_staff": true, + "is_superuser": false, + "groups": [ + 410, + 411 + ], + "user_permissions": [] + } + }, + "deps": [ + { + "model": "auth.group", + "pk": 410, + "fields": { + "name": "UNICEF User", + "permissions": [] + } + }, + { + "model": "auth.group", + "pk": 411, + "fields": { + "name": "Partnership Manager", + "permissions": [] + } + } + ] + }, + "partner": { + "master": { + "model": "partners.partnerorganization", + "pk": 2014, + "fields": { + "created": "2019-09-06T09:50:26.805Z", + "modified": "2019-09-06T09:50:26.809Z", + "partner_type": "", + "cso_type": null, + "name": "Partner 1", + "short_name": "", + "description": "", + "shared_with": null, + "street_address": null, + "city": null, + "postal_code": null, + "country": null, + "address": null, + "email": null, + "phone_number": null, + "vendor_number": "VP1", + "alternate_id": null, + "alternate_name": null, + "rating": null, + "type_of_assessment": null, + "last_assessment_date": null, + "core_values_assessment_date": null, + "vision_synced": false, + "blocked": false, + "deleted_flag": false, + "manually_blocked": false, + "hidden": false, + "total_ct_cp": null, + "total_ct_cy": null, + "net_ct_cy": null, + "reported_cy": null, + "total_ct_ytd": null, + "outstanding_dct_amount_6_to_9_months_usd": null, + "outstanding_dct_amount_more_than_9_months_usd": null, + "hact_values": { + "audits": { + "completed": 0, + "minimum_requirements": 0 + }, + "spot_checks": { + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "follow_up_required": 0 + }, + "assurance_coverage": "void", + "programmatic_visits": { + "planned": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + } + }, + "outstanding_findings": 0 + }, + "basis_for_risk_rating": "" + } + }, + "deps": [] + }, + "partner1": { + "master": { + "model": "partners.partnerorganization", + "pk": 2015, + "fields": { + "created": "2019-09-06T09:50:26.810Z", + "modified": "2019-09-06T09:50:26.813Z", + "partner_type": "", + "cso_type": null, + "name": "Partner 2", + "short_name": "", + "description": "", + "shared_with": null, + "street_address": null, + "city": null, + "postal_code": null, + "country": null, + "address": null, + "email": null, + "phone_number": null, + "vendor_number": null, + "alternate_id": null, + "alternate_name": null, + "rating": null, + "type_of_assessment": null, + "last_assessment_date": null, + "core_values_assessment_date": null, + "vision_synced": false, + "blocked": false, + "deleted_flag": false, + "manually_blocked": false, + "hidden": false, + "total_ct_cp": null, + "total_ct_cy": null, + "net_ct_cy": null, + "reported_cy": null, + "total_ct_ytd": null, + "outstanding_dct_amount_6_to_9_months_usd": null, + "outstanding_dct_amount_more_than_9_months_usd": null, + "hact_values": { + "audits": { + "completed": 0, + "minimum_requirements": 0 + }, + "spot_checks": { + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "follow_up_required": 0 + }, + "assurance_coverage": "void", + "programmatic_visits": { + "planned": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + } + }, + "outstanding_findings": 0 + }, + "basis_for_risk_rating": "" + } + }, + "deps": [] + }, + "agreement": { + "master": { + "model": "partners.agreement", + "pk": 1979, + "fields": { + "created": "2019-09-06T09:50:26.816Z", + "modified": "2019-09-06T09:50:26.839Z", + "partner": 2014, + "country_programme": 1979, + "agreement_type": "PCA", + "agreement_number": "TST/PCA20191979", + "attached_agreement": "", + "start": "2019-09-06", + "end": "2019-12-31", + "reference_number_year": 2019, + "special_conditions_pca": false, + "signed_by_unicef_date": "2019-09-06", + "signed_by": null, + "signed_by_partner_date": "2019-09-06", + "partner_manager": null, + "status": "signed", + "authorized_officers": [] + } + }, + "deps": [ + { + "model": "partners.partnerorganization", + "pk": 2014, + "fields": { + "created": "2019-09-06T09:50:26.805Z", + "modified": "2019-09-06T09:50:26.809Z", + "partner_type": "", + "cso_type": null, + "name": "Partner 1", + "short_name": "", + "description": "", + "shared_with": null, + "street_address": null, + "city": null, + "postal_code": null, + "country": null, + "address": null, + "email": null, + "phone_number": null, + "vendor_number": "VP1", + "alternate_id": null, + "alternate_name": null, + "rating": null, + "type_of_assessment": null, + "last_assessment_date": null, + "core_values_assessment_date": null, + "vision_synced": false, + "blocked": false, + "deleted_flag": false, + "manually_blocked": false, + "hidden": false, + "total_ct_cp": null, + "total_ct_cy": null, + "net_ct_cy": null, + "reported_cy": null, + "total_ct_ytd": null, + "outstanding_dct_amount_6_to_9_months_usd": null, + "outstanding_dct_amount_more_than_9_months_usd": null, + "hact_values": { + "audits": { + "completed": 0, + "minimum_requirements": 0 + }, + "spot_checks": { + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "follow_up_required": 0 + }, + "assurance_coverage": "void", + "programmatic_visits": { + "planned": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + } + }, + "outstanding_findings": 0 + }, + "basis_for_risk_rating": "" + } + }, + { + "model": "reports.countryprogramme", + "pk": 1979, + "fields": { + "name": "Country Programme 0", + "wbs": "0000/A0/00", + "invalid": false, + "from_date": "2019-01-01", + "to_date": "2019-12-31" + } + } + ] + }, + "active_agreement": { + "master": { + "model": "partners.agreement", + "pk": 1980, + "fields": { + "created": "2019-09-06T09:50:26.840Z", + "modified": "2019-09-06T09:50:26.850Z", + "partner": 2015, + "country_programme": 1980, + "agreement_type": "PCA", + "agreement_number": "TST/PCA20191980", + "attached_agreement": "", + "start": "2019-09-06", + "end": "2019-12-31", + "reference_number_year": 2019, + "special_conditions_pca": false, + "signed_by_unicef_date": "2019-09-06", + "signed_by": null, + "signed_by_partner_date": "2019-09-06", + "partner_manager": null, + "status": "active", + "authorized_officers": [] + } + }, + "deps": [ + { + "model": "partners.partnerorganization", + "pk": 2015, + "fields": { + "created": "2019-09-06T09:50:26.810Z", + "modified": "2019-09-06T09:50:26.813Z", + "partner_type": "", + "cso_type": null, + "name": "Partner 2", + "short_name": "", + "description": "", + "shared_with": null, + "street_address": null, + "city": null, + "postal_code": null, + "country": null, + "address": null, + "email": null, + "phone_number": null, + "vendor_number": null, + "alternate_id": null, + "alternate_name": null, + "rating": null, + "type_of_assessment": null, + "last_assessment_date": null, + "core_values_assessment_date": null, + "vision_synced": false, + "blocked": false, + "deleted_flag": false, + "manually_blocked": false, + "hidden": false, + "total_ct_cp": null, + "total_ct_cy": null, + "net_ct_cy": null, + "reported_cy": null, + "total_ct_ytd": null, + "outstanding_dct_amount_6_to_9_months_usd": null, + "outstanding_dct_amount_more_than_9_months_usd": null, + "hact_values": { + "audits": { + "completed": 0, + "minimum_requirements": 0 + }, + "spot_checks": { + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "follow_up_required": 0 + }, + "assurance_coverage": "void", + "programmatic_visits": { + "planned": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + } + }, + "outstanding_findings": 0 + }, + "basis_for_risk_rating": "" + } + }, + { + "model": "reports.countryprogramme", + "pk": 1980, + "fields": { + "name": "Country Programme 1", + "wbs": "0000/A0/01", + "invalid": false, + "from_date": "2019-01-01", + "to_date": "2019-12-31" + } + } + ] + }, + "intervention": { + "master": { + "model": "partners.intervention", + "pk": 2044, + "fields": { + "created": "2019-09-06T09:50:26.864Z", + "modified": "2019-09-06T09:50:26.867Z", + "document_type": "", + "agreement": 1979, + "country_programme": null, + "number": "TST/PCA20191979/20192044", + "title": "Intervention 1", + "status": "draft", + "start": null, + "end": null, + "submission_date": "2019-09-06", + "submission_date_prc": null, + "reference_number_year": 2019, + "review_date_prc": null, + "prc_review_document": "", + "signed_pd_document": "", + "signed_by_unicef_date": null, + "signed_by_partner_date": null, + "unicef_signatory": null, + "partner_authorized_officer_signatory": null, + "contingency_pd": false, + "activation_letter": "", + "termination_doc": "", + "population_focus": null, + "in_amendment": false, + "metadata": {}, + "unicef_focal_points": [], + "partner_focal_points": [], + "sections": [], + "offices": [], + "flat_locations": [] + } + }, + "deps": [ + { + "model": "partners.agreement", + "pk": 1979, + "fields": { + "created": "2019-09-06T09:50:26.816Z", + "modified": "2019-09-06T09:50:26.839Z", + "partner": 2014, + "country_programme": 1979, + "agreement_type": "PCA", + "agreement_number": "TST/PCA20191979", + "attached_agreement": "", + "start": "2019-09-06", + "end": "2019-12-31", + "reference_number_year": 2019, + "special_conditions_pca": false, + "signed_by_unicef_date": "2019-09-06", + "signed_by": null, + "signed_by_partner_date": "2019-09-06", + "partner_manager": null, + "status": "signed", + "authorized_officers": [] + } + }, + { + "model": "partners.partnerorganization", + "pk": 2014, + "fields": { + "created": "2019-09-06T09:50:26.805Z", + "modified": "2019-09-06T09:50:26.809Z", + "partner_type": "", + "cso_type": null, + "name": "Partner 1", + "short_name": "", + "description": "", + "shared_with": null, + "street_address": null, + "city": null, + "postal_code": null, + "country": null, + "address": null, + "email": null, + "phone_number": null, + "vendor_number": "VP1", + "alternate_id": null, + "alternate_name": null, + "rating": null, + "type_of_assessment": null, + "last_assessment_date": null, + "core_values_assessment_date": null, + "vision_synced": false, + "blocked": false, + "deleted_flag": false, + "manually_blocked": false, + "hidden": false, + "total_ct_cp": null, + "total_ct_cy": null, + "net_ct_cy": null, + "reported_cy": null, + "total_ct_ytd": null, + "outstanding_dct_amount_6_to_9_months_usd": null, + "outstanding_dct_amount_more_than_9_months_usd": null, + "hact_values": { + "audits": { + "completed": 0, + "minimum_requirements": 0 + }, + "spot_checks": { + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "follow_up_required": 0 + }, + "assurance_coverage": "void", + "programmatic_visits": { + "planned": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + } + }, + "outstanding_findings": 0 + }, + "basis_for_risk_rating": "" + } + }, + { + "model": "reports.countryprogramme", + "pk": 1979, + "fields": { + "name": "Country Programme 0", + "wbs": "0000/A0/00", + "invalid": false, + "from_date": "2019-01-01", + "to_date": "2019-12-31" + } + } + ] + }, + "intervention_2": { + "master": { + "model": "partners.intervention", + "pk": 2046, + "fields": { + "created": "2019-09-06T09:50:26.915Z", + "modified": "2019-09-06T09:50:26.917Z", + "document_type": "PD", + "agreement": 1979, + "country_programme": null, + "number": "TST/PCA20191979/PD20192046", + "title": "Intervention 2", + "status": "draft", + "start": null, + "end": null, + "submission_date": "2019-09-06", + "submission_date_prc": null, + "reference_number_year": 2019, + "review_date_prc": null, + "prc_review_document": "", + "signed_pd_document": "", + "signed_by_unicef_date": null, + "signed_by_partner_date": null, + "unicef_signatory": null, + "partner_authorized_officer_signatory": null, + "contingency_pd": false, + "activation_letter": "", + "termination_doc": "", + "population_focus": null, + "in_amendment": false, + "metadata": {}, + "unicef_focal_points": [], + "partner_focal_points": [], + "sections": [], + "offices": [], + "flat_locations": [] + } + }, + "deps": [ + { + "model": "partners.agreement", + "pk": 1979, + "fields": { + "created": "2019-09-06T09:50:26.816Z", + "modified": "2019-09-06T09:50:26.839Z", + "partner": 2014, + "country_programme": 1979, + "agreement_type": "PCA", + "agreement_number": "TST/PCA20191979", + "attached_agreement": "", + "start": "2019-09-06", + "end": "2019-12-31", + "reference_number_year": 2019, + "special_conditions_pca": false, + "signed_by_unicef_date": "2019-09-06", + "signed_by": null, + "signed_by_partner_date": "2019-09-06", + "partner_manager": null, + "status": "signed", + "authorized_officers": [] + } + }, + { + "model": "partners.partnerorganization", + "pk": 2014, + "fields": { + "created": "2019-09-06T09:50:26.805Z", + "modified": "2019-09-06T09:50:26.809Z", + "partner_type": "", + "cso_type": null, + "name": "Partner 1", + "short_name": "", + "description": "", + "shared_with": null, + "street_address": null, + "city": null, + "postal_code": null, + "country": null, + "address": null, + "email": null, + "phone_number": null, + "vendor_number": "VP1", + "alternate_id": null, + "alternate_name": null, + "rating": null, + "type_of_assessment": null, + "last_assessment_date": null, + "core_values_assessment_date": null, + "vision_synced": false, + "blocked": false, + "deleted_flag": false, + "manually_blocked": false, + "hidden": false, + "total_ct_cp": null, + "total_ct_cy": null, + "net_ct_cy": null, + "reported_cy": null, + "total_ct_ytd": null, + "outstanding_dct_amount_6_to_9_months_usd": null, + "outstanding_dct_amount_more_than_9_months_usd": null, + "hact_values": { + "audits": { + "completed": 0, + "minimum_requirements": 0 + }, + "spot_checks": { + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "follow_up_required": 0 + }, + "assurance_coverage": "void", + "programmatic_visits": { + "planned": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + } + }, + "outstanding_findings": 0 + }, + "basis_for_risk_rating": "" + } + }, + { + "model": "reports.countryprogramme", + "pk": 1979, + "fields": { + "name": "Country Programme 0", + "wbs": "0000/A0/00", + "invalid": false, + "from_date": "2019-01-01", + "to_date": "2019-12-31" + } + } + ] + }, + "active_intervention": { + "master": { + "model": "partners.intervention", + "pk": 2045, + "fields": { + "created": "2019-09-06T09:50:26.872Z", + "modified": "2019-09-06T09:50:26.874Z", + "document_type": "PD", + "agreement": 1980, + "country_programme": null, + "number": "TST/PCA20191980/PD20192045", + "title": "Active Intervention", + "status": "active", + "start": "2019-09-05", + "end": "2019-12-05", + "submission_date": "2019-09-06", + "submission_date_prc": null, + "reference_number_year": 2019, + "review_date_prc": null, + "prc_review_document": "", + "signed_pd_document": "", + "signed_by_unicef_date": "2019-09-05", + "signed_by_partner_date": "2019-09-05", + "unicef_signatory": 6664, + "partner_authorized_officer_signatory": 2015, + "contingency_pd": false, + "activation_letter": "", + "termination_doc": "", + "population_focus": null, + "in_amendment": false, + "metadata": {}, + "unicef_focal_points": [], + "partner_focal_points": [], + "sections": [], + "offices": [], + "flat_locations": [] + } + }, + "deps": [ + { + "model": "partners.agreement", + "pk": 1980, + "fields": { + "created": "2019-09-06T09:50:26.840Z", + "modified": "2019-09-06T09:50:26.850Z", + "partner": 2015, + "country_programme": 1980, + "agreement_type": "PCA", + "agreement_number": "TST/PCA20191980", + "attached_agreement": "", + "start": "2019-09-06", + "end": "2019-12-31", + "reference_number_year": 2019, + "special_conditions_pca": false, + "signed_by_unicef_date": "2019-09-06", + "signed_by": null, + "signed_by_partner_date": "2019-09-06", + "partner_manager": null, + "status": "active", + "authorized_officers": [] + } + }, + { + "model": "partners.partnerorganization", + "pk": 2015, + "fields": { + "created": "2019-09-06T09:50:26.810Z", + "modified": "2019-09-06T09:50:26.813Z", + "partner_type": "", + "cso_type": null, + "name": "Partner 2", + "short_name": "", + "description": "", + "shared_with": null, + "street_address": null, + "city": null, + "postal_code": null, + "country": null, + "address": null, + "email": null, + "phone_number": null, + "vendor_number": null, + "alternate_id": null, + "alternate_name": null, + "rating": null, + "type_of_assessment": null, + "last_assessment_date": null, + "core_values_assessment_date": null, + "vision_synced": false, + "blocked": false, + "deleted_flag": false, + "manually_blocked": false, + "hidden": false, + "total_ct_cp": null, + "total_ct_cy": null, + "net_ct_cy": null, + "reported_cy": null, + "total_ct_ytd": null, + "outstanding_dct_amount_6_to_9_months_usd": null, + "outstanding_dct_amount_more_than_9_months_usd": null, + "hact_values": { + "audits": { + "completed": 0, + "minimum_requirements": 0 + }, + "spot_checks": { + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "follow_up_required": 0 + }, + "assurance_coverage": "void", + "programmatic_visits": { + "planned": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + } + }, + "outstanding_findings": 0 + }, + "basis_for_risk_rating": "" + } + }, + { + "model": "reports.countryprogramme", + "pk": 1980, + "fields": { + "name": "Country Programme 1", + "wbs": "0000/A0/01", + "invalid": false, + "from_date": "2019-01-01", + "to_date": "2019-12-31" + } + }, + { + "model": "users.user", + "pk": 6664, + "fields": { + "created": "2019-09-06T09:50:26.851Z", + "modified": "2019-09-06T09:50:26.862Z", + "username": "QvnLzWzvHUSg", + "email": "staff@unicef.org", + "password": "md5$EcJak3vWmN4B$97409c09f89ec7a732d1938241774a34", + "first_name": "", + "middle_name": "", + "last_name": "", + "date_joined": "2019-09-06T09:50:26.851Z", + "last_login": null, + "is_active": true, + "is_staff": true, + "is_superuser": false, + "groups": [ + 410 + ], + "user_permissions": [] + } + }, + { + "model": "auth.group", + "pk": 410, + "fields": { + "name": "UNICEF User", + "permissions": [] + } + }, + { + "model": "partners.partnerstaffmember", + "pk": 2015, + "fields": { + "created": "2019-09-06T09:50:26.812Z", + "modified": "2019-09-06T09:50:26.812Z", + "partner": 2015, + "title": "Jedi Master", + "first_name": "Mace", + "last_name": "Windu", + "email": "mace1@example.com", + "phone": "", + "active": true + } + } + ] + }, + "reporting_requirement": { + "master": { + "model": "reports.reportingrequirement", + "pk": 40, + "fields": { + "created": "2019-09-06T09:50:26.919Z", + "modified": "2019-09-06T09:50:26.919Z", + "intervention": 2045, + "start_date": "2001-01-29", + "end_date": "2010-04-07", + "due_date": "2018-05-28", + "report_type": "('HR', 'Humanitarian Report')" + } + }, + "deps": [ + { + "model": "partners.intervention", + "pk": 2045, + "fields": { + "created": "2019-09-06T09:50:26.872Z", + "modified": "2019-09-06T09:50:26.874Z", + "document_type": "PD", + "agreement": 1980, + "country_programme": null, + "number": "TST/PCA20191980/PD20192045", + "title": "Active Intervention", + "status": "active", + "start": "2019-09-05", + "end": "2019-12-05", + "submission_date": "2019-09-06", + "submission_date_prc": null, + "reference_number_year": 2019, + "review_date_prc": null, + "prc_review_document": "", + "signed_pd_document": "", + "signed_by_unicef_date": "2019-09-05", + "signed_by_partner_date": "2019-09-05", + "unicef_signatory": 6664, + "partner_authorized_officer_signatory": 2015, + "contingency_pd": false, + "activation_letter": "", + "termination_doc": "", + "population_focus": null, + "in_amendment": false, + "metadata": {}, + "unicef_focal_points": [], + "partner_focal_points": [], + "sections": [], + "offices": [], + "flat_locations": [] + } + }, + { + "model": "partners.agreement", + "pk": 1980, + "fields": { + "created": "2019-09-06T09:50:26.840Z", + "modified": "2019-09-06T09:50:26.850Z", + "partner": 2015, + "country_programme": 1980, + "agreement_type": "PCA", + "agreement_number": "TST/PCA20191980", + "attached_agreement": "", + "start": "2019-09-06", + "end": "2019-12-31", + "reference_number_year": 2019, + "special_conditions_pca": false, + "signed_by_unicef_date": "2019-09-06", + "signed_by": null, + "signed_by_partner_date": "2019-09-06", + "partner_manager": null, + "status": "active", + "authorized_officers": [] + } + }, + { + "model": "partners.partnerorganization", + "pk": 2015, + "fields": { + "created": "2019-09-06T09:50:26.810Z", + "modified": "2019-09-06T09:50:26.813Z", + "partner_type": "", + "cso_type": null, + "name": "Partner 2", + "short_name": "", + "description": "", + "shared_with": null, + "street_address": null, + "city": null, + "postal_code": null, + "country": null, + "address": null, + "email": null, + "phone_number": null, + "vendor_number": null, + "alternate_id": null, + "alternate_name": null, + "rating": null, + "type_of_assessment": null, + "last_assessment_date": null, + "core_values_assessment_date": null, + "vision_synced": false, + "blocked": false, + "deleted_flag": false, + "manually_blocked": false, + "hidden": false, + "total_ct_cp": null, + "total_ct_cy": null, + "net_ct_cy": null, + "reported_cy": null, + "total_ct_ytd": null, + "outstanding_dct_amount_6_to_9_months_usd": null, + "outstanding_dct_amount_more_than_9_months_usd": null, + "hact_values": { + "audits": { + "completed": 0, + "minimum_requirements": 0 + }, + "spot_checks": { + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "follow_up_required": 0 + }, + "assurance_coverage": "void", + "programmatic_visits": { + "planned": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + } + }, + "outstanding_findings": 0 + }, + "basis_for_risk_rating": "" + } + }, + { + "model": "reports.countryprogramme", + "pk": 1980, + "fields": { + "name": "Country Programme 1", + "wbs": "0000/A0/01", + "invalid": false, + "from_date": "2019-01-01", + "to_date": "2019-12-31" + } + }, + { + "model": "users.user", + "pk": 6664, + "fields": { + "created": "2019-09-06T09:50:26.851Z", + "modified": "2019-09-06T09:50:26.862Z", + "username": "QvnLzWzvHUSg", + "email": "staff@unicef.org", + "password": "md5$EcJak3vWmN4B$97409c09f89ec7a732d1938241774a34", + "first_name": "", + "middle_name": "", + "last_name": "", + "date_joined": "2019-09-06T09:50:26.851Z", + "last_login": null, + "is_active": true, + "is_staff": true, + "is_superuser": false, + "groups": [ + 410 + ], + "user_permissions": [] + } + }, + { + "model": "auth.group", + "pk": 410, + "fields": { + "name": "UNICEF User", + "permissions": [] + } + }, + { + "model": "partners.partnerstaffmember", + "pk": 2015, + "fields": { + "created": "2019-09-06T09:50:26.812Z", + "modified": "2019-09-06T09:50:26.812Z", + "partner": 2015, + "title": "Jedi Master", + "first_name": "Mace", + "last_name": "Windu", + "email": "mace1@example.com", + "phone": "", + "active": true + } + } + ] + }, + "result_type": { + "master": { + "model": "reports.resulttype", + "pk": 1871, + "fields": { + "name": "Output" + } + }, + "deps": [] + }, + "result": { + "master": { + "model": "reports.result", + "pk": 1871, + "fields": { + "country_programme": null, + "result_type": 1871, + "sector": null, + "name": "Result 0", + "code": null, + "from_date": "2019-01-01", + "to_date": "2019-12-31", + "parent": null, + "humanitarian_tag": false, + "humanitarian_marker_code": null, + "humanitarian_marker_name": null, + "wbs": null, + "vision_id": null, + "gic_code": null, + "gic_name": null, + "sic_code": null, + "sic_name": null, + "activity_focus_code": null, + "activity_focus_name": null, + "hidden": false, + "ram": false, + "created": "2019-09-06T09:50:26.876Z", + "modified": "2019-09-06T09:50:26.876Z", + "lft": 1, + "rght": 2, + "tree_id": 1, + "level": 0 + } + }, + "deps": [ + { + "model": "reports.resulttype", + "pk": 1871, + "fields": { + "name": "Output" + } + } + ] + }, + "partnership_budget": { + "master": { + "model": "partners.interventionbudget", + "pk": 52, + "fields": { + "created": "2019-09-06T09:50:26.919Z", + "modified": "2019-09-06T09:50:26.919Z", + "intervention": 2044, + "partner_contribution": "20.00", + "unicef_cash": "10.00", + "in_kind_amount": "0.00", + "total": "30.00", + "partner_contribution_local": "200.00", + "unicef_cash_local": "100.00", + "in_kind_amount_local": "10.00", + "currency": "", + "total_local": "310.00" + } + }, + "deps": [ + { + "model": "partners.intervention", + "pk": 2044, + "fields": { + "created": "2019-09-06T09:50:26.864Z", + "modified": "2019-09-06T09:50:26.867Z", + "document_type": "", + "agreement": 1979, + "country_programme": null, + "number": "TST/PCA20191979/20192044", + "title": "Intervention 1", + "status": "draft", + "start": null, + "end": null, + "submission_date": "2019-09-06", + "submission_date_prc": null, + "reference_number_year": 2019, + "review_date_prc": null, + "prc_review_document": "", + "signed_pd_document": "", + "signed_by_unicef_date": null, + "signed_by_partner_date": null, + "unicef_signatory": null, + "partner_authorized_officer_signatory": null, + "contingency_pd": false, + "activation_letter": "", + "termination_doc": "", + "population_focus": null, + "in_amendment": false, + "metadata": {}, + "unicef_focal_points": [], + "partner_focal_points": [], + "sections": [], + "offices": [], + "flat_locations": [] + } + }, + { + "model": "partners.agreement", + "pk": 1979, + "fields": { + "created": "2019-09-06T09:50:26.816Z", + "modified": "2019-09-06T09:50:26.839Z", + "partner": 2014, + "country_programme": 1979, + "agreement_type": "PCA", + "agreement_number": "TST/PCA20191979", + "attached_agreement": "", + "start": "2019-09-06", + "end": "2019-12-31", + "reference_number_year": 2019, + "special_conditions_pca": false, + "signed_by_unicef_date": "2019-09-06", + "signed_by": null, + "signed_by_partner_date": "2019-09-06", + "partner_manager": null, + "status": "signed", + "authorized_officers": [] + } + }, + { + "model": "partners.partnerorganization", + "pk": 2014, + "fields": { + "created": "2019-09-06T09:50:26.805Z", + "modified": "2019-09-06T09:50:26.809Z", + "partner_type": "", + "cso_type": null, + "name": "Partner 1", + "short_name": "", + "description": "", + "shared_with": null, + "street_address": null, + "city": null, + "postal_code": null, + "country": null, + "address": null, + "email": null, + "phone_number": null, + "vendor_number": "VP1", + "alternate_id": null, + "alternate_name": null, + "rating": null, + "type_of_assessment": null, + "last_assessment_date": null, + "core_values_assessment_date": null, + "vision_synced": false, + "blocked": false, + "deleted_flag": false, + "manually_blocked": false, + "hidden": false, + "total_ct_cp": null, + "total_ct_cy": null, + "net_ct_cy": null, + "reported_cy": null, + "total_ct_ytd": null, + "outstanding_dct_amount_6_to_9_months_usd": null, + "outstanding_dct_amount_more_than_9_months_usd": null, + "hact_values": { + "audits": { + "completed": 0, + "minimum_requirements": 0 + }, + "spot_checks": { + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "follow_up_required": 0 + }, + "assurance_coverage": "void", + "programmatic_visits": { + "planned": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + } + }, + "outstanding_findings": 0 + }, + "basis_for_risk_rating": "" + } + }, + { + "model": "reports.countryprogramme", + "pk": 1979, + "fields": { + "name": "Country Programme 0", + "wbs": "0000/A0/00", + "invalid": false, + "from_date": "2019-01-01", + "to_date": "2019-12-31" + } + } + ] + }, + "fr_1": { + "master": { + "model": "funds.fundsreservationheader", + "pk": 99, + "fields": { + "created": "2019-09-06T09:50:26.921Z", + "modified": "2019-09-06T09:50:26.921Z", + "intervention": null, + "vendor_code": "YuqEOQMFqloxYHrvYjjV", + "fr_number": "qXHQBdgDZdhIYtxNTTtl", + "document_date": "2019-01-01", + "fr_type": "DrCdefpczGOveCknefML", + "currency": "USD", + "document_text": "WKxSRgRxeDYnHtafxIAv", + "intervention_amt": "67.25", + "total_amt": "122.35", + "total_amt_local": "125.88", + "actual_amt": "244.74", + "actual_amt_local": "297.71", + "outstanding_amt": "257.71", + "outstanding_amt_local": "154.74", + "start_date": "2018-12-30", + "end_date": "2020-01-07", + "multi_curr_flag": false, + "completed_flag": false, + "delegated": false + } + }, + "deps": [] + }, + "fr_2": { + "master": { + "model": "funds.fundsreservationheader", + "pk": 100, + "fields": { + "created": "2019-09-06T09:50:26.923Z", + "modified": "2019-09-06T09:50:26.923Z", + "intervention": null, + "vendor_code": "QGixIDUqaperZjhalZle", + "fr_number": "cNgFvaJxSSdeZaaVvffu", + "document_date": "2019-01-01", + "fr_type": "IPAPKYpFMShqPUixmwga", + "currency": "USD", + "document_text": "hZJkBAWDMzouymlggpSz", + "intervention_amt": "210.75", + "total_amt": "35.80", + "total_amt_local": "293.79", + "actual_amt": "274.72", + "actual_amt_local": "245.28", + "outstanding_amt": "166.94", + "outstanding_amt_local": "167.80", + "start_date": "2018-12-30", + "end_date": "2020-01-11", + "multi_curr_flag": false, + "completed_flag": false, + "delegated": false + } + }, + "deps": [] + }, + "result_link": { + "master": { + "model": "partners.interventionresultlink", + "pk": 1693, + "fields": { + "created": "2019-09-06T09:50:26.878Z", + "modified": "2019-09-06T09:50:26.878Z", + "intervention": 2045, + "cp_output": 1871, + "ram_indicators": [] + } + }, + "deps": [ + { + "model": "partners.intervention", + "pk": 2045, + "fields": { + "created": "2019-09-06T09:50:26.872Z", + "modified": "2019-09-06T09:50:26.874Z", + "document_type": "PD", + "agreement": 1980, + "country_programme": null, + "number": "TST/PCA20191980/PD20192045", + "title": "Active Intervention", + "status": "active", + "start": "2019-09-05", + "end": "2019-12-05", + "submission_date": "2019-09-06", + "submission_date_prc": null, + "reference_number_year": 2019, + "review_date_prc": null, + "prc_review_document": "", + "signed_pd_document": "", + "signed_by_unicef_date": "2019-09-05", + "signed_by_partner_date": "2019-09-05", + "unicef_signatory": 6664, + "partner_authorized_officer_signatory": 2015, + "contingency_pd": false, + "activation_letter": "", + "termination_doc": "", + "population_focus": null, + "in_amendment": false, + "metadata": {}, + "unicef_focal_points": [], + "partner_focal_points": [], + "sections": [], + "offices": [], + "flat_locations": [] + } + }, + { + "model": "partners.agreement", + "pk": 1980, + "fields": { + "created": "2019-09-06T09:50:26.840Z", + "modified": "2019-09-06T09:50:26.850Z", + "partner": 2015, + "country_programme": 1980, + "agreement_type": "PCA", + "agreement_number": "TST/PCA20191980", + "attached_agreement": "", + "start": "2019-09-06", + "end": "2019-12-31", + "reference_number_year": 2019, + "special_conditions_pca": false, + "signed_by_unicef_date": "2019-09-06", + "signed_by": null, + "signed_by_partner_date": "2019-09-06", + "partner_manager": null, + "status": "active", + "authorized_officers": [] + } + }, + { + "model": "partners.partnerorganization", + "pk": 2015, + "fields": { + "created": "2019-09-06T09:50:26.810Z", + "modified": "2019-09-06T09:50:26.813Z", + "partner_type": "", + "cso_type": null, + "name": "Partner 2", + "short_name": "", + "description": "", + "shared_with": null, + "street_address": null, + "city": null, + "postal_code": null, + "country": null, + "address": null, + "email": null, + "phone_number": null, + "vendor_number": null, + "alternate_id": null, + "alternate_name": null, + "rating": null, + "type_of_assessment": null, + "last_assessment_date": null, + "core_values_assessment_date": null, + "vision_synced": false, + "blocked": false, + "deleted_flag": false, + "manually_blocked": false, + "hidden": false, + "total_ct_cp": null, + "total_ct_cy": null, + "net_ct_cy": null, + "reported_cy": null, + "total_ct_ytd": null, + "outstanding_dct_amount_6_to_9_months_usd": null, + "outstanding_dct_amount_more_than_9_months_usd": null, + "hact_values": { + "audits": { + "completed": 0, + "minimum_requirements": 0 + }, + "spot_checks": { + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "follow_up_required": 0 + }, + "assurance_coverage": "void", + "programmatic_visits": { + "planned": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + } + }, + "outstanding_findings": 0 + }, + "basis_for_risk_rating": "" + } + }, + { + "model": "reports.countryprogramme", + "pk": 1980, + "fields": { + "name": "Country Programme 1", + "wbs": "0000/A0/01", + "invalid": false, + "from_date": "2019-01-01", + "to_date": "2019-12-31" + } + }, + { + "model": "users.user", + "pk": 6664, + "fields": { + "created": "2019-09-06T09:50:26.851Z", + "modified": "2019-09-06T09:50:26.862Z", + "username": "QvnLzWzvHUSg", + "email": "staff@unicef.org", + "password": "md5$EcJak3vWmN4B$97409c09f89ec7a732d1938241774a34", + "first_name": "", + "middle_name": "", + "last_name": "", + "date_joined": "2019-09-06T09:50:26.851Z", + "last_login": null, + "is_active": true, + "is_staff": true, + "is_superuser": false, + "groups": [ + 410 + ], + "user_permissions": [] + } + }, + { + "model": "auth.group", + "pk": 410, + "fields": { + "name": "UNICEF User", + "permissions": [] + } + }, + { + "model": "partners.partnerstaffmember", + "pk": 2015, + "fields": { + "created": "2019-09-06T09:50:26.812Z", + "modified": "2019-09-06T09:50:26.812Z", + "partner": 2015, + "title": "Jedi Master", + "first_name": "Mace", + "last_name": "Windu", + "email": "mace1@example.com", + "phone": "", + "active": true + } + }, + { + "model": "reports.result", + "pk": 1871, + "fields": { + "country_programme": null, + "result_type": 1871, + "sector": null, + "name": "Result 0", + "code": null, + "from_date": "2019-01-01", + "to_date": "2019-12-31", + "parent": null, + "humanitarian_tag": false, + "humanitarian_marker_code": null, + "humanitarian_marker_name": null, + "wbs": null, + "vision_id": null, + "gic_code": null, + "gic_name": null, + "sic_code": null, + "sic_name": null, + "activity_focus_code": null, + "activity_focus_name": null, + "hidden": false, + "ram": false, + "created": "2019-09-06T09:50:26.876Z", + "modified": "2019-09-06T09:50:26.876Z", + "lft": 1, + "rght": 2, + "tree_id": 1, + "level": 0 + } + }, + { + "model": "reports.resulttype", + "pk": 1871, + "fields": { + "name": "Output" + } + } + ] + }, + "lower_result": { + "master": { + "model": "reports.lowerresult", + "pk": 109, + "fields": { + "created": "2019-09-06T09:50:26.880Z", + "modified": "2019-09-06T09:50:26.880Z", + "result_link": 1693, + "name": "Lower Result 1", + "code": "2045-1" + } + }, + "deps": [ + { + "model": "partners.interventionresultlink", + "pk": 1693, + "fields": { + "created": "2019-09-06T09:50:26.878Z", + "modified": "2019-09-06T09:50:26.878Z", + "intervention": 2045, + "cp_output": 1871, + "ram_indicators": [] + } + }, + { + "model": "partners.intervention", + "pk": 2045, + "fields": { + "created": "2019-09-06T09:50:26.872Z", + "modified": "2019-09-06T09:50:26.874Z", + "document_type": "PD", + "agreement": 1980, + "country_programme": null, + "number": "TST/PCA20191980/PD20192045", + "title": "Active Intervention", + "status": "active", + "start": "2019-09-05", + "end": "2019-12-05", + "submission_date": "2019-09-06", + "submission_date_prc": null, + "reference_number_year": 2019, + "review_date_prc": null, + "prc_review_document": "", + "signed_pd_document": "", + "signed_by_unicef_date": "2019-09-05", + "signed_by_partner_date": "2019-09-05", + "unicef_signatory": 6664, + "partner_authorized_officer_signatory": 2015, + "contingency_pd": false, + "activation_letter": "", + "termination_doc": "", + "population_focus": null, + "in_amendment": false, + "metadata": {}, + "unicef_focal_points": [], + "partner_focal_points": [], + "sections": [], + "offices": [], + "flat_locations": [] + } + }, + { + "model": "partners.agreement", + "pk": 1980, + "fields": { + "created": "2019-09-06T09:50:26.840Z", + "modified": "2019-09-06T09:50:26.850Z", + "partner": 2015, + "country_programme": 1980, + "agreement_type": "PCA", + "agreement_number": "TST/PCA20191980", + "attached_agreement": "", + "start": "2019-09-06", + "end": "2019-12-31", + "reference_number_year": 2019, + "special_conditions_pca": false, + "signed_by_unicef_date": "2019-09-06", + "signed_by": null, + "signed_by_partner_date": "2019-09-06", + "partner_manager": null, + "status": "active", + "authorized_officers": [] + } + }, + { + "model": "partners.partnerorganization", + "pk": 2015, + "fields": { + "created": "2019-09-06T09:50:26.810Z", + "modified": "2019-09-06T09:50:26.813Z", + "partner_type": "", + "cso_type": null, + "name": "Partner 2", + "short_name": "", + "description": "", + "shared_with": null, + "street_address": null, + "city": null, + "postal_code": null, + "country": null, + "address": null, + "email": null, + "phone_number": null, + "vendor_number": null, + "alternate_id": null, + "alternate_name": null, + "rating": null, + "type_of_assessment": null, + "last_assessment_date": null, + "core_values_assessment_date": null, + "vision_synced": false, + "blocked": false, + "deleted_flag": false, + "manually_blocked": false, + "hidden": false, + "total_ct_cp": null, + "total_ct_cy": null, + "net_ct_cy": null, + "reported_cy": null, + "total_ct_ytd": null, + "outstanding_dct_amount_6_to_9_months_usd": null, + "outstanding_dct_amount_more_than_9_months_usd": null, + "hact_values": { + "audits": { + "completed": 0, + "minimum_requirements": 0 + }, + "spot_checks": { + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "follow_up_required": 0 + }, + "assurance_coverage": "void", + "programmatic_visits": { + "planned": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + } + }, + "outstanding_findings": 0 + }, + "basis_for_risk_rating": "" + } + }, + { + "model": "reports.countryprogramme", + "pk": 1980, + "fields": { + "name": "Country Programme 1", + "wbs": "0000/A0/01", + "invalid": false, + "from_date": "2019-01-01", + "to_date": "2019-12-31" + } + }, + { + "model": "users.user", + "pk": 6664, + "fields": { + "created": "2019-09-06T09:50:26.851Z", + "modified": "2019-09-06T09:50:26.862Z", + "username": "QvnLzWzvHUSg", + "email": "staff@unicef.org", + "password": "md5$EcJak3vWmN4B$97409c09f89ec7a732d1938241774a34", + "first_name": "", + "middle_name": "", + "last_name": "", + "date_joined": "2019-09-06T09:50:26.851Z", + "last_login": null, + "is_active": true, + "is_staff": true, + "is_superuser": false, + "groups": [ + 410 + ], + "user_permissions": [] + } + }, + { + "model": "auth.group", + "pk": 410, + "fields": { + "name": "UNICEF User", + "permissions": [] + } + }, + { + "model": "partners.partnerstaffmember", + "pk": 2015, + "fields": { + "created": "2019-09-06T09:50:26.812Z", + "modified": "2019-09-06T09:50:26.812Z", + "partner": 2015, + "title": "Jedi Master", + "first_name": "Mace", + "last_name": "Windu", + "email": "mace1@example.com", + "phone": "", + "active": true + } + }, + { + "model": "reports.result", + "pk": 1871, + "fields": { + "country_programme": null, + "result_type": 1871, + "sector": null, + "name": "Result 0", + "code": null, + "from_date": "2019-01-01", + "to_date": "2019-12-31", + "parent": null, + "humanitarian_tag": false, + "humanitarian_marker_code": null, + "humanitarian_marker_name": null, + "wbs": null, + "vision_id": null, + "gic_code": null, + "gic_name": null, + "sic_code": null, + "sic_name": null, + "activity_focus_code": null, + "activity_focus_name": null, + "hidden": false, + "ram": false, + "created": "2019-09-06T09:50:26.876Z", + "modified": "2019-09-06T09:50:26.876Z", + "lft": 1, + "rght": 2, + "tree_id": 1, + "level": 0 + } + }, + { + "model": "reports.resulttype", + "pk": 1871, + "fields": { + "name": "Output" + } + } + ] + }, + "indicator_blueprint": { + "master": { + "model": "reports.indicatorblueprint", + "pk": 109, + "fields": { + "created": "2019-09-06T09:50:26.879Z", + "modified": "2019-09-06T09:50:26.879Z", + "title": "The Blueprint", + "unit": "number", + "description": null, + "code": null, + "subdomain": null, + "disaggregatable": false, + "calculation_formula_across_periods": "sum", + "calculation_formula_across_locations": "sum", + "display_type": "number" + } + }, + "deps": [] + }, + "applied_indicator": { + "master": { + "model": "reports.appliedindicator", + "pk": 108, + "fields": { + "created": "2019-09-06T09:50:26.882Z", + "modified": "2019-09-06T09:50:26.882Z", + "indicator": 109, + "measurement_specifications": null, + "label": null, + "numerator_label": null, + "denominator_label": null, + "section": null, + "cluster_indicator_id": null, + "response_plan_name": null, + "cluster_name": null, + "cluster_indicator_title": null, + "lower_result": 109, + "context_code": null, + "target": { + "d": 1, + "v": 0 + }, + "baseline": { + "d": 1, + "v": 0 + }, + "assumptions": null, + "means_of_verification": null, + "total": 0, + "is_high_frequency": false, + "is_active": true, + "disaggregation": [ + 70 + ], + "locations": [ + 1893 + ] + } + }, + "deps": [ + { + "model": "reports.indicatorblueprint", + "pk": 109, + "fields": { + "created": "2019-09-06T09:50:26.879Z", + "modified": "2019-09-06T09:50:26.879Z", + "title": "The Blueprint", + "unit": "number", + "description": null, + "code": null, + "subdomain": null, + "disaggregatable": false, + "calculation_formula_across_periods": "sum", + "calculation_formula_across_locations": "sum", + "display_type": "number" + } + }, + { + "model": "reports.lowerresult", + "pk": 109, + "fields": { + "created": "2019-09-06T09:50:26.880Z", + "modified": "2019-09-06T09:50:26.880Z", + "result_link": 1693, + "name": "Lower Result 1", + "code": "2045-1" + } + }, + { + "model": "partners.interventionresultlink", + "pk": 1693, + "fields": { + "created": "2019-09-06T09:50:26.878Z", + "modified": "2019-09-06T09:50:26.878Z", + "intervention": 2045, + "cp_output": 1871, + "ram_indicators": [] + } + }, + { + "model": "partners.intervention", + "pk": 2045, + "fields": { + "created": "2019-09-06T09:50:26.872Z", + "modified": "2019-09-06T09:50:26.874Z", + "document_type": "PD", + "agreement": 1980, + "country_programme": null, + "number": "TST/PCA20191980/PD20192045", + "title": "Active Intervention", + "status": "active", + "start": "2019-09-05", + "end": "2019-12-05", + "submission_date": "2019-09-06", + "submission_date_prc": null, + "reference_number_year": 2019, + "review_date_prc": null, + "prc_review_document": "", + "signed_pd_document": "", + "signed_by_unicef_date": "2019-09-05", + "signed_by_partner_date": "2019-09-05", + "unicef_signatory": 6664, + "partner_authorized_officer_signatory": 2015, + "contingency_pd": false, + "activation_letter": "", + "termination_doc": "", + "population_focus": null, + "in_amendment": false, + "metadata": {}, + "unicef_focal_points": [], + "partner_focal_points": [], + "sections": [], + "offices": [], + "flat_locations": [] + } + }, + { + "model": "partners.agreement", + "pk": 1980, + "fields": { + "created": "2019-09-06T09:50:26.840Z", + "modified": "2019-09-06T09:50:26.850Z", + "partner": 2015, + "country_programme": 1980, + "agreement_type": "PCA", + "agreement_number": "TST/PCA20191980", + "attached_agreement": "", + "start": "2019-09-06", + "end": "2019-12-31", + "reference_number_year": 2019, + "special_conditions_pca": false, + "signed_by_unicef_date": "2019-09-06", + "signed_by": null, + "signed_by_partner_date": "2019-09-06", + "partner_manager": null, + "status": "active", + "authorized_officers": [] + } + }, + { + "model": "partners.partnerorganization", + "pk": 2015, + "fields": { + "created": "2019-09-06T09:50:26.810Z", + "modified": "2019-09-06T09:50:26.813Z", + "partner_type": "", + "cso_type": null, + "name": "Partner 2", + "short_name": "", + "description": "", + "shared_with": null, + "street_address": null, + "city": null, + "postal_code": null, + "country": null, + "address": null, + "email": null, + "phone_number": null, + "vendor_number": null, + "alternate_id": null, + "alternate_name": null, + "rating": null, + "type_of_assessment": null, + "last_assessment_date": null, + "core_values_assessment_date": null, + "vision_synced": false, + "blocked": false, + "deleted_flag": false, + "manually_blocked": false, + "hidden": false, + "total_ct_cp": null, + "total_ct_cy": null, + "net_ct_cy": null, + "reported_cy": null, + "total_ct_ytd": null, + "outstanding_dct_amount_6_to_9_months_usd": null, + "outstanding_dct_amount_more_than_9_months_usd": null, + "hact_values": { + "audits": { + "completed": 0, + "minimum_requirements": 0 + }, + "spot_checks": { + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "follow_up_required": 0 + }, + "assurance_coverage": "void", + "programmatic_visits": { + "planned": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + }, + "completed": { + "q1": 0, + "q2": 0, + "q3": 0, + "q4": 0, + "total": 0 + } + }, + "outstanding_findings": 0 + }, + "basis_for_risk_rating": "" + } + }, + { + "model": "reports.countryprogramme", + "pk": 1980, + "fields": { + "name": "Country Programme 1", + "wbs": "0000/A0/01", + "invalid": false, + "from_date": "2019-01-01", + "to_date": "2019-12-31" + } + }, + { + "model": "users.user", + "pk": 6664, + "fields": { + "created": "2019-09-06T09:50:26.851Z", + "modified": "2019-09-06T09:50:26.862Z", + "username": "QvnLzWzvHUSg", + "email": "staff@unicef.org", + "password": "md5$EcJak3vWmN4B$97409c09f89ec7a732d1938241774a34", + "first_name": "", + "middle_name": "", + "last_name": "", + "date_joined": "2019-09-06T09:50:26.851Z", + "last_login": null, + "is_active": true, + "is_staff": true, + "is_superuser": false, + "groups": [ + 410 + ], + "user_permissions": [] + } + }, + { + "model": "auth.group", + "pk": 410, + "fields": { + "name": "UNICEF User", + "permissions": [] + } + }, + { + "model": "partners.partnerstaffmember", + "pk": 2015, + "fields": { + "created": "2019-09-06T09:50:26.812Z", + "modified": "2019-09-06T09:50:26.812Z", + "partner": 2015, + "title": "Jedi Master", + "first_name": "Mace", + "last_name": "Windu", + "email": "mace1@example.com", + "phone": "", + "active": true + } + }, + { + "model": "reports.result", + "pk": 1871, + "fields": { + "country_programme": null, + "result_type": 1871, + "sector": null, + "name": "Result 0", + "code": null, + "from_date": "2019-01-01", + "to_date": "2019-12-31", + "parent": null, + "humanitarian_tag": false, + "humanitarian_marker_code": null, + "humanitarian_marker_name": null, + "wbs": null, + "vision_id": null, + "gic_code": null, + "gic_name": null, + "sic_code": null, + "sic_name": null, + "activity_focus_code": null, + "activity_focus_name": null, + "hidden": false, + "ram": false, + "created": "2019-09-06T09:50:26.876Z", + "modified": "2019-09-06T09:50:26.876Z", + "lft": 1, + "rght": 2, + "tree_id": 1, + "level": 0 + } + }, + { + "model": "reports.resulttype", + "pk": 1871, + "fields": { + "name": "Output" + } + }, + { + "model": "reports.disaggregation", + "pk": 70, + "fields": { + "created": "2019-09-06T09:50:26.912Z", + "modified": "2019-09-06T09:50:26.912Z", + "name": "A Disaggregation", + "active": false + } + }, + { + "model": "locations.location", + "pk": 1893, + "fields": { + "name": "A Location", + "gateway": 1893, + "latitude": null, + "longitude": null, + "p_code": "a-p-code", + "parent": null, + "geom": null, + "point": "SRID=4326;POINT (20 20)", + "is_active": true, + "created": "2019-09-06T09:50:26.885Z", + "modified": "2019-09-06T09:50:26.885Z", + "lft": 1, + "rght": 2, + "tree_id": 1, + "level": 0 + } + }, + { + "model": "locations.gatewaytype", + "pk": 1893, + "fields": { + "created": "2019-09-06T09:50:26.884Z", + "modified": "2019-09-06T09:50:26.884Z", + "name": "A Gateway", + "admin_level": 0 + } + } + ] + }, + "file_type_attachment": { + "master": { + "model": "unicef_attachments.filetype", + "pk": 7531, + "fields": { + "order": 2, + "name": "file_type_2", + "label": "", + "code": "partners_intervention_attachment" + } + }, + "deps": [] + }, + "file_type_prc": { + "master": { + "model": "unicef_attachments.filetype", + "pk": 7532, + "fields": { + "order": 3, + "name": "file_type_3", + "label": "", + "code": "partners_intervention_prc_review" + } + }, + "deps": [] + }, + "file_type_pd": { + "master": { + "model": "unicef_attachments.filetype", + "pk": 7533, + "fields": { + "order": 4, + "name": "file_type_4", + "label": "", + "code": "partners_intervention_signed_pd" + } + }, + "deps": [] + }, + "disaggregation": { + "master": { + "model": "reports.disaggregation", + "pk": 70, + "fields": { + "created": "2019-09-06T09:50:26.912Z", + "modified": "2019-09-06T09:50:26.912Z", + "name": "A Disaggregation", + "active": false + } + }, + "deps": [] + } +} \ No newline at end of file diff --git a/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api_prp/TestAPIPRP/get__api_prp_v1_interventions__{'workspace': 'ZZZ'}.response.json b/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api_prp/TestAPIPRP/get__api_prp_v1_interventions__{'workspace': 'ZZZ'}.response.json new file mode 100644 index 0000000000..9534dfffe1 --- /dev/null +++ b/src/etools/applications/partners/tests/_api_checker/etools.applications.partners.tests.test_api_prp/TestAPIPRP/get__api_prp_v1_interventions__{'workspace': 'ZZZ'}.response.json @@ -0,0 +1,143 @@ +{ + "status_code": 200, + "headers": { + "content-type": [ + "Content-Type", + "application/json" + ], + "vary": [ + "Vary", + "Accept, Origin, Cookie" + ], + "allow": [ + "Allow", + "GET" + ], + "x-frame-options": [ + "X-Frame-Options", + "SAMEORIGIN" + ], + "content-length": [ + "Content-Length", + "1877" + ] + }, + "data": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "id": 2045, + "title": "Active Intervention", + "document_type": "PD", + "business_area_code": "ZZZ", + "offices": [], + "number": "TST/PCA20191980/PD20192045", + "status": "active", + "partner_org": { + "short_name": "", + "street_address": null, + "last_assessment_date": null, + "partner_type": "", + "cso_type": null, + "total_ct_cp": null, + "total_ct_cy": null, + "address": null, + "city": null, + "postal_code": null, + "country": null, + "id": 2015, + "unicef_vendor_number": null, + "name": "Partner 2", + "alternate_name": null, + "rating": null, + "email": null, + "phone_number": null, + "basis_for_risk_rating": "", + "core_values_assessment_date": null, + "type_of_assessment": null + }, + "special_reports": [], + "sections": [], + "agreement": "TST/PCA20191980", + "unicef_focal_points": [], + "agreement_auth_officers": [], + "focal_points": [], + "start_date": "2019-09-05", + "end_date": "2019-12-05", + "cso_budget": "0.00", + "cso_budget_currency": "", + "unicef_budget": "0.00", + "unicef_budget_currency": "", + "reporting_requirements": [ + { + "id": 40, + "start_date": "2001-01-29", + "end_date": "2010-04-07", + "due_date": "2018-05-28", + "report_type": "('HR', 'Humanitarian Report')" + } + ], + "expected_results": [ + { + "id": 109, + "title": "Lower Result 1", + "result_link": 1693, + "cp_output": { + "id": 1871, + "title": "Result 0" + }, + "indicators": [ + { + "id": 108, + "title": "The Blueprint", + "blueprint_id": 109, + "cluster_indicator_id": null, + "means_of_verification": null, + "baseline": { + "d": 1, + "v": 0 + }, + "target": { + "d": 1, + "v": 0 + }, + "locations": [ + { + "id": 1893, + "name": "A Location", + "pcode": "a-p-code", + "location_type": "A Gateway", + "admin_level": 0 + } + ], + "disaggregation": [ + { + "id": 70, + "name": "A Disaggregation", + "disaggregation_values": [] + } + ], + "is_high_frequency": false, + "is_active": true, + "numerator_label": null, + "denominator_label": null, + "unit": "number", + "display_type": "number" + } + ] + } + ], + "update_date": "2019-09-06T09:50:26.874000Z", + "amendments": [], + "locations": [], + "unicef_budget_cash": "0.00", + "unicef_budget_supplies": "0.00", + "disbursement": null, + "disbursement_percent": null + } + ] + }, + "content_type": null +} \ No newline at end of file diff --git a/src/etools/applications/partners/tests/data/prp-intervention-list.json b/src/etools/applications/partners/tests/data/prp-intervention-list.json index 32cfecc0d3..ea7dc5d55c 100644 --- a/src/etools/applications/partners/tests/data/prp-intervention-list.json +++ b/src/etools/applications/partners/tests/data/prp-intervention-list.json @@ -88,6 +88,9 @@ "cso_budget_currency": "", "id": 642, "cso_budget": "0.00", + "disbursement": null, + "disbursement_percent": null, + "document_type": "PD", "update_date": "2017-10-12T08:10:43.708677Z" } ] diff --git a/src/etools/applications/partners/tests/test_api.py b/src/etools/applications/partners/tests/test_api.py index d04a7b582d..b5e20798b6 100644 --- a/src/etools/applications/partners/tests/test_api.py +++ b/src/etools/applications/partners/tests/test_api.py @@ -45,6 +45,8 @@ class TestAPIIntervention(BaseTenantTestCase, metaclass=ViewSetChecker): reverse("partners_api:intervention-indicators"), reverse("partners_api:intervention-amendments"), reverse("partners_api:intervention-map"), + reverse("partners_api:intervention-applied-indicators-list"), + ] def get_fixtures(cls): diff --git a/src/etools/applications/partners/tests/test_api_partners.py b/src/etools/applications/partners/tests/test_api_partners.py index 3d32f53d02..a07dd3c4fc 100644 --- a/src/etools/applications/partners/tests/test_api_partners.py +++ b/src/etools/applications/partners/tests/test_api_partners.py @@ -473,6 +473,63 @@ def test_vendor_exists(self): {"error": "This vendor number already exists in eTools"} ) + def test_missing_required_keys(self): + mock_insight = Mock(return_value=(True, { + "ROWSET": { + "ROW": { + "VENDOR_CODE": "321", + } + } + })) + with patch(INSIGHT_PATH, mock_insight): + response = self.forced_auth_req( + 'post', + "{}?vendor=321".format(self.url), + data={}, + view=self.view + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual( + response.data, + {"error": "Partner skipped because one or more of the required fields are missing"} + ) + + def test_invalid_partner_type(self): + vendor_number = "321" + mock_insight = Mock(return_value=(True, { + "ROWSET": { + "ROW": { + "PARTNER_TYPE_DESC": "SOMETHING INVALID", + "VENDOR_CODE": vendor_number, + "VENDOR_NAME": "New Partner", + "CSO_TYPE": "National NGO", + "CORE_VALUE_ASSESSMENT_DT": "01-Jan-01", + 'TOTAL_CASH_TRANSFERRED_CP': "2,000", + 'TOTAL_CASH_TRANSFERRED_CY': "2,000", + 'NET_CASH_TRANSFERRED_CY': "2,000", + 'TOTAL_CASH_TRANSFERRED_YTD': "2,000", + 'REPORTED_CY': "2,000", + "COUNTRY": "239", + "MARKED_FOR_DELETION": 'true', + "POSTING_BLOCK": 'true', + } + } + })) + with patch(INSIGHT_PATH, mock_insight): + response = self.forced_auth_req( + 'post', + "{}?vendor=321".format(self.url), + data={}, + view=self.view + ) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + qs = PartnerOrganization.objects.filter(vendor_number=vendor_number) + self.assertTrue(qs.exists()) + partner = qs.first() + self.assertEqual(partner.hidden, True) + self.assertEqual(partner.blocked, True) + self.assertEqual(partner.deleted_flag, True) + def test_post(self): self.assertFalse( PartnerOrganization.objects.filter(name="New Partner").exists() @@ -484,8 +541,12 @@ def test_post(self): "VENDOR_NAME": "New Partner", "PARTNER_TYPE_DESC": "UN AGENCY", "CSO_TYPE": "National NGO", - "TOTAL_CASH_TRANSFERRED_CP": "2,000", "CORE_VALUE_ASSESSMENT_DT": "01-Jan-01", + 'TOTAL_CASH_TRANSFERRED_CP': "2,000", + 'TOTAL_CASH_TRANSFERRED_CY': "2,000", + 'NET_CASH_TRANSFERRED_CY': "2,000", + 'TOTAL_CASH_TRANSFERRED_YTD': "2,000", + 'REPORTED_CY': "2,000", "COUNTRY": "239", } } diff --git a/src/etools/applications/partners/tests/test_api_prp.py b/src/etools/applications/partners/tests/test_api_prp.py index 07fd59244a..849bf30bc3 100644 --- a/src/etools/applications/partners/tests/test_api_prp.py +++ b/src/etools/applications/partners/tests/test_api_prp.py @@ -9,15 +9,23 @@ from rest_framework.test import APIRequestFactory from unicef_locations.tests.factories import GatewayTypeFactory, LocationFactory +from etools.applications.attachments.tests.factories import AttachmentFileTypeFactory from etools.applications.core.tests.cases import BaseTenantTestCase from etools.applications.core.tests.mixins import WorkspaceRequiredAPITestMixIn -from etools.applications.partners.models import InterventionResultLink, PartnerOrganization +from etools.applications.funds.tests.factories import FundsReservationHeaderFactory +from etools.applications.partners.models import ( + Intervention, + InterventionBudget, + InterventionResultLink, + PartnerOrganization, +) from etools.applications.partners.permissions import READ_ONLY_API_GROUP_NAME -from etools.applications.partners.tests.factories import InterventionFactory +from etools.applications.partners.tests.factories import AgreementFactory, InterventionFactory, PartnerFactory from etools.applications.partners.tests.test_utils import setup_intervention_test_data -from etools.applications.reports.models import AppliedIndicator, IndicatorBlueprint, LowerResult -from etools.applications.reports.tests.factories import ResultFactory +from etools.applications.reports.models import AppliedIndicator, IndicatorBlueprint, LowerResult, ResultType +from etools.applications.reports.tests.factories import ReportingRequirementFactory, ResultFactory from etools.applications.users.tests.factories import GroupFactory, UserFactory +from etools.libraries.tests.api_checker import ApiCheckerMixin, AssertTimeStampedMixin class TestInterventionsAPI(WorkspaceRequiredAPITestMixIn, BaseTenantTestCase): @@ -73,6 +81,8 @@ def test_prp_api(self): del expected_intervention['agreement'] del expected_intervention['partner_org']['unicef_vendor_number'] del actual_intervention['partner_org']['unicef_vendor_number'] + del expected_intervention['reporting_requirements'] + del actual_intervention['reporting_requirements'] for j in range(len(expected_intervention['expected_results'])): del expected_intervention['expected_results'][j]['id'] @@ -183,3 +193,85 @@ def test_staff_has_access(self): """Ensure a staff user has access""" response = self.forced_auth_req('get', self.url, user=UserFactory(is_staff=True), data=self.query_param_data) self.assertEquals(response.status_code, status.HTTP_200_OK) + + +class TestAPIPRP(ApiCheckerMixin, AssertTimeStampedMixin, WorkspaceRequiredAPITestMixIn, BaseTenantTestCase): + + def assert_results(self, response, expected, path=''): + extepted_results = json.loads(json.dumps(expected['results'][0])) + response_results = json.loads(json.dumps(response['results'][0])) + assert sorted(response_results.keys()) == sorted(extepted_results.keys()) + + def get_fixtures(self): + today = datetime.date.today() + + partnership_user = UserFactory(is_staff=True, email='partner@unicef.org') + partnership_user.groups.add(GroupFactory()) + + partner = PartnerFactory(name='Partner 1', vendor_number="VP1") + partner1 = PartnerFactory(name='Partner 2') + agreement = AgreementFactory(partner=partner, signed_by_unicef_date=datetime.date.today()) + active_agreement = AgreementFactory( + partner=partner1, + status='active', + signed_by_unicef_date=datetime.date.today(), + signed_by_partner_date=datetime.date.today()) + unicef_staff = UserFactory(is_staff=True, email='staff@unicef.org') + result_type = ResultType.objects.get_or_create(name=ResultType.OUTPUT)[0] + intervention = InterventionFactory(agreement=agreement, title='Intervention 1') + active_intervention = InterventionFactory( + agreement=active_agreement, title='Active Intervention', + document_type=Intervention.PD, start=today - datetime.timedelta(days=1), + end=today + datetime.timedelta(days=90), + status='active', + signed_by_unicef_date=today - datetime.timedelta(days=1), + signed_by_partner_date=today - datetime.timedelta(days=1), + unicef_signatory=unicef_staff, + partner_authorized_officer_signatory=partner1.staff_members.all().first() + ) + result = ResultFactory(result_type=result_type) + result_link = InterventionResultLink.objects.create(intervention=active_intervention, cp_output=result) + indicator_blueprint = IndicatorBlueprint.objects.create(title='The Blueprint') + lower_result = LowerResult.objects.create(result_link=result_link, name='Lower Result 1') + applied_indicator = AppliedIndicator.objects.create(indicator=indicator_blueprint, lower_result=lower_result, ) + applied_indicator.locations.add(LocationFactory( + name='A Location', gateway=GatewayTypeFactory(name='A Gateway'), p_code='a-p-code')) + disaggregation = applied_indicator.disaggregation.create(name='A Disaggregation') + + return { + "unicef_staff": unicef_staff, + "partnership_manager_user": partnership_user, + "partner": partner, + "partner1": partner1, + "agreement": agreement, + "active_agreement": active_agreement, + "intervention": intervention, + "intervention_2": InterventionFactory(agreement=agreement, title='Intervention 2', + document_type=Intervention.PD), + "active_intervention": active_intervention, + "reporting_requirement": ReportingRequirementFactory(intervention=active_intervention), + "result_type": result_type, + "result": result, + "partnership_budget": InterventionBudget.objects.create( + intervention=intervention, + unicef_cash=10, + unicef_cash_local=100, + partner_contribution=20, + partner_contribution_local=200, + in_kind_amount_local=10, + ), + "fr_1": FundsReservationHeaderFactory(intervention=None, currency='USD'), + "fr_2": FundsReservationHeaderFactory(intervention=None, currency='USD'), + "result_link": result_link, + "lower_result": lower_result, + "indicator_blueprint": indicator_blueprint, + "applied_indicator": applied_indicator, + "file_type_attachment": AttachmentFileTypeFactory(code="partners_intervention_attachment"), + "file_type_prc": AttachmentFileTypeFactory(code="partners_intervention_prc_review"), + "file_type_pd": AttachmentFileTypeFactory(code="partners_intervention_signed_pd"), + "disaggregation": disaggregation + } + + def test_prp_api(self): + url = reverse('prp_api_v1:prp-intervention-list') + self.assertGET(url, data={'workspace': self.tenant.business_area_code}) diff --git a/src/etools/applications/partners/tests/test_utils.py b/src/etools/applications/partners/tests/test_utils.py index 2207591e70..73153d795c 100644 --- a/src/etools/applications/partners/tests/test_utils.py +++ b/src/etools/applications/partners/tests/test_utils.py @@ -14,7 +14,11 @@ from etools.applications.partners.models import Agreement, Intervention, InterventionBudget, InterventionResultLink from etools.applications.partners.tests.factories import AgreementFactory, InterventionFactory, PartnerFactory from etools.applications.reports.models import AppliedIndicator, IndicatorBlueprint, LowerResult, ResultType -from etools.applications.reports.tests.factories import CountryProgrammeFactory, ResultFactory +from etools.applications.reports.tests.factories import ( + CountryProgrammeFactory, + ReportingRequirementFactory, + ResultFactory, +) from etools.applications.users.tests.factories import GroupFactory, UserFactory @@ -52,7 +56,7 @@ def setup_intervention_test_data(test_case, include_results_and_indicators=False unicef_signatory=test_case.unicef_staff, partner_authorized_officer_signatory=test_case.partner1.staff_members.all().first() ) - + test_case.reporting_requirement = ReportingRequirementFactory(intervention=test_case.active_intervention) test_case.result_type = ResultType.objects.get_or_create(name=ResultType.OUTPUT)[0] test_case.result = ResultFactory(result_type=test_case.result_type) diff --git a/src/etools/applications/partners/tests/test_views.py b/src/etools/applications/partners/tests/test_views.py index 2452409b5f..578c5c5b3f 100644 --- a/src/etools/applications/partners/tests/test_views.py +++ b/src/etools/applications/partners/tests/test_views.py @@ -1420,6 +1420,30 @@ def test_intervention_validation_doctype_ssfa(self): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn('Document type PD or HPD can only be associated with a PCA agreement.', response.data) + def test_intervention_validation_multiple_agreement_ssfa(self): + self.agreement.agreement_type = Agreement.SSFA + self.agreement.save() + intervention = Intervention.objects.get(id=self.intervention["id"]) + intervention.document_type = Intervention.SSFA + intervention.save() + self.agreement.interventions.add(intervention) + + response = self.forced_auth_req( + 'post', + reverse( + "partners_api:intervention-list" + ), + user=self.partnership_manager_user, + data={ + "agreement": self.agreement.id, + "document_type": Intervention.SSFA, + "status": Intervention.DRAFT, + "title": "test" + } + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn('You can only add one SSFA Document for each SSFA Agreement', response.data) + def test_intervention_validation_dates(self): today = datetime.date.today() data = { diff --git a/src/etools/applications/partners/urls_v2.py b/src/etools/applications/partners/urls_v2.py index 7d01c674a4..05a15cb3d5 100644 --- a/src/etools/applications/partners/urls_v2.py +++ b/src/etools/applications/partners/urls_v2.py @@ -34,6 +34,7 @@ InterventionResultLinkListCreateView, InterventionResultLinkUpdateView, InterventionResultListAPIView, + InterventionWithAppliedIndicatorsView, ) from etools.applications.partners.views.partner_organization_v2 import ( PartnerNotAssuranceCompliant, @@ -135,6 +136,10 @@ view=InterventionListAPIView.as_view(http_method_names=['get', 'post']), name='intervention-list'), + url(r'^interventions/applied-indicators/$', + view=InterventionWithAppliedIndicatorsView.as_view(http_method_names=['get', ]), + name='intervention-applied-indicators-list'), + url(r'^interventions/result-links/(?P\d+)/lower-results/$', view=InterventionLowerResultListCreateView.as_view(http_method_names=['get', 'post']), name='intervention-lower-results-list'), diff --git a/src/etools/applications/partners/validation/interventions.py b/src/etools/applications/partners/validation/interventions.py index fda53d784c..a2ac508335 100644 --- a/src/etools/applications/partners/validation/interventions.py +++ b/src/etools/applications/partners/validation/interventions.py @@ -222,7 +222,7 @@ def ssfa_agreement_has_no_other_intervention(i): if i.document_type == i.SSFA: if not(i.agreement.agreement_type == i.agreement.SSFA): raise BasicValidationError(_('Agreement selected is not of type SSFA')) - return i.agreement.interventions.all().count() <= 1 + return i.agreement.interventions.count() <= 1 return True diff --git a/src/etools/applications/partners/views/interventions_v2.py b/src/etools/applications/partners/views/interventions_v2.py index 388487b252..362a3a195e 100644 --- a/src/etools/applications/partners/views/interventions_v2.py +++ b/src/etools/applications/partners/views/interventions_v2.py @@ -31,6 +31,7 @@ PartnerScopeFilter, ) from etools.applications.partners.models import ( + Agreement, Intervention, InterventionAmendment, InterventionAttachment, @@ -66,6 +67,7 @@ InterventionResultCUSerializer, InterventionResultLinkSimpleCUSerializer, InterventionResultSerializer, + InterventionToIndicatorsListSerializer, MinimalInterventionListSerializer, PlannedVisitsCUSerializer, ) @@ -149,6 +151,15 @@ def create(self, request, *args, **kwargs): 'result_links' ] nested_related_names = ['ll_results'] + + if request.data.get('document_type') == Intervention.SSFA: + agreement = Agreement.objects.get(pk=request.data.get('agreement')) + if agreement and agreement.interventions.count(): + raise ValidationError( + 'You can only add one SSFA Document for each SSFA Agreement', + status.HTTP_400_BAD_REQUEST + ) + serializer = self.my_create(request, related_fields, nested_related_names=nested_related_names, @@ -216,6 +227,22 @@ def list(self, request): return response +class InterventionWithAppliedIndicatorsView(QueryStringFilterMixin, ListAPIView): + """ Interventions.""" + queryset = Intervention.objects.all() + serializer_class = InterventionToIndicatorsListSerializer + permission_classes = (PartnershipManagerPermission,) + + filters = ( + ('sections', 'sections__in'), + ('status', 'status__in'), + ) + + def get_queryset(self): + return super().get_queryset().select_related('agreement__partner').prefetch_related( + 'result_links__ll_results__applied_indicators__indicator') + + class InterventionListDashView(InterventionListBaseView): """ Create new Interventions. diff --git a/src/etools/applications/partners/views/partner_organization_v2.py b/src/etools/applications/partners/views/partner_organization_v2.py index 822c337eb5..0d63716d8b 100644 --- a/src/etools/applications/partners/views/partner_organization_v2.py +++ b/src/etools/applications/partners/views/partner_organization_v2.py @@ -471,6 +471,11 @@ def create(self, request, *args, **kwargs): country = request.user.profile.country partner_sync = PartnerSynchronizer(business_area_code=country.business_area_code) + + if not partner_sync._filter_records([partner_resp]): + return Response({"error": 'Partner skipped because one or more of the required fields are missing'}, + status=status.HTTP_400_BAD_REQUEST) + partner_sync._partner_save(partner_resp, full_sync=False) partner = PartnerOrganization.objects.get( diff --git a/src/etools/applications/partners/views/prp_v1.py b/src/etools/applications/partners/views/prp_v1.py index d498783589..926d46f0aa 100644 --- a/src/etools/applications/partners/views/prp_v1.py +++ b/src/etools/applications/partners/views/prp_v1.py @@ -1,12 +1,10 @@ -import functools -import operator - -from django.db.models import Q +from django.db.models import CharField, Count, Sum from rest_framework.generics import get_object_or_404, ListAPIView from rest_framework.pagination import LimitOffsetPagination from rest_framework.response import Response from rest_framework.views import APIView +from unicef_restlib.views import QueryStringFilterMixin from etools.applications.partners.filters import PartnerScopeFilter from etools.applications.partners.models import Intervention, PartnerOrganization @@ -17,6 +15,7 @@ PRPPartnerOrganizationListSerializer, ) from etools.applications.partners.views.helpers import set_tenant_or_fail +from etools.libraries.djangolib.models import MaxDistinct class PRPPDFileView(APIView): @@ -40,7 +39,7 @@ class PRPPartnerPagination(LimitOffsetPagination): default_limit = 300 -class PRPInterventionListAPIView(ListAPIView): +class PRPInterventionListAPIView(QueryStringFilterMixin, ListAPIView): """ Create new Interventions. Returns a list of Interventions. @@ -50,51 +49,45 @@ class PRPInterventionListAPIView(ListAPIView): filter_backends = (PartnerScopeFilter,) pagination_class = PRPInterventionPagination - def paginate_queryset(self, queryset): - return super().paginate_queryset(queryset) + queryset = Intervention.objects.filter( + result_links__ll_results__applied_indicators__isnull=False, + reporting_requirements__isnull=False, + in_amendment=False + ).prefetch_related( + 'result_links__cp_output', + 'result_links__ll_results', + 'result_links__ll_results__applied_indicators__indicator', + 'result_links__ll_results__applied_indicators__disaggregation__disaggregation_values', + 'result_links__ll_results__applied_indicators__locations', + 'special_reporting_requirements', + 'reporting_requirements', + 'frs', + 'partner_focal_points', + 'unicef_focal_points', + 'agreement__authorized_officers', + 'agreement__partner', + 'amendments', + 'flat_locations', + 'sections' + ).exclude(status=Intervention.DRAFT).annotate( + Count("frs__currency", distinct=True), + Sum("frs__actual_amt_local"), + max_fr_currency=MaxDistinct("frs__currency", output_field=CharField(), distinct=True), + ) + + filters = ( + ('country_programme', 'agreement__country_programme'), + ('section', 'sections__pk'), + ('status', 'status'), + ('partner', 'agreement__partner'), + ('updated_before', 'modified__lte'), + ('updated_after', 'modified__gte'), + ) def get_queryset(self, format=None): - q = Intervention.objects.prefetch_related( - 'result_links__cp_output', - 'result_links__ll_results', - 'result_links__ll_results__applied_indicators__indicator', - 'result_links__ll_results__applied_indicators__disaggregation__disaggregation_values', - 'result_links__ll_results__applied_indicators__locations', - 'special_reporting_requirements', - 'reporting_requirements', - 'frs', - 'partner_focal_points', - 'unicef_focal_points', - 'agreement__authorized_officers', - 'agreement__partner', - 'amendments', - 'flat_locations', - 'sections' - ).exclude(status=Intervention.DRAFT) - - query_params = self.request.query_params - workspace = query_params.get('workspace', None) + workspace = self.request.query_params.get('workspace', None) set_tenant_or_fail(workspace) - - if query_params: - queries = [] - if "country_programme" in query_params.keys(): - queries.append(Q(agreement__country_programme=query_params.get("country_programme"))) - if "section" in query_params.keys(): - queries.append(Q(sections__pk=query_params.get("section"))) - if "status" in query_params.keys(): - queries.append(Q(status=query_params.get("status"))) - if "partner" in query_params.keys(): - queries.append(Q(agreement__partner=query_params.get("partner"))) - if "updated_before" in query_params.keys(): - queries.append(Q(modified__lte=query_params.get("updated_before"))) - if "updated_after" in query_params.keys(): - queries.append(Q(modified__gte=query_params.get("updated_after"))) - if queries: - expression = functools.reduce(operator.and_, queries) - q = q.filter(expression).distinct() - - return q + return super().get_queryset() class PRPPartnerListAPIView(ListAPIView): @@ -105,15 +98,10 @@ class PRPPartnerListAPIView(ListAPIView): serializer_class = PRPPartnerOrganizationListSerializer permission_classes = (ListCreateAPIMixedPermission, ) pagination_class = PRPPartnerPagination - - def paginate_queryset(self, queryset): - return super().paginate_queryset(queryset) + queryset = PartnerOrganization.objects.all() def get_queryset(self, format=None): - q = PartnerOrganization.objects.all() - query_params = self.request.query_params workspace = query_params.get('workspace', None) set_tenant_or_fail(workspace) - - return q + return super().get_queryset() diff --git a/src/etools/applications/reports/admin.py b/src/etools/applications/reports/admin.py index 3f13d07c53..d507782c2d 100644 --- a/src/etools/applications/reports/admin.py +++ b/src/etools/applications/reports/admin.py @@ -52,7 +52,7 @@ def queryset(self, request, queryset): class SectionAdmin(admin.ModelAdmin): form = AutoSizeTextForm - list_display = ('name', 'color', 'dashboard',) + list_display = ('name', 'color', 'dashboard', 'active', ) list_editable = ('color', 'dashboard',) diff --git a/src/etools/applications/reports/migrations/0018_section_active.py b/src/etools/applications/reports/migrations/0018_section_active.py new file mode 100644 index 0000000000..57a677270d --- /dev/null +++ b/src/etools/applications/reports/migrations/0018_section_active.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.3 on 2019-07-09 19:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reports', '0017_auto_20190424_1509'), + ] + + operations = [ + migrations.AddField( + model_name='section', + name='active', + field=models.BooleanField(default=True, verbose_name='Active'), + ), + ] diff --git a/src/etools/applications/reports/migrations/0019_auto_20190816_1609.py b/src/etools/applications/reports/migrations/0019_auto_20190816_1609.py new file mode 100644 index 0000000000..5f12f0a839 --- /dev/null +++ b/src/etools/applications/reports/migrations/0019_auto_20190816_1609.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.4 on 2019-08-16 16:09 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('reports', '0018_section_active'), + ] + + operations = [ + migrations.AlterModelOptions( + name='appliedindicator', + options={'ordering': ('created',)}, + ), + migrations.AlterModelOptions( + name='lowerresult', + options={'ordering': ('created',)}, + ), + ] diff --git a/src/etools/applications/reports/models.py b/src/etools/applications/reports/models.py index 927bff6c7f..c330e5e06c 100644 --- a/src/etools/applications/reports/models.py +++ b/src/etools/applications/reports/models.py @@ -140,6 +140,7 @@ class Section(TimeStampedModel): alternate_name = models.CharField(max_length=255, null=True, default='', verbose_name=_('Alternate Name')) dashboard = models.BooleanField(default=False, verbose_name=_('Dashboard')) color = models.CharField(max_length=7, null=True, blank=True, verbose_name=_('Color')) + active = models.BooleanField(default=True, verbose_name=_('Active')) class Meta: ordering = ['name'] @@ -369,7 +370,7 @@ def __str__(self): class Meta: unique_together = (('result_link', 'code'),) - ordering = ('-created',) + ordering = ('created',) def save(self, **kwargs): if not self.code: @@ -674,6 +675,7 @@ def baseline_display(self): class Meta: unique_together = (("indicator", "lower_result"),) + ordering = ("created",) def save(self, *args, **kwargs): super().save(*args, **kwargs) diff --git a/src/etools/applications/reports/serializers/v2.py b/src/etools/applications/reports/serializers/v2.py index 95a74cd39c..d25ba07f58 100644 --- a/src/etools/applications/reports/serializers/v2.py +++ b/src/etools/applications/reports/serializers/v2.py @@ -59,7 +59,6 @@ class Meta: def create(self, validated_data): # always try to get first - validated_data['title'] = validated_data['title'].title() return IndicatorBlueprint.objects.get_or_create(**validated_data)[0] @@ -224,6 +223,14 @@ class Meta: fields = '__all__' +class AppliedIndicatorBasicSerializer(serializers.ModelSerializer): + title = serializers.ReadOnlyField(source='indicator.title') + + class Meta: + model = AppliedIndicator + fields = ('pk', 'title', 'section') + + class ClusterSerializer(serializers.ModelSerializer): class Meta: diff --git a/src/etools/applications/reports/views/v1.py b/src/etools/applications/reports/views/v1.py index 2f81c03bea..5c09420e8b 100644 --- a/src/etools/applications/reports/views/v1.py +++ b/src/etools/applications/reports/views/v1.py @@ -3,6 +3,7 @@ from rest_framework.generics import ListAPIView, RetrieveAPIView from rest_framework.permissions import IsAdminUser from rest_framework.response import Response +from unicef_restlib.views import QueryStringFilterMixin from etools.applications.reports.models import CountryProgramme, Indicator, Result, ResultType, Section, Unit from etools.applications.reports.serializers.v1 import ( @@ -25,7 +26,8 @@ class ResultTypeViewSet(mixins.ListModelMixin, serializer_class = ResultTypeSerializer -class SectionViewSet(mixins.RetrieveModelMixin, +class SectionViewSet(QueryStringFilterMixin, + mixins.RetrieveModelMixin, mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet): @@ -34,6 +36,9 @@ class SectionViewSet(mixins.RetrieveModelMixin, """ queryset = Section.objects.all() serializer_class = SectionCreateSerializer + filters = ( + ('active', 'active'), + ) class ResultViewSet(viewsets.ModelViewSet): diff --git a/src/etools/applications/reports/views/v2.py b/src/etools/applications/reports/views/v2.py index d5fc608f91..4d6ddb39a7 100644 --- a/src/etools/applications/reports/views/v2.py +++ b/src/etools/applications/reports/views/v2.py @@ -426,7 +426,8 @@ class ClusterListAPIView(ListAPIView): CSVRenderer, CSVFlatRenderer, ) - queryset = AppliedIndicator.objects.filter(cluster_name__isnull=False).values('cluster_name').distinct() + queryset = AppliedIndicator.objects.filter(cluster_name__isnull=False).order_by( + 'cluster_name').values('cluster_name').distinct() class SpecialReportingRequirementListCreateView(ListCreateAPIView): diff --git a/src/etools/applications/t2f/filters/__init__.py b/src/etools/applications/t2f/filters/__init__.py index 535445c10b..eb6baa4e9f 100644 --- a/src/etools/applications/t2f/filters/__init__.py +++ b/src/etools/applications/t2f/filters/__init__.py @@ -1,4 +1,3 @@ - from django.db.models import F from django.db.models.query_utils import Q @@ -80,6 +79,12 @@ def filter_queryset(self, request, queryset, view): if statuses is not None: statuses = statuses.split(',') queryset = queryset.filter(status__in=statuses) + + mf_sections = request.query_params.get('mf_sections', None) + if mf_sections is not None: + mf_sections = mf_sections.split(',') + queryset = queryset.filter(section__in=mf_sections) + # To have proper keys in data dict, the serializer renames the incoming values according to the needs return queryset.filter(**data) diff --git a/src/etools/applications/t2f/serializers/travel.py b/src/etools/applications/t2f/serializers/travel.py index 9795d893a2..8b820f989e 100644 --- a/src/etools/applications/t2f/serializers/travel.py +++ b/src/etools/applications/t2f/serializers/travel.py @@ -360,7 +360,7 @@ def update_object(self, obj, data): related_manager.add(*value) def update_related_objects(self, attr_name, related_data): - many = isinstance(self._fields[attr_name], serializers.ListSerializer) + many = isinstance(self.fields[attr_name], serializers.ListSerializer) try: related = getattr(self.instance, attr_name) @@ -371,7 +371,7 @@ def update_related_objects(self, attr_name, related_data): # Load the queryset related = related.all() - model = self._fields[attr_name].child.Meta.model + model = self.fields[attr_name].child.Meta.model related_to_delete = {o.pk for o in related} # Iterate over incoming data and create/update the models diff --git a/src/etools/applications/tpm/admin.py b/src/etools/applications/tpm/admin.py index a8b391d843..5968088edb 100644 --- a/src/etools/applications/tpm/admin.py +++ b/src/etools/applications/tpm/admin.py @@ -8,6 +8,7 @@ class TPMActivityAdmin(admin.ModelAdmin): list_display = ( '__str__', + 'is_pv' ) search_fields = ( 'tpm_visit__author__username', diff --git a/src/etools/applications/tpm/export/renderers.py b/src/etools/applications/tpm/export/renderers.py index e2c036b845..ead721aa4d 100644 --- a/src/etools/applications/tpm/export/renderers.py +++ b/src/etools/applications/tpm/export/renderers.py @@ -3,17 +3,19 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework_csv.renderers import CSVRenderer +from unicef_rest_export.renderers import FriendlyCSVRenderer -class TPMActivityCSVRenderer(CSVRenderer): +class TPMActivityCSVRenderer(FriendlyCSVRenderer): header = ['ref', 'visit', 'visit_status', 'activity', 'section', 'cp_output', 'partner', 'intervention', 'pd_ssfa', - 'locations', 'date', 'unicef_focal_points', 'offices', 'tpm_focal_points', 'visit_information', + 'locations', 'date', 'unicef_focal_points', 'offices', 'tpm_focal_points', 'visit_information', 'is_pv', 'additional_information', 'link'] labels = { 'ref': _('Visit Ref. #'), 'visit': _('Visit'), 'visit_status': _('Status of Visit'), 'activity': _('Task'), + 'is_pv': _('Is Programmatic Visit'), 'section': _('Section'), 'cp_output': _('PD/SSFA output'), 'partner': _('Partner'), diff --git a/src/etools/applications/tpm/export/serializers.py b/src/etools/applications/tpm/export/serializers.py index 2a92d2677f..dcc196c8a2 100644 --- a/src/etools/applications/tpm/export/serializers.py +++ b/src/etools/applications/tpm/export/serializers.py @@ -24,6 +24,7 @@ class TPMActivityExportSerializer(serializers.Serializer): visit_information = serializers.CharField(source='tpm_visit.visit_information') visit_status = serializers.CharField(source='tpm_visit.get_status_display') activity = serializers.SerializerMethodField() + is_pv = serializers.BooleanField() section = serializers.CharField() cp_output = serializers.CharField() partner = serializers.CharField() diff --git a/src/etools/applications/tpm/filters.py b/src/etools/applications/tpm/filters.py index 13a96b1c38..75cfedf9ff 100644 --- a/src/etools/applications/tpm/filters.py +++ b/src/etools/applications/tpm/filters.py @@ -3,7 +3,7 @@ from django_filters import rest_framework as filters from rest_framework.filters import BaseFilterBackend -from etools.applications.tpm.models import TPMVisit +from etools.applications.tpm.models import TPMActivity, TPMVisit class ReferenceNumberOrderingFilter(BaseFilterBackend): @@ -19,9 +19,8 @@ def filter_queryset(self, request, queryset, view): class TPMVisitFilter(filters.FilterSet): - tpm_activities__offices__in = filters.BaseInFilter( - field_name="tpm_activities__offices", - ) + tpm_activities__offices__in = filters.BaseInFilter(field_name="tpm_activities__offices") + tpm_partner_focal_points__in = filters.BaseInFilter(field_name="tpm_partner_focal_points") class Meta: model = TPMVisit @@ -36,5 +35,20 @@ class Meta: 'tpm_activities__date': ['exact', 'lte', 'gte', 'gt', 'lt'], 'status': ['exact', 'in'], 'tpm_activities__unicef_focal_points': ['exact'], - 'tpm_partner_focal_points': ['exact'], + 'tpm_partner_focal_points': ['exact', 'in'], + } + + +class TPMActivityFilter(filters.FilterSet): + offices__in = filters.BaseInFilter(field_name="offices") + + class Meta: + model = TPMActivity + fields = { + 'tpm_visit': ['exact'], + 'section': ['exact', 'in'], + 'offices': ['exact', 'in'], + 'tpm_visit__tpm_partner': ['exact'], + 'partner': ['exact', 'in'], + 'tpm_visit__status': ['exact', 'in'], } diff --git a/src/etools/applications/tpm/management/commands/update_tpm_permissions.py b/src/etools/applications/tpm/management/commands/update_tpm_permissions.py index 14fdc3875d..fb51d12c30 100644 --- a/src/etools/applications/tpm/management/commands/update_tpm_permissions.py +++ b/src/etools/applications/tpm/management/commands/update_tpm_permissions.py @@ -243,8 +243,13 @@ def assign_permissions(self): condition=self.visit_status(TPMVisit.STATUSES.draft)) # visit cancelled + tpm_cancelled_condition = self.visit_status(TPMVisit.STATUSES.cancelled) self.add_permissions([self.unicef_user, self.third_party_monitor], 'view', ['tpm.tpmvisit.cancel_comment'], condition=self.visit_status(TPMVisit.STATUSES.cancelled)) + self.add_permissions(self.third_party_monitor, 'view', self.tpm_visit_details, + condition=tpm_cancelled_condition) + self.add_permissions(self.third_party_monitor, 'view', self.visit_report, + condition=tpm_cancelled_condition) # visit assigned self.add_permissions(self.third_party_monitor, 'view', self.tpm_visit_details, @@ -295,11 +300,12 @@ def assign_permissions(self): self.add_permissions(self.unicef_user, 'view', self.action_points_block, condition=tpm_reported_condition) - self.add_permissions(self.pme, 'view', 'tpm.tpmactivity.pv_applicable', + self.add_permissions([self.pme, self.focal_point], 'view', 'tpm.tpmactivity.pv_applicable', condition=tpm_reported_condition) - self.add_permissions(self.pme, 'edit', ['tpm.tpmvisit.approval_comment', 'tpm.tpmvisit.report_reject_comments'], + self.add_permissions([self.pme, self.focal_point], 'edit', + ['tpm.tpmvisit.approval_comment', 'tpm.tpmvisit.report_reject_comments'], condition=tpm_reported_condition) - self.add_permissions(self.pme, 'action', + self.add_permissions([self.pme, self.focal_point], 'action', ['tpm.tpmvisit.approve', 'tpm.tpmvisit.reject_report'], condition=tpm_reported_condition) diff --git a/src/etools/applications/tpm/serializers/visit.py b/src/etools/applications/tpm/serializers/visit.py index a8e1a0896f..058fc33619 100644 --- a/src/etools/applications/tpm/serializers/visit.py +++ b/src/etools/applications/tpm/serializers/visit.py @@ -103,6 +103,18 @@ def create(self, validated_data): return super().create(validated_data) +class TPMActivityLightSerializer(serializers.ModelSerializer): + + description = serializers.ReadOnlyField(source='__str__') + + class Meta: + model = TPMActivity + fields = [ + 'id', 'description', 'date', 'tpm_visit', 'is_pv', 'partner', 'intervention', 'cp_output', 'section', + 'unicef_focal_points', 'locations', 'additional_information', 'offices', + ] + + class TPMActivitySerializer(PermissionsBasedSerializerMixin, WritableNestedSerializerMixin, ActivitySerializer): partner = SeparatedReadWriteField( diff --git a/src/etools/applications/tpm/templates/tpm/visit_letter_pdf.html b/src/etools/applications/tpm/templates/tpm/visit_letter_pdf.html index cd26de7e11..222fcb4f2e 100644 --- a/src/etools/applications/tpm/templates/tpm/visit_letter_pdf.html +++ b/src/etools/applications/tpm/templates/tpm/visit_letter_pdf.html @@ -57,7 +57,7 @@ {% block page_header %} @@ -93,7 +93,7 @@

UNICEF kindly asks the reader of this letter to facilitate the work of the staff of {{ visit.tpm_partner.name }} in order to carry out these visits.

- + diff --git a/src/etools/applications/tpm/tests/test_transitions.py b/src/etools/applications/tpm/tests/test_transitions.py index fe2d54f6dc..011121f754 100644 --- a/src/etools/applications/tpm/tests/test_transitions.py +++ b/src/etools/applications/tpm/tests/test_transitions.py @@ -249,7 +249,10 @@ def setUpTestData(cls): class FPPermissionsForTpmTransitionTestCase(TPMTransitionPermissionsTestCase): - ALLOWED_TRANSITION = [] + ALLOWED_TRANSITION = [ + ('tpm_reported', 'reject_report'), + ('tpm_reported', 'approve'), + ] @classmethod def setUpTestData(cls): diff --git a/src/etools/applications/tpm/urls.py b/src/etools/applications/tpm/urls.py index ceab4c16a1..aa3ffb2521 100644 --- a/src/etools/applications/tpm/urls.py +++ b/src/etools/applications/tpm/urls.py @@ -9,6 +9,7 @@ ActivityReportAttachmentsViewSet, PartnerAttachmentsViewSet, TPMActionPointViewSet, + TPMActivityViewSet, TPMPartnerViewSet, TPMStaffMembersViewSet, TPMVisitViewSet, @@ -28,6 +29,7 @@ tpm_visits_api = routers.SimpleRouter() tpm_visits_api.register(r'visits', TPMVisitViewSet, basename='visits') +tpm_visits_api.register(r'activities', TPMActivityViewSet, basename='activities') visit_attachments_api = NestedComplexRouter(tpm_visits_api, r'visits') visit_attachments_api.register('report-attachments', VisitReportAttachmentsViewSet, diff --git a/src/etools/applications/tpm/views.py b/src/etools/applications/tpm/views.py index 5817f8f3c1..06719628dc 100644 --- a/src/etools/applications/tpm/views.py +++ b/src/etools/applications/tpm/views.py @@ -53,7 +53,7 @@ TPMPartnerExportSerializer, TPMVisitExportSerializer, ) -from etools.applications.tpm.filters import ReferenceNumberOrderingFilter, TPMVisitFilter +from etools.applications.tpm.filters import ReferenceNumberOrderingFilter, TPMActivityFilter, TPMVisitFilter from etools.applications.tpm.models import PME, ThirdPartyMonitor, TPMActionPoint, TPMActivity, TPMVisit, UNICEFUser from etools.applications.tpm.serializers.attachments import ( ActivityAttachmentsSerializer, @@ -72,6 +72,7 @@ ) from etools.applications.tpm.serializers.visit import ( TPMActionPointSerializer, + TPMActivityLightSerializer, TPMVisitDraftSerializer, TPMVisitLightSerializer, TPMVisitSerializer, @@ -310,8 +311,7 @@ class TPMVisitViewSet( viewsets.GenericViewSet ): metadata_class = PermissionBasedMetadata - queryset = TPMVisit.objects.all().prefetch_related( - 'tpm_partner', + queryset = TPMVisit.objects.select_related('tpm_partner').prefetch_related( 'tpm_activities__unicef_focal_points', ) serializer_class = TPMVisitSerializer @@ -459,6 +459,16 @@ def tpm_visit_letter(self, request, *args, **kwargs): ) +class TPMActivityViewSet(viewsets.ReadOnlyModelViewSet): + queryset = TPMActivity.objects.all() + serializer_class = TPMActivityLightSerializer + filter_class = TPMActivityFilter + + filter_backends = (SearchFilter, OrderingFilter, DjangoFilterBackend) + search_fields = ('tpm_visit__tpm_partner__vendor_number', 'tpm_visit__tpm_partner__name', + 'partner__name', 'partner__vendor_number') + + class TPMActionPointViewSet(BaseTPMViewSet, mixins.ListModelMixin, mixins.CreateModelMixin, diff --git a/src/etools/applications/vision/exceptions.py b/src/etools/applications/vision/exceptions.py deleted file mode 100644 index 6cf47cfdaa..0000000000 --- a/src/etools/applications/vision/exceptions.py +++ /dev/null @@ -1,5 +0,0 @@ - - -class VisionSyncException(Exception): - def __init__(self, message): - self.message = message diff --git a/src/etools/assets/images/letter_logo_bottom.png b/src/etools/assets/images/letter_logo_bottom.png new file mode 100644 index 0000000000..f97f338e96 Binary files /dev/null and b/src/etools/assets/images/letter_logo_bottom.png differ diff --git a/src/etools/assets/images/qDWQF3.png b/src/etools/assets/images/letter_logo_top.png similarity index 100% rename from src/etools/assets/images/qDWQF3.png rename to src/etools/assets/images/letter_logo_top.png diff --git a/src/etools/config/settings/base.py b/src/etools/config/settings/base.py index 7dcb0e69c8..a534ae1d25 100644 --- a/src/etools/config/settings/base.py +++ b/src/etools/config/settings/base.py @@ -476,15 +476,6 @@ def before_send(event, hint): 'etools.applications.core.auth.DRFBasicAuthMixin', ) -ISSUE_CHECKS = [ - 'etools.applications.management.issues.project_checks.ActivePCANoSignedDocCheck', - 'etools.applications.management.issues.project_checks.PdOutputsWrongCheck', - 'etools.applications.management.issues.project_checks.InterventionsAssociatedSSFACheck', - 'etools.applications.management.issues.project_checks.InterventionsAreValidCheck', - 'etools.applications.management.issues.project_checks.PDAmendmentsMissingFilesCheck', - 'etools.applications.management.issues.project_checks.PCAAmendmentsMissingFilesCheck', -] - EMAIL_FOR_USER_RESPONSIBLE_FOR_INVESTIGATION_ESCALATIONS = get_from_secrets_or_env( 'EMAIL_FOR_USER_RESPONSIBLE_FOR_INVESTIGATION_ESCALATIONS', 'integrity1@unicef.org' ) @@ -496,17 +487,6 @@ def before_send(event, hint): AZURE_GRAPH_API_VERSION = 'beta' AZURE_GRAPH_API_PAGE_SIZE = 250 -# drfpaswordless: https://github.com/aaronn/django-rest-framework-passwordless - -PASSWORDLESS_AUTH = { - # we can't use email here, because to_alias field length is 40, while email can be up to 254 symbols length. - # with custom user model we can avoid this a bit tricky with custom property like cropped_email, - # but for contrib user there is nothing better than use field having appropriate max length. - # username is better choice as it can be only 30 symbols max and unique. - 'PASSWORDLESS_USER_EMAIL_FIELD_NAME': 'username' -} - - KEY = os.getenv('AZURE_B2C_CLIENT_ID', None) SECRET = os.getenv('AZURE_B2C_CLIENT_SECRET', None) @@ -564,3 +544,7 @@ def before_send(event, hint): GEOS_LIBRARY_PATH = os.getenv('GEOS_LIBRARY_PATH', '/usr/lib/libgeos_c.so.1') # default path GDAL_LIBRARY_PATH = os.getenv('GDAL_LIBRARY_PATH', '/usr/lib/libgdal.so.20') # default path + +SHELL_PLUS_PRE_IMPORTS = ( + ('etools.applications.core.util_scripts', '*'), +) diff --git a/src/etools/config/settings/local.py b/src/etools/config/settings/local.py index 5c5e384e5a..0611630acf 100644 --- a/src/etools/config/settings/local.py +++ b/src/etools/config/settings/local.py @@ -90,7 +90,3 @@ } LOGGING['handlers']['console']['filters'] = ['tenant_context'] LOGGING['handlers']['console']['formatter'] = 'tenant_context' - -SHELL_PLUS_PRE_IMPORTS = ( - ('etools.applications.core.util_scripts', '*'), -) diff --git a/tox.ini b/tox.ini index c4ee6ce4a8..0daeb0ee1f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = d{21,22} +envlist = d{22} [testenv] basepython=python3.7 @@ -17,12 +17,6 @@ whitelist_externals = pipenv commands = pipenv install --dev --ignore-pipfile -[testenv:d21] -commands = - {[testenv]commands} - pip install "django>=2.1,<2.2" - ./runtests.sh - [testenv:d22] commands = {[testenv]commands}