commit
561af4ba58
2
Pipfile
2
Pipfile
|
@ -29,4 +29,4 @@ werkzeug = "*"
|
|||
flask-cors = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
||||
python_version = "3.8"
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
||||
|
|
@ -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 ###
|
112
pb/__init__.py
112
pb/__init__.py
|
@ -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:
|
||||
|
|
31
pb/forms.py
31
pb/forms.py
|
@ -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'},
|
||||
|
|
60
pb/models.py
60
pb/models.py
|
@ -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):
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
{% for category, message in messages %}
|
||||
<div class="alert {{ category }}">
|
||||
<span class="btn-close" onclick="hideElement(this.parentElement);">×</span>
|
||||
{{ message }}
|
||||
{{ message|safe }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue