Merge pull request #28 from sartography/dev

Dev --> Testing
This commit is contained in:
Aaron Louie 2020-08-13 16:51:05 -04:00 committed by GitHub
commit 561af4ba58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 3969 additions and 115 deletions

View File

@ -29,4 +29,4 @@ werkzeug = "*"
flask-cors = "*"
[requires]
python_version = "3.7"
python_version = "3.8"

198
Pipfile.lock generated
View File

@ -1,11 +1,11 @@
{
"_meta": {
"hash": {
"sha256": "a54fa04ebf32589249df94f3e70521c5af1b7ac3b6c8ca4c2bfd9e02dd572092"
"sha256": "3772dd5e9d33ab21b8303e235c50fc23feaa086e526e2fc9fb032f2b355e01ec"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
"python_version": "3.8"
},
"sources": [
{
@ -20,6 +20,7 @@
"hashes": [
"sha256:035ab00497217628bf5d0be82d664d8713ab13d37b630084da8e1f98facf4dbf"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.4.2"
},
"attrs": {
@ -27,6 +28,7 @@
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==19.3.0"
},
"babel": {
@ -34,6 +36,7 @@
"sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38",
"sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.0"
},
"certifi": {
@ -55,6 +58,7 @@
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==7.1.2"
},
"clickclick": {
@ -131,11 +135,11 @@
},
"flask-sqlalchemy": {
"hashes": [
"sha256:0b656fbf87c5f24109d859bafa791d29751fabbda2302b606881ae5485b557a5",
"sha256:fcfe6df52cd2ed8a63008ca36b86a51fa7a4b70cef1c39e5625f722fca32308e"
"sha256:05b31d2034dd3f2a685cbbae4cfc4ed906b2a733cff7964ada450fd5e462b84e",
"sha256:bfc7150eaf809b1c283879302f04c42791136060c6eeb12c0c6674fb1291fae5"
],
"index": "pypi",
"version": "==2.4.3"
"version": "==2.4.4"
},
"flask-table": {
"hashes": [
@ -222,16 +226,9 @@
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.10"
},
"importlib-metadata": {
"hashes": [
"sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83",
"sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"
],
"markers": "python_version < '3.8'",
"version": "==1.7.0"
},
"infinity": {
"hashes": [
"sha256:8daa7c15ce2100fdccfde212337e0cd5cf085869f54dc2634b6c30d61461ecda"
@ -243,19 +240,21 @@
"sha256:88b101b2668a1d81d6d72d4c2018e53bc6c7fc544c987849da1c7f77545c3bc9",
"sha256:f576e85132d34f5bf7df5183c2c6f94cfb32e528f53065345cf71329ba0b8924"
],
"markers": "python_version >= '3.5'",
"version": "==0.5.0"
},
"intervals": {
"hashes": [
"sha256:37921da1407a5e9384e8e1350cfb8500f8d0d69fc43d03d01a4fdc6e7a7c7166"
"sha256:c49f6956d0f26e4b3b1d07059c3801146747fa1e372c0023b171a53ea3bdfef5"
],
"version": "==0.8.1"
"version": "==0.9.0"
},
"itsdangerous": {
"hashes": [
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.1.0"
},
"jinja2": {
@ -263,6 +262,7 @@
"sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
"sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.11.2"
},
"jsonschema": {
@ -290,6 +290,7 @@
"sha256:8195c8c1400ceb53496064314c6736719c6f25e7479cd24c77be3d9361cddc27",
"sha256:93729a258e4ff0747c876bd9e20df1b9758028946e976324ccd2d68245c7b6a9"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.1.3"
},
"markupsafe": {
@ -328,14 +329,16 @@
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.1.1"
},
"marshmallow": {
"hashes": [
"sha256:35ee2fb188f0bd9fc1cf9ac35e45fd394bd1c153cee430745a465ea435514bd5",
"sha256:9aa20f9b71c992b4782dad07c51d92884fd0f7c5cb9d3c737bea17ec1bad765f"
"sha256:67bf4cae9d3275b3fc74bd7ff88a7c98ee8c57c94b251a67b031dc293ecc4b76",
"sha256:a2a5eefb4b75a3b43f05be1cca0b6686adf56af7465c3ca629e5ad8d1e1fe13d"
],
"version": "==3.6.1"
"markers": "python_version >= '3.5'",
"version": "==3.7.1"
},
"marshmallow-sqlalchemy": {
"hashes": [
@ -347,11 +350,11 @@
},
"openapi-spec-validator": {
"hashes": [
"sha256:0caacd9829e9e3051e830165367bf58d436d9487b29a09220fa7edb9f47ff81b",
"sha256:d4da8aef72bf5be40cf0df444abd20009a41baf9048a8e03750c07a934f1bdd8",
"sha256:e489c7a273284bc78277ac22791482e8058d323b4a265015e9fcddf6a8045bcd"
"sha256:6dd75e50c94f1bb454d0e374a56418e7e06a07affb2c7f1df88564c5d728dac3",
"sha256:79381a69b33423ee400ae1624a461dae7725e450e2e306e32f2dd8d16a4d85cb",
"sha256:ec1b01a00e20955a527358886991ae34b4b791b253027ee9f7df5f84b59d91c7"
],
"version": "==0.2.8"
"version": "==0.2.9"
},
"psycopg2-binary": {
"hashes": [
@ -407,13 +410,16 @@
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.1"
},
"python-editor": {
"hashes": [
"sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8",
"sha256:c3da2053dbab6b29c94e43c486ff67206eafbe7eb52dbec7390b5e2fb05aac77",
"sha256:1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d",
"sha256:51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b",
"sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8"
"sha256:ea87e17f6ec459e780e4221f295411462e0d0810858e055fc514684350a2f522",
"sha256:51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b"
],
"version": "==1.0.4"
},
@ -445,6 +451,7 @@
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.24.0"
},
"six": {
@ -452,6 +459,7 @@
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.15.0"
},
"sqlalchemy": {
@ -490,30 +498,32 @@
},
"sqlalchemy-utils": {
"hashes": [
"sha256:be319a16022b6a01e1d6c838340485beb4d34fd9c1c19d2303356804fa0faa09"
"sha256:fb66e9956e41340011b70b80f898fde6064ec1817af77199ee21ace71d7d6ab0"
],
"version": "==0.36.7"
"version": "==0.36.8"
},
"swagger-ui-bundle": {
"hashes": [
"sha256:49d2e12d60a6499e9d37ea37953b5d700f4e114edc7520fe918bae5eb693a20e",
"sha256:c5373b683487b1b914dccd23bcd9a3016afa2c2d1cda10f8713c0a9af0f91dd3",
"sha256:f776811855092c086dbb08216c8810a84accef8c76c796a135caa13645c5cc68"
"sha256:f5255f786cde67a2638111f4a7d04355836743198a83c4ecbe815d9fc384b0c8",
"sha256:f5691167f2e9f73ecbe8229a89454ae5ea958f90bb0d4583ed7adaae598c4122"
],
"version": "==0.0.6"
"version": "==0.0.8"
},
"urllib3": {
"hashes": [
"sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527",
"sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"
"sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a",
"sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
],
"version": "==1.25.9"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==1.25.10"
},
"validators": {
"hashes": [
"sha256:31e8bb01b48b48940a021b8a9576b840f98fa06b91762ef921d02cb96d38727a"
"sha256:401cb441dd61bb1a03b10c8a3a884642409e22a2a19e03bbfc4891e0ddbc7268",
"sha256:898b6b8197fbc320daf25d3b32fa928fd25e225c33790cb58ed54b48aebe1858"
],
"version": "==0.15.0"
"markers": "python_version >= '3.4'",
"version": "==0.17.1"
},
"webassets": {
"hashes": [
@ -532,10 +542,10 @@
},
"wtforms": {
"hashes": [
"sha256:6ff8635f4caeed9f38641d48cfe019d0d3896f41910ab04494143fc027866e1b",
"sha256:861a13b3ae521d6700dac3b2771970bd354a63ba7043ecc3a82b5288596a1972"
"sha256:7b504fc724d0d1d4d5d5c114e778ec88c37ea53144683e084215eed5155ada4c",
"sha256:81195de0ac94fbc8368abbaf9197b88c4f3ffd6c2719b5bf5fc9da744f3d829c"
],
"version": "==2.3.1"
"version": "==2.3.3"
},
"wtforms-alchemy": {
"hashes": [
@ -550,13 +560,6 @@
],
"version": "==0.10.4"
},
"zipp": {
"hashes": [
"sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b",
"sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"
],
"version": "==3.1.0"
},
"zope.event": {
"hashes": [
"sha256:69c27debad9bdacd9ce9b735dad382142281ac770c4a432b533d6d65c4614bcf",
@ -607,6 +610,7 @@
"sha256:f68bf937f113b88c866d090fea0bc52a098695173fc613b055a17ff0cf9683b6",
"sha256:fb55c182a3f7b84c1a2d6de5fa7b1a05d4660d866b91dbf8d74549c57a1499e8"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==5.1.0"
}
},
@ -616,58 +620,62 @@
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==19.3.0"
},
"coverage": {
"hashes": [
"sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a",
"sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355",
"sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65",
"sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7",
"sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9",
"sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1",
"sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0",
"sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55",
"sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c",
"sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6",
"sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef",
"sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019",
"sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e",
"sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0",
"sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf",
"sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24",
"sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2",
"sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c",
"sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4",
"sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0",
"sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd",
"sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04",
"sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e",
"sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730",
"sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2",
"sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768",
"sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796",
"sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7",
"sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a",
"sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489",
"sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052"
"sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb",
"sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3",
"sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716",
"sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034",
"sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3",
"sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8",
"sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0",
"sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f",
"sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4",
"sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962",
"sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d",
"sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b",
"sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4",
"sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3",
"sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258",
"sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59",
"sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01",
"sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd",
"sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b",
"sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d",
"sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89",
"sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd",
"sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b",
"sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d",
"sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46",
"sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546",
"sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082",
"sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b",
"sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4",
"sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8",
"sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811",
"sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd",
"sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651",
"sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0"
],
"index": "pypi",
"version": "==5.1"
"version": "==5.2.1"
},
"importlib-metadata": {
"iniconfig": {
"hashes": [
"sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83",
"sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"
"sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437",
"sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69"
],
"markers": "python_version < '3.8'",
"version": "==1.7.0"
"version": "==1.0.1"
},
"more-itertools": {
"hashes": [
"sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5",
"sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"
],
"markers": "python_version >= '3.5'",
"version": "==8.4.0"
},
"packaging": {
@ -675,6 +683,7 @@
"sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8",
"sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==20.4"
},
"pbr": {
@ -690,6 +699,7 @@
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.13.1"
},
"py": {
@ -697,6 +707,7 @@
"sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2",
"sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.9.0"
},
"pyparsing": {
@ -704,36 +715,31 @@
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7"
},
"pytest": {
"hashes": [
"sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1",
"sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"
"sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4",
"sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad"
],
"index": "pypi",
"version": "==5.4.3"
"version": "==6.0.1"
},
"six": {
"hashes": [
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.15.0"
},
"wcwidth": {
"toml": {
"hashes": [
"sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784",
"sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"
"sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f",
"sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"
],
"version": "==0.2.5"
},
"zipp": {
"hashes": [
"sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b",
"sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"
],
"version": "==3.1.0"
"version": "==0.10.1"
}
}
}

46
example_data.py Normal file
View File

@ -0,0 +1,46 @@
import csv
from pb import db, session
from pb.models import Sponsor
class ExampleDataLoader:
@staticmethod
def clean_db():
session.flush() # Clear out any transactions before deleting it all to avoid spurious errors.
for table in reversed(db.metadata.sorted_tables):
session.execute(table.delete())
session.commit()
session.flush()
@staticmethod
def load_all():
ExampleDataLoader().load_sponsors()
# self.load_studies()
# self.load_investigators()
@staticmethod
def load_sponsors():
# Load sponsors from csv
with open('./pb/static/csv/sponsors.csv') as csv_file:
data = csv.reader(csv_file, delimiter=',')
first_line = True
sponsors = []
for row in data:
# Skip first line, which will be the column headings
if first_line:
# row[0]: SPONSOR_ID
# row[1]: SP_NAME
# row[2]: SP_MAILING_ADDRESS
# row[3]: SP_TYPE
first_line = False
elif int(row[0] or -1) != -1:
new_sponsor = Sponsor(SPONSOR_ID=int(row[0]), SP_NAME=row[1], SP_MAILING_ADDRESS=row[2], SP_TYPE=row[3])
new_sponsor.SP_TYPE_GROUP_NAME = Sponsor.get_type_group_name(new_sponsor.SP_TYPE)
sponsors.append(new_sponsor)
session.add_all(sponsors)
session.commit()

View File

@ -0,0 +1,49 @@
"""empty message
Revision ID: ffba4886d280
Revises: d3592c4e8a39
Create Date: 2020-08-12 14:06:05.072787
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'ffba4886d280'
down_revision = 'd3592c4e8a39'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('sponsor',
sa.Column('SPONSOR_ID', sa.Integer(), nullable=False),
sa.Column('SP_NAME', sa.String(), nullable=True),
sa.Column('SP_MAILING_ADDRESS', sa.String(), nullable=True),
sa.Column('SP_PHONE', sa.String(), nullable=True),
sa.Column('SP_FAX', sa.String(), nullable=True),
sa.Column('SP_EMAIL', sa.String(), nullable=True),
sa.Column('SP_HOMEPAGE', sa.String(), nullable=True),
sa.Column('COMMONRULEAGENCY', sa.Boolean(), nullable=True),
sa.Column('SP_TYPE', sa.String(), nullable=True),
sa.Column('SP_TYPE_GROUP_NAME', sa.String(), nullable=True),
sa.PrimaryKeyConstraint('SPONSOR_ID')
)
op.create_table('study_sponsor',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('SS_STUDY', sa.Integer(), nullable=True),
sa.Column('SPONSOR_ID', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['SPONSOR_ID'], ['sponsor.SPONSOR_ID'], ),
sa.ForeignKeyConstraint(['SS_STUDY'], ['study.STUDYID'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('study_sponsor')
op.drop_table('sponsor')
# ### end Alembic commands ###

View File

@ -59,6 +59,11 @@ origins_re = [r"^https?:\/\/%s(.*)" % o.replace('.', '\.') for o in app.config['
cors = CORS(connexion_app.app, origins=origins_re)
db = SQLAlchemy(app)
""":type: sqlalchemy.orm.SQLAlchemy"""
session = db.session
""":type: sqlalchemy.orm.Session"""
migrate = Migrate(app, db)
ma = Marshmallow(app)
@ -115,6 +120,21 @@ def has_no_empty_params(rule):
return len(defaults) >= len(arguments)
@app.cli.command()
def load_example_data():
"""Load example data into the database."""
from example_data import ExampleDataLoader
ExampleDataLoader().clean_db()
ExampleDataLoader().load_all()
@app.cli.command()
def load_example_sponsors():
"""Load example data into the database."""
from example_data import ExampleDataLoader
ExampleDataLoader().load_sponsors()
@app.route('/site_map')
def site_map():
links = []
@ -130,9 +150,9 @@ def site_map():
# **************************
# WEB FORMS
# **************************
from pb.forms import StudyForm, StudyTable, InvestigatorForm, StudyDetailsForm, ConfirmDeleteForm
from pb.forms import StudyForm, StudyTable, InvestigatorForm, StudyDetailsForm, ConfirmDeleteForm, StudySponsorForm
from pb.models import Study, RequiredDocument, Investigator, StudySchema, RequiredDocumentSchema, InvestigatorSchema, \
StudyDetails, StudyDetailsSchema
StudyDetails, StudyDetailsSchema, StudySponsor, Sponsor
@app.route('/', methods=['GET', 'POST'])
@ -256,6 +276,84 @@ def del_investigator(inv_id):
return redirect_home()
@app.route('/study_sponsor/<study_id>', methods=['GET', 'POST'])
def edit_study_sponsor(study_id):
study = db.session.query(Study).filter(Study.STUDYID == study_id).first()
form = StudySponsorForm(request.form)
action = BASE_HREF + "/study_sponsor/" + study_id
title = "Edit sponsors for Study " + study_id
form.SPONSOR_IDS.choices = [(s.SPONSOR_ID, f'{s.SP_NAME} ({s.SP_TYPE})') for s in db.session.query(Sponsor).all()]
if request.method == 'GET':
if hasattr(study, 'sponsors'):
form.SPONSOR_IDS.data = [s.SPONSOR_ID for s in study.sponsors]
if request.method == 'POST':
# Remove all existing sponsors
session.query(StudySponsor).filter(StudySponsor.SS_STUDY == study_id).delete()
# Add the new ones
for sponsor_id in form.SPONSOR_IDS.data:
study_sponsor = StudySponsor(SS_STUDY=study_id, SPONSOR_ID=sponsor_id)
db.session.add(study_sponsor)
db.session.commit()
sponsor_label = 'sponsor' if len(form.SPONSOR_IDS.data) == 1 else 'sponsors'
flash(f'Study {sponsor_label} edited successfully!', 'success')
return redirect_home()
return render_template(
'form.html',
form=form,
action=action,
title=title,
description_map={},
base_href=BASE_HREF
)
@app.route('/del_study_sponsor/<study_sponsor_id>', methods=['GET', 'POST'])
def del_study_sponsor(study_sponsor_id):
study_sponsor_id = int(study_sponsor_id)
study_sponsor_model: StudySponsor = db.session.query(StudySponsor).filter(StudySponsor.id == study_sponsor_id).first()
if study_sponsor_model is None:
flash('StudySponsor not found.', 'warn')
return redirect_home()
sponsor_span = f'<span class="highlight">{study_sponsor_model.sponsor.SP_NAME} ' \
f'({study_sponsor_model.sponsor.SP_TYPE})</span>'
study_id = int(study_sponsor_model.SS_STUDY)
form = ConfirmDeleteForm(request.form, obj=study_sponsor_model)
if request.method == 'GET':
action = f'{BASE_HREF}/del_study_sponsor/{study_sponsor_id}'
title = 'Remove study sponsor?'
details = f'Are you sure you want to remove {sponsor_span} ' \
f'as a sponsor of Study {study_id}? ' \
f'This will not remove the sponsor itself from the system.'
return render_template(
'form.html',
form=form,
action=action,
title=title,
details=details,
description_map=description_map,
base_href=BASE_HREF
)
if request.method == 'POST':
if form.confirm and form.confirm.data:
db.session.query(StudySponsor).filter(StudySponsor.id == study_sponsor_id).delete()
db.session.commit()
flash(f'Sponsor {sponsor_span} removed from Study {study_id}.', 'success')
else:
flash('Delete canceled.', 'info')
return redirect_home()
@app.route('/del_study/<study_id>', methods=['GET', 'POST'])
def del_study(study_id):
study_id = int(study_id)
@ -267,9 +365,9 @@ def del_study(study_id):
form = ConfirmDeleteForm(request.form, obj=study_model)
if request.method == 'GET':
action = BASE_HREF + "/del_study/%i" % study_id
title = "Delete Study #%i?" % study_id
details = "Are you sure you want to delete Study '%s'?" % study_model.TITLE
action = f'{BASE_HREF}/del_study/{study_id}'
title = f'Delete Study #{study_id}?'
details = f'Are you sure you want to delete Study <span class="highlight">{study_model.TITLE}</span>?'
return render_template(
'form.html',
@ -286,7 +384,9 @@ def del_study(study_id):
db.session.query(RequiredDocument).filter(RequiredDocument.STUDYID == study_id).delete()
db.session.query(Investigator).filter(Investigator.STUDYID == study_id).delete()
db.session.query(StudyDetails).filter(StudyDetails.STUDYID == study_id).delete()
db.session.query(Study).filter(Study.STUDYID == study_id).delete()
db.session.query(StudySponsor).filter(StudySponsor.SS_STUDY == study_id).delete()
study = db.session.query(Study).filter(Study.STUDYID == study_id).first()
session.delete(study)
db.session.commit()
flash('Study %i deleted.' % study_id, 'success')
else:

View File

@ -23,6 +23,16 @@ class InvestigatorForm(FlaskForm):
INVESTIGATORTYPE = SelectField("InvestigatorType", choices=[(i.INVESTIGATORTYPE, i.INVESTIGATORTYPEFULL) for i in Investigator.all_types()])
class StudySponsorForm(FlaskForm):
STUDY_ID = HiddenField()
SPONSOR_IDS = SelectMultipleField(
"Sponsor",
coerce=int,
render_kw={'class': 'multi'},
validators=[validators.DataRequired()]
)
class StudyDetailsForm(ModelForm, FlaskForm):
class Meta:
model = StudyDetails
@ -32,6 +42,7 @@ class ConfirmDeleteForm(FlaskForm):
confirm = BooleanField('Yes, really delete', default='checked',
false_values=(False, 'false', 0, '0'))
class RequirementsTable(Table):
AUXDOCID = Col('Code')
AUXDOC = Col('Name')
@ -47,6 +58,20 @@ class InvestigatorsTable(Table):
)
class SponsorCol(Col):
def td_format(self, content):
return f'{content.SP_NAME} ({content.SP_TYPE})'
class SponsorsTable(Table):
sponsor = SponsorCol('Sponsor')
delete = LinkCol(
'delete', 'del_study_sponsor', url_kwargs=dict(study_sponsor_id='id'),
anchor_attrs={'class': 'btn btn-icon btn-warn', 'title': 'Delete Sponsor'},
th_html_attrs={'class': 'mat-icon text-center', 'title': 'Delete Sponsor'}
)
class StudyTable(Table):
def sort_url(self, col_id, reverse=False):
pass
@ -65,6 +90,11 @@ class StudyTable(Table):
anchor_attrs={'class': 'btn btn-icon btn-accent', 'title': 'Add Investigator'},
th_html_attrs={'class': 'mat-icon text-center', 'title': 'Add Investigator'}
)
add_sponsor = LinkCol(
'account_balance', 'edit_study_sponsor', url_kwargs=dict(study_id='STUDYID'),
anchor_attrs={'class': 'btn btn-icon btn-accent', 'title': 'Edit Sponsor(s)'},
th_html_attrs={'class': 'mat-icon text-center', 'title': 'Edit Sponsor(s)'}
)
STUDYID = Col('Study Id')
TITLE = Col('Title')
NETBADGEID = Col('User')
@ -72,6 +102,7 @@ class StudyTable(Table):
Q_COMPLETE = BoolCol('Complete?')
requirements = NestedTableCol('Requirements', RequirementsTable)
investigators = NestedTableCol('Investigators', InvestigatorsTable)
sponsors = NestedTableCol('Sponsors', SponsorsTable)
delete = LinkCol(
'delete', 'del_study', url_kwargs=dict(study_id='STUDYID'),
anchor_attrs={'class': 'btn btn-icon btn-warn', 'title': 'Delete Study'},

View File

@ -1,7 +1,66 @@
from marshmallow import fields
from sqlalchemy import func
from pb import db, ma
class Sponsor(db.Model):
SPONSOR_ID = db.Column(db.Integer, primary_key=True)
SP_NAME = db.Column(db.String, nullable=True)
SP_MAILING_ADDRESS = db.Column(db.String, nullable=True)
SP_PHONE = db.Column(db.String, nullable=True)
SP_FAX = db.Column(db.String, nullable=True)
SP_EMAIL = db.Column(db.String, nullable=True)
SP_HOMEPAGE = db.Column(db.String, nullable=True)
COMMONRULEAGENCY = db.Column(db.Boolean, nullable=True)
SP_TYPE = db.Column(db.String, nullable=True)
SP_TYPE_GROUP_NAME = db.Column(db.String, nullable=True)
@staticmethod
def all_types():
types = [
Sponsor(SP_TYPE="Federal", SP_TYPE_GROUP_NAME="Government"),
Sponsor(SP_TYPE="Foundation/Not for Profit", SP_TYPE_GROUP_NAME="Other External Funding"),
Sponsor(SP_TYPE="Incoming Sub Award", SP_TYPE_GROUP_NAME="Government"),
Sponsor(SP_TYPE="Industry", SP_TYPE_GROUP_NAME="Industry"),
Sponsor(SP_TYPE="Internal/Departmental/Gift", SP_TYPE_GROUP_NAME="Internal Funding"),
Sponsor(SP_TYPE="No Funding", SP_TYPE_GROUP_NAME="Internal Funding"),
Sponsor(SP_TYPE="Other Colleges and Universities", SP_TYPE_GROUP_NAME="Other External Funding"),
Sponsor(SP_TYPE="State", SP_TYPE_GROUP_NAME="Government"),
]
return types
@staticmethod
def get_type_group_name(type_code):
for t in Sponsor.all_types():
if t.SP_TYPE == type_code:
return t.SP_TYPE_GROUP_NAME
class SponsorSchema(ma.Schema):
class Meta:
fields = ("SPONSOR_ID", "SP_NAME", "SP_MAILING_ADDRESS",
"SP_PHONE", "SP_FAX", "SP_EMAIL", "SP_HOMEPAGE",
"COMMONRULEAGENCY", "SP_TYPE")
class StudySponsor(db.Model):
id = db.Column(db.Integer, primary_key=True)
SS_STUDY = db.Column(db.Integer, db.ForeignKey('study.STUDYID'))
SPONSOR_ID = db.Column(db.Integer, db.ForeignKey('sponsor.SPONSOR_ID'))
study = db.relationship("Study", back_populates="sponsors")
sponsor = db.relationship("Sponsor")
class StudySponsorSchema(ma.Schema):
class Meta:
fields = ("SS_STUDY", "SPONSOR_ID", "SP_NAME", "SP_TYPE", "SP_TYPE_GROUP_NAME", "COMMONRULEAGENCY")
SP_TYPE = fields.Function(lambda obj: obj.sponsor.SP_TYPE)
SP_NAME = fields.Function(lambda obj: obj.sponsor.SP_NAME)
SP_TYPE_GROUP_NAME = fields.Function(lambda obj: obj.sponsor.SP_TYPE_GROUP_NAME)
COMMONRULEAGENCY = fields.Function(lambda obj: obj.sponsor.COMMONRULEAGENCY)
class Study(db.Model):
STUDYID = db.Column(db.Integer, primary_key=True)
HSRNUMBER = db.Column(db.String())
@ -12,6 +71,7 @@ class Study(db.Model):
requirements = db.relationship("RequiredDocument", backref="study", lazy='dynamic')
investigators = db.relationship("Investigator", backref="study", lazy='dynamic')
study_details = db.relationship("StudyDetails", uselist=False, backref="study")
sponsors = db.relationship("StudySponsor", back_populates="study", cascade="all, delete, delete-orphan")
class StudySchema(ma.Schema):

3497
pb/static/csv/sponsors.csv Normal file

File diff suppressed because it is too large Load Diff

View File

@ -218,6 +218,10 @@ select.multi {
margin-bottom: 40px;
padding: 2em;
&.hidden {
display: none;
}
&:nth-child(even) {
background-color: $color-gray-light-2;
}
@ -292,3 +296,8 @@ select.multi {
margin: 0;
}
}
.highlight {
font-weight: bolder;
font-style: italic;
}

View File

@ -13,13 +13,13 @@
</head>
<body>
<h2>{{ title }}</h2>
<p>{{ details }}</p>
<p>{{ details|safe }}</p>
<form action="{{ action }}" method="post">
{{ form.csrf_token() }}
{% for field in form if field.name != "csrf_token" %}
<div class="form-field">
<div class="form-field {{ field.widget.input_type }}">
<div class="form-field-label">{{ field.label() }}:</div>
<div class="form-field-input">{{ field }}</div>
<div class="form-field-help">{{ description_map[field.name] }}</div>

View File

@ -22,7 +22,7 @@
{% for category, message in messages %}
<div class="alert {{ category }}">
<span class="btn-close" onclick="hideElement(this.parentElement);">&times;</span>
{{ message }}
{{ message|safe }}
</div>
{% endfor %}
{% endif %}

View File

@ -1,14 +1,16 @@
import os
from werkzeug.datastructures import MultiDict
os.environ["TESTING"] = "true"
import unittest
import random
import string
from pb import app, db
from pb.forms import StudyForm
from pb.models import Study, RequiredDocument
from pb import app, db, session
from pb.forms import StudyForm, StudySponsorForm
from pb.models import Study, RequiredDocument, Sponsor, StudySponsor
from example_data import ExampleDataLoader
class Sanity_Check_Test(unittest.TestCase):
auths = {}
@ -21,19 +23,26 @@ class Sanity_Check_Test(unittest.TestCase):
@classmethod
def tearDownClass(cls):
db.drop_all()
db.session.remove()
pass
def setUp(self):
ExampleDataLoader().clean_db()
self.ctx.push()
def tearDown(self):
ExampleDataLoader().clean_db()
self.ctx.pop()
self.auths = {}
def test_add_and_edit_study(self):
"""Add and edit a study"""
def load_sponsors(self):
ExampleDataLoader().load_sponsors()
sponsors = session.query(Sponsor).all()
num_lines = sum(1 for line in open('pb/static/csv/sponsors.csv'))
self.assertIsNotNone(sponsors)
self.assertEqual(len(sponsors), num_lines - 1)
return sponsors
def add_study(self):
study_title = "My Test Document" + ''.join(random.choices(string.digits, k=8))
study = Study(TITLE=study_title, NETBADGEID="dhf8r")
form = StudyForm(formdata=None, obj=study)
@ -47,11 +56,18 @@ class Sanity_Check_Test(unittest.TestCase):
assert r.status_code == 302
added_study = Study.query.filter(Study.TITLE == study_title).first()
assert added_study
num_studies_before = Study.query.count()
num_docs_before = RequiredDocument.query.filter(Study.STUDYID == added_study.STUDYID).count()
self.assertEqual(num_reqs, num_docs_before)
return added_study
def test_add_and_edit_study(self):
"""Add and edit a study"""
added_study: Study = self.add_study()
num_studies_before = Study.query.count()
num_docs_before = RequiredDocument.query.filter(Study.STUDYID == added_study.STUDYID).count()
"""Edit an existing study"""
added_study.title = "New Title" + ''.join(random.choices(string.digits, k=8))
form_2 = StudyForm(formdata=None, obj=added_study)
@ -68,3 +84,43 @@ class Sanity_Check_Test(unittest.TestCase):
num_docs_after = RequiredDocument.query.filter(Study.STUDYID == edited_study.STUDYID).count()
self.assertEqual(num_docs_before, num_docs_after)
self.assertEqual(num_studies_before, num_studies_after)
def test_add_and_edit_study_sponsor(self):
"""Add and edit a study sponsor"""
num_sponsors = 5
all_sponsors = self.load_sponsors()
study: Study = self.add_study()
self.assertIsNotNone(study)
num_study_sponsors_before = len(study.sponsors)
self.assertEqual(num_study_sponsors_before, 0)
study_sponsors_before = session.query(StudySponsor).filter(StudySponsor.SS_STUDY == study.STUDYID).all()
self.assertEqual(len(study_sponsors_before), 0)
"""Add sponsors to an existing study"""
random_sponsor_ids = random.choices([s.SPONSOR_ID for s in all_sponsors], k=num_sponsors)
self.assertEqual(len(random_sponsor_ids), num_sponsors)
formdata = MultiDict()
formdata.add('SS_STUDY', study.STUDYID)
for sponsor_id in random_sponsor_ids:
formdata.add('SPONSOR_IDS', sponsor_id)
form = StudySponsorForm(formdata=formdata, obj=study)
form.SPONSOR_IDS.choices = [(s.SPONSOR_ID, f'{s.SP_NAME} ({s.SP_TYPE})') for s in all_sponsors]
self.assertEqual(len(form.data['SPONSOR_IDS']), num_sponsors)
rv = self.app.post(f'/study_sponsor/{study.STUDYID}', data=form.data, follow_redirects=False)
assert rv.status_code == 302
edited_study = Study.query.filter(Study.STUDYID == study.STUDYID).first()
assert edited_study
num_study_sponsors_after = len(edited_study.sponsors)
self.assertGreater(num_study_sponsors_after, 0)
self.assertEqual(num_study_sponsors_after, num_sponsors)
study_sponsors_after = session.query(StudySponsor).filter(StudySponsor.SS_STUDY == study.STUDYID).all()
self.assertGreater(len(study_sponsors_after), 0)
self.assertEqual(len(study_sponsors_after), num_sponsors)