diff --git a/.gitignore b/.gitignore index 1a77a3e..8c8bf6d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ .idea __pycache__/ app.db +pb/static/.webassets-cache* +pb/static/*.css static/.webassets-cache* static/*.css diff --git a/Dockerfile b/Dockerfile index 063151b..8e6885a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,31 +1,22 @@ -FROM python:3.7 +FROM python:3.7-slim -ENV PATH=/root/.local/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin - -# install node and yarn -RUN apt-get update -RUN apt-get -y install postgresql-client libpcre3 libpcre3-dev - -# config project dir -RUN mkdir /protocol-builder-mock -WORKDIR /protocol-builder-mock - -# install python requirements -RUN pip install pipenv -ADD Pipfile /protocol-builder-mock/ -ADD Pipfile.lock /protocol-builder-mock/ -RUN pipenv install --dev - -# include rejoiner code (gets overriden by local changes) -COPY . /protocol-builder-mock/ - -ENV FLASK_APP=/protocol-builder-mock/app.py - -# run webserver by default -CMD ["pipenv", "run", "flask", "db", "upgrade"] -CMD ["pipenv", "run", "python", "/protocol-builder-mock/run.py"] - -# expose ports -EXPOSE 5001 +WORKDIR /app +COPY Pipfile Pipfile.lock /app/ +RUN set -xe \ + && pip install pipenv \ + && apt-get update -q \ + && apt-get install -y -q \ + gcc python3-dev libssl-dev \ + curl postgresql-client git-core \ + gunicorn3 postgresql-client \ + && pipenv install --dev \ + && apt-get remove -y gcc python3-dev libssl-dev \ + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* \ + && mkdir -p /app \ + && useradd _gunicorn --no-create-home --user-group +COPY . /app/ +WORKDIR /app diff --git a/Pipfile b/Pipfile index 2d13f1d..4b98838 100644 --- a/Pipfile +++ b/Pipfile @@ -4,6 +4,7 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] +pbr = "*" [packages] flask = "*" @@ -21,6 +22,9 @@ marshmallow-sqlalchemy = "*" wtforms-alchemy = "*" psycopg2-binary = "*" pyscss = "*" +gunicorn = "*" +werkzeug = "*" +flask-cors = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 34da46f..9398ded 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "ca92325f9ff90d6263f261dc514de998c618a3e91614f9c1987b2f0db9b72dcf" + "sha256": "4897f5ad1de5dcc7a407c45a670a3e5cf332d56fa138bfe1805441aa18c195cc" }, "pipfile-spec": 6, "requires": { @@ -38,10 +38,10 @@ }, "certifi": { "hashes": [ - "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", - "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" + "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", + "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" ], - "version": "==2019.11.28" + "version": "==2020.4.5.1" }, "chardet": { "hashes": [ @@ -52,10 +52,10 @@ }, "click": { "hashes": [ - "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc", - "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a" + "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", + "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" ], - "version": "==7.1.1" + "version": "==7.1.2" }, "clickclick": { "hashes": [ @@ -69,11 +69,11 @@ "swagger-ui" ], "hashes": [ - "sha256:bf32bfae6af337cfa4a8489c21516adbe5c50e3f8dc0b7ed2394ce8dde218018", - "sha256:c568e579f84be808e387dcb8570bb00a536891be1318718a0dad3ba90f034191" + "sha256:1ccfac57d4bb7adf4295ba6f5e48f5a1f66057df6a0713417766c9b5235182ee", + "sha256:5439e9659a89c4380d93a07acfbf3380d70be4130574de8881e5f0dfec7ad0e2" ], "index": "pypi", - "version": "==2.6.0" + "version": "==2.7.0" }, "decorator": { "hashes": [ @@ -84,11 +84,11 @@ }, "flask": { "hashes": [ - "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52", - "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6" + "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", + "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" ], "index": "pypi", - "version": "==1.1.1" + "version": "==1.1.2" }, "flask-assets": { "hashes": [ @@ -105,13 +105,21 @@ ], "version": "==1.0.0" }, - "flask-marshmallow": { + "flask-cors": { "hashes": [ - "sha256:01520ef1851ccb64d4ffb33196cddff895cc1302ae1585bff1abf58684a8111a", - "sha256:28b969193958d9602ab5d6add6d280e0e360c8e373d3492c2f73b024ecd36374" + "sha256:72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16", + "sha256:f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a" ], "index": "pypi", - "version": "==0.11.0" + "version": "==3.0.8" + }, + "flask-marshmallow": { + "hashes": [ + "sha256:6e6aec171b8e092e0eafaf035ff5b8637bf3a58ab46f568c4c1bab02f2a3c196", + "sha256:a1685536e7ab5abdc712bbc1ac1a6b0b50951a368502f7985e7d1c27b3c21e59" + ], + "index": "pypi", + "version": "==0.12.0" }, "flask-migrate": { "hashes": [ @@ -123,11 +131,11 @@ }, "flask-sqlalchemy": { "hashes": [ - "sha256:0078d8663330dc05a74bc72b3b6ddc441b9a744e2f56fe60af1a5bfc81334327", - "sha256:6974785d913666587949f7c2946f7001e4fa2cb2d19f4e69ead02e4b8f50b33d" + "sha256:2298f6b874c2a2f1f048eaf21ce5d984e36a04ca849b0ac473050a67c8dae76f", + "sha256:6cd9f71a97ef18ca5ae7d8bd316a32b82814efe7b088096ba68fddfd8a17cbe7" ], "index": "pypi", - "version": "==2.4.1" + "version": "==2.4.2" }, "flask-table": { "hashes": [ @@ -146,32 +154,31 @@ }, "gevent": { "hashes": [ - "sha256:0774babec518a24d9a7231d4e689931f31b332c4517a771e532002614e270a64", - "sha256:0e1e5b73a445fe82d40907322e1e0eec6a6745ca3cea19291c6f9f50117bb7ea", - "sha256:0ff2b70e8e338cf13bedf146b8c29d475e2a544b5d1fe14045aee827c073842c", - "sha256:107f4232db2172f7e8429ed7779c10f2ed16616d75ffbe77e0e0c3fcdeb51a51", - "sha256:14b4d06d19d39a440e72253f77067d27209c67e7611e352f79fe69e0f618f76e", - "sha256:1b7d3a285978b27b469c0ff5fb5a72bcd69f4306dbbf22d7997d83209a8ba917", - "sha256:1eb7fa3b9bd9174dfe9c3b59b7a09b768ecd496debfc4976a9530a3e15c990d1", - "sha256:2711e69788ddb34c059a30186e05c55a6b611cb9e34ac343e69cf3264d42fe1c", - "sha256:28a0c5417b464562ab9842dd1fb0cc1524e60494641d973206ec24d6ec5f6909", - "sha256:3249011d13d0c63bea72d91cec23a9cf18c25f91d1f115121e5c9113d753fa12", - "sha256:44089ed06a962a3a70e96353c981d628b2d4a2f2a75ea5d90f916a62d22af2e8", - "sha256:4bfa291e3c931ff3c99a349d8857605dca029de61d74c6bb82bd46373959c942", - "sha256:50024a1ee2cf04645535c5ebaeaa0a60c5ef32e262da981f4be0546b26791950", - "sha256:53b72385857e04e7faca13c613c07cab411480822ac658d97fd8a4ddbaf715c8", - "sha256:74b7528f901f39c39cdbb50cdf08f1a2351725d9aebaef212a29abfbb06895ee", - "sha256:7d0809e2991c9784eceeadef01c27ee6a33ca09ebba6154317a257353e3af922", - "sha256:896b2b80931d6b13b5d9feba3d4eebc67d5e6ec54f0cf3339d08487d55d93b0e", - "sha256:8d9ec51cc06580f8c21b41fd3f2b3465197ba5b23c00eb7d422b7ae0380510b0", - "sha256:9f7a1e96fec45f70ad364e46de32ccacab4d80de238bd3c2edd036867ccd48ad", - "sha256:ab4dc33ef0e26dc627559786a4fba0c2227f125db85d970abbf85b77506b3f51", - "sha256:d1e6d1f156e999edab069d79d890859806b555ce4e4da5b6418616322f0a3df1", - "sha256:d752bcf1b98174780e2317ada12013d612f05116456133a6acf3e17d43b71f05", - "sha256:e5bcc4270671936349249d26140c267397b7b4b1381f5ec8b13c53c5b53ab6e1" + "sha256:00b03601b8dd1ee2aa07811cb60a4befe36173b15d91c6e207e37f8d77dd6fac", + "sha256:0acc15ba2ac2a555529ad82d5a28fc85dbb6b2ff947657d67bebfd352e2b5c14", + "sha256:15eae3cd450dac7dae7f4ac59e01db1378965c9ef565c39c5ae78c5a888f9ac9", + "sha256:1dc7f1f6bc1f67d625e4272b01e717eba0b4fa024d2ff7934c8d320674d6f7fa", + "sha256:1dd95433be45e1115053878366e3f5332ae99c39cb345be23851327c062b9f4a", + "sha256:28b7d83b4327ceb79668eca2049bf4b9ce66d5ace18a88335e3035b573f889fd", + "sha256:31dc5d4ab8172cc00c4ff17cb18edee633babd961f64bf54214244d769bc3a74", + "sha256:38db524ea88d81d596b2cbb6948fced26654a15fec40ea4529224e239a6f45e8", + "sha256:3ff477b6d275396123faf8ce2d5b82f96d85ba264e0b9d4b56a2bac49d1b9adc", + "sha256:4d2729dd4bf9c4d0f29482f53cdf9fc90a498aebb5cd7ae8b45d35657437d2ac", + "sha256:52e5cd607749ed3b8aa0272cacf2c11deec61fca4c3bec57a9fea8c49316627d", + "sha256:5c604179cebcc57f10505d8db177b92a715907815a464b066e7eba322d1c33ac", + "sha256:88c76df4967c5229f853aa67ad1b394d9e4f985b0359c9bc9879416bba3e7c68", + "sha256:929c33df8e9bcbe31906024fcd21580bd018196dbd3249eb5b2f19d63e11092d", + "sha256:92edc18a357473e01a4e4a82c073ed3c99ceca6e3ce93c23668dd4a2401f07dc", + "sha256:937d36730f2b0dee3387712074b1f15b802e2e074a3d7c6dcaf70521236d607c", + "sha256:9b4e940fc6071afebb86ba5f48dbb5f1fc3cb96ebeb8cf145eb5b499e9c6ee33", + "sha256:a7805934e8ce81610b61f806572c3d504cedd698cc8c9460d78d2893ba598c4a", + "sha256:d07a2afe4215731eb57d5b257a2e7e7e170d8a7ae1f02f6d0682cd3403debea9", + "sha256:e01d5373528e4ebdde66dc47a608d225fa3c4408ccd828d26c49b7ff75d82bd9", + "sha256:efd9546468502a30ddd4699c3124ccb9d3099130f9b5ae1e2a54ad5b46e86120", + "sha256:fcb64f3a28420d1b872b7ef41b12e8a1a4dcadfc8eff3c09993ab0cdf52584a1" ], "index": "pypi", - "version": "==1.4.0" + "version": "==20.5.0" }, "greenlet": { "hashes": [ @@ -201,6 +208,14 @@ "markers": "platform_python_implementation == 'CPython'", "version": "==0.4.15" }, + "gunicorn": { + "hashes": [ + "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626", + "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c" + ], + "index": "pypi", + "version": "==20.0.4" + }, "idna": { "hashes": [ "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", @@ -210,11 +225,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302", - "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b" + "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f", + "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e" ], "markers": "python_version < '3.8'", - "version": "==1.5.0" + "version": "==1.6.0" }, "infinity": { "hashes": [ @@ -224,9 +239,10 @@ }, "inflection": { "hashes": [ - "sha256:18ea7fb7a7d152853386523def08736aa8c32636b047ade55f7578c4edeb16ca" + "sha256:32a5c3341d9583ec319548b9015b7fbdf8c429cbcb575d326c33ae3a0e90d52c", + "sha256:9a15d3598f01220e93f2207c432cfede50daff53137ce660fb8be838ef1ca6cc" ], - "version": "==0.3.1" + "version": "==0.4.0" }, "intervals": { "hashes": [ @@ -243,10 +259,10 @@ }, "jinja2": { "hashes": [ - "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", - "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49" + "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", + "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" ], - "version": "==2.11.1" + "version": "==2.11.2" }, "jsonschema": { "hashes": [ @@ -315,18 +331,18 @@ }, "marshmallow": { "hashes": [ - "sha256:90854221bbb1498d003a0c3cc9d8390259137551917961c8b5258c64026b2f85", - "sha256:ac2e13b30165501b7d41fc0371b8df35944f5849769d136f20e2c5f6cdc6e665" + "sha256:c2673233aa21dde264b84349dc2fd1dce5f30ed724a0a00e75426734de5b84ab", + "sha256:f88fe96434b1f0f476d54224d59333eba8ca1a203a2695683c1855675c4049a7" ], - "version": "==3.5.1" + "version": "==3.6.0" }, "marshmallow-sqlalchemy": { "hashes": [ - "sha256:9301c6fd197bd97337820ea1417aa1233d0ee3e22748ebd5821799bc841a57e8", - "sha256:dde9e20bcb710e9e59f765a38e3d6d17f1b2d6b4320cbdc2cea0f6b57f70d08c" + "sha256:3247e41e424146340b03a369f2b7c6f0364477ccedc4e2481e84d5f3a8d3c67f", + "sha256:dbbe51d28bb28e7ee2782e51310477f7a2c5a111a301f6dd8e264e11ab820427" ], "index": "pypi", - "version": "==0.22.3" + "version": "==0.23.0" }, "openapi-spec-validator": { "hashes": [ @@ -338,41 +354,39 @@ }, "psycopg2-binary": { "hashes": [ - "sha256:040234f8a4a8dfd692662a8308d78f63f31a97e1c42d2480e5e6810c48966a29", - "sha256:086f7e89ec85a6704db51f68f0dcae432eff9300809723a6e8782c41c2f48e03", - "sha256:18ca813fdb17bc1db73fe61b196b05dd1ca2165b884dd5ec5568877cabf9b039", - "sha256:19dc39616850342a2a6db70559af55b22955f86667b5f652f40c0e99253d9881", - "sha256:2166e770cb98f02ed5ee2b0b569d40db26788e0bf2ec3ae1a0d864ea6f1d8309", - "sha256:3a2522b1d9178575acee4adf8fd9f979f9c0449b00b4164bb63c3475ea6528ed", - "sha256:3aa773580f85a28ffdf6f862e59cb5a3cc7ef6885121f2de3fca8d6ada4dbf3b", - "sha256:3b5deaa3ee7180585a296af33e14c9b18c218d148e735c7accf78130765a47e3", - "sha256:407af6d7e46593415f216c7f56ba087a9a42bd6dc2ecb86028760aa45b802bd7", - "sha256:4c3c09fb674401f630626310bcaf6cd6285daf0d5e4c26d6e55ca26a2734e39b", - "sha256:4c6717962247445b4f9e21c962ea61d2e884fc17df5ddf5e35863b016f8a1f03", - "sha256:50446fae5681fc99f87e505d4e77c9407e683ab60c555ec302f9ac9bffa61103", - "sha256:5057669b6a66aa9ca118a2a860159f0ee3acf837eda937bdd2a64f3431361a2d", - "sha256:5dd90c5438b4f935c9d01fcbad3620253da89d19c1f5fca9158646407ed7df35", - "sha256:659c815b5b8e2a55193ede2795c1e2349b8011497310bb936da7d4745652823b", - "sha256:69b13fdf12878b10dc6003acc8d0abf3ad93e79813fd5f3812497c1c9fb9be49", - "sha256:7a1cb80e35e1ccea3e11a48afe65d38744a0e0bde88795cc56a4d05b6e4f9d70", - "sha256:7e6e3c52e6732c219c07bd97fff6c088f8df4dae3b79752ee3a817e6f32e177e", - "sha256:7f42a8490c4fe854325504ce7a6e4796b207960dabb2cbafe3c3959cb00d1d7e", - "sha256:84156313f258eafff716b2961644a4483a9be44a5d43551d554844d15d4d224e", - "sha256:8578d6b8192e4c805e85f187bc530d0f52ba86c39172e61cd51f68fddd648103", - "sha256:890167d5091279a27e2505ff0e1fb273f8c48c41d35c5b92adbf4af80e6b2ed6", - "sha256:98e10634792ac0e9e7a92a76b4991b44c2325d3e7798270a808407355e7bb0a1", - "sha256:9aadff9032e967865f9778485571e93908d27dab21d0fdfdec0ca779bb6f8ad9", - "sha256:9f24f383a298a0c0f9b3113b982e21751a8ecde6615494a3f1470eb4a9d70e9e", - "sha256:a73021b44813b5c84eda4a3af5826dd72356a900bac9bd9dd1f0f81ee1c22c2f", - "sha256:afd96845e12638d2c44d213d4810a08f4dc4a563f9a98204b7428e567014b1cd", - "sha256:b73ddf033d8cd4cc9dfed6324b1ad2a89ba52c410ef6877998422fcb9c23e3a8", - "sha256:b8f490f5fad1767a1331df1259763b3bad7d7af12a75b950c2843ba319b2415f", - "sha256:dbc5cd56fff1a6152ca59445178652756f4e509f672e49ccdf3d79c1043113a4", - "sha256:eac8a3499754790187bb00574ab980df13e754777d346f85e0ff6df929bcd964", - "sha256:eaed1c65f461a959284649e37b5051224f4db6ebdc84e40b5e65f2986f101a08" + "sha256:008da3ab51adc70a5f1cfbbe5db3a22607ab030eb44bcecf517ad11a0c2b3cac", + "sha256:07cf82c870ec2d2ce94d18e70c13323c89f2f2a2628cbf1feee700630be2519a", + "sha256:08507efbe532029adee21b8d4c999170a83760d38249936038bd0602327029b5", + "sha256:107d9be3b614e52a192719c6bf32e8813030020ea1d1215daa86ded9a24d8b04", + "sha256:17a0ea0b0eabf07035e5e0d520dabc7950aeb15a17c6d36128ba99b2721b25b1", + "sha256:3286541b9d85a340ee4ed42732d15fc1bb441dc500c97243a768154ab8505bb5", + "sha256:3939cf75fc89c5e9ed836e228c4a63604dff95ad19aed2bbf71d5d04c15ed5ce", + "sha256:40abc319f7f26c042a11658bf3dd3b0b3bceccf883ec1c565d5c909a90204434", + "sha256:51f7823f1b087d2020d8e8c9e6687473d3d239ba9afc162d9b2ab6e80b53f9f9", + "sha256:6bb2dd006a46a4a4ce95201f836194eb6a1e863f69ee5bab506673e0ca767057", + "sha256:702f09d8f77dc4794651f650828791af82f7c2efd8c91ae79e3d9fe4bb7d4c98", + "sha256:7036ccf715925251fac969f4da9ad37e4b7e211b1e920860148a10c0de963522", + "sha256:7b832d76cc65c092abd9505cc670c4e3421fd136fb6ea5b94efbe4c146572505", + "sha256:8f74e631b67482d504d7e9cf364071fc5d54c28e79a093ff402d5f8f81e23bfa", + "sha256:930315ac53dc65cbf52ab6b6d27422611f5fb461d763c531db229c7e1af6c0b3", + "sha256:96d3038f5bd061401996614f65d27a4ecb62d843eb4f48e212e6d129171a721f", + "sha256:a20299ee0ea2f9cca494396ac472d6e636745652a64a418b39522c120fd0a0a4", + "sha256:a34826d6465c2e2bbe9d0605f944f19d2480589f89863ed5f091943be27c9de4", + "sha256:a69970ee896e21db4c57e398646af9edc71c003bc52a3cc77fb150240fefd266", + "sha256:b9a8b391c2b0321e0cd7ec6b4cfcc3dd6349347bd1207d48bcb752aa6c553a66", + "sha256:ba13346ff6d3eb2dca0b6fa0d8a9d999eff3dcd9b55f3a890f12b0b6362b2b38", + "sha256:bb0608694a91db1e230b4a314e8ed00ad07ed0c518f9a69b83af2717e31291a3", + "sha256:c8830b7d5f16fd79d39b21e3d94f247219036b29b30c8270314c46bf8b732389", + "sha256:cac918cd7c4c498a60f5d2a61d4f0a6091c2c9490d81bc805c963444032d0dab", + "sha256:cc30cb900f42c8a246e2cb76539d9726f407330bc244ca7729c41a44e8d807fb", + "sha256:ccdc6a87f32b491129ada4b87a43b1895cf2c20fdb7f98ad979647506ffc41b6", + "sha256:d1a8b01f6a964fec702d6b6dac1f91f2b9f9fe41b310cbb16c7ef1fac82df06d", + "sha256:e004db88e5a75e5fdab1620fb9f90c9598c2a195a594225ac4ed2a6f1c23e162", + "sha256:eb2f43ae3037f1ef5e19339c41cf56947021ac892f668765cd65f8ab9814192e", + "sha256:fa466306fcf6b39b8a61d003123d442b23707d635a5cb05ac4e1b62cc79105cd" ], "index": "pypi", - "version": "==2.8.4" + "version": "==2.8.5" }, "pyrsistent": { "hashes": [ @@ -382,10 +396,10 @@ }, "pyscss": { "hashes": [ - "sha256:123c1a9087f1c420bea891ebf19d5222262c7d30ced20bb38586023de28c9d4f" + "sha256:f1df571569021a23941a538eb154405dde80bed35dc1ea7c5f3e18e0144746bf" ], "index": "pypi", - "version": "==1.3.6" + "version": "==1.3.7" }, "python-dateutil": { "hashes": [ @@ -404,10 +418,10 @@ }, "pytz": { "hashes": [ - "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", - "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" + "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", + "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048" ], - "version": "==2019.3" + "version": "==2020.1" }, "pyyaml": { "hashes": [ @@ -434,23 +448,50 @@ }, "six": { "hashes": [ - "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", - "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "version": "==1.14.0" + "version": "==1.15.0" }, "sqlalchemy": { "hashes": [ - "sha256:c4cca4aed606297afbe90d4306b49ad3a4cd36feb3f87e4bfd655c57fd9ef445" + "sha256:128bc917ed20d78143a45024455ff0aed7d3b96772eba13d5dbaf9cc57e5c41b", + "sha256:156a27548ba4e1fed944ff9fcdc150633e61d350d673ae7baaf6c25c04ac1f71", + "sha256:27e2efc8f77661c9af2681755974205e7462f1ae126f498f4fe12a8b24761d15", + "sha256:2a12f8be25b9ea3d1d5b165202181f2b7da4b3395289000284e5bb86154ce87c", + "sha256:31c043d5211aa0e0773821fcc318eb5cbe2ec916dfbc4c6eea0c5188971988eb", + "sha256:65eb3b03229f684af0cf0ad3bcc771970c1260a82a791a8d07bffb63d8c95bcc", + "sha256:6cd157ce74a911325e164441ff2d9b4e244659a25b3146310518d83202f15f7a", + "sha256:703c002277f0fbc3c04d0ae4989a174753a7554b2963c584ce2ec0cddcf2bc53", + "sha256:869bbb637de58ab0a912b7f20e9192132f9fbc47fc6b5111cd1e0f6cdf5cf9b0", + "sha256:8a0e0cd21da047ea10267c37caf12add400a92f0620c8bc09e4a6531a765d6d7", + "sha256:8d01e949a5d22e5c4800d59b50617c56125fc187fbeb8fa423e99858546de616", + "sha256:925b4fe5e7c03ed76912b75a9a41dfd682d59c0be43bce88d3b27f7f5ba028fb", + "sha256:9cb1819008f0225a7c066cac8bb0cf90847b2c4a6eb9ebb7431dbd00c56c06c5", + "sha256:a87d496884f40c94c85a647c385f4fd5887941d2609f71043e2b73f2436d9c65", + "sha256:a9030cd30caf848a13a192c5e45367e3c6f363726569a56e75dc1151ee26d859", + "sha256:a9e75e49a0f1583eee0ce93270232b8e7bb4b1edc89cc70b07600d525aef4f43", + "sha256:b50f45d0e82b4562f59f0e0ca511f65e412f2a97d790eea5f60e34e5f1aabc9a", + "sha256:b7878e59ec31f12d54b3797689402ee3b5cfcb5598f2ebf26491732758751908", + "sha256:ce1ddaadee913543ff0154021d31b134551f63428065168e756d90bdc4c686f5", + "sha256:ce2646e4c0807f3461be0653502bb48c6e91a5171d6e450367082c79e12868bf", + "sha256:ce6c3d18b2a8ce364013d47b9cad71db815df31d55918403f8db7d890c9d07ae", + "sha256:e4e2664232005bd306f878b0f167a31f944a07c4de0152c444f8c61bbe3cfb38", + "sha256:e8aa395482728de8bdcca9cc0faf3765ab483e81e01923aaa736b42f0294f570", + "sha256:eb4fcf7105bf071c71068c6eee47499ab8d4b8f5a11fc35147c934f0faa60f23", + "sha256:ed375a79f06cad285166e5be74745df1ed6845c5624aafadec4b7a29c25866ef", + "sha256:f35248f7e0d63b234a109dd72fbfb4b5cb6cb6840b221d0df0ecbf54ab087654", + "sha256:f502ef245c492b391e0e23e94cba030ab91722dcc56963c85bfd7f3441ea2bbe", + "sha256:fe01bac7226499aedf472c62fa3b85b2c619365f3f14dd222ffe4f3aa91e5f98" ], "index": "pypi", - "version": "==1.3.15" + "version": "==1.3.17" }, "sqlalchemy-utils": { "hashes": [ - "sha256:f268af5bc03597fe7690d60df3e5f1193254a83e07e4686f720f61587ec4493a" + "sha256:7a7fab14bed80df065412bbf71a0a9b0bfeb4b7c111c2d9bffe57283082f3a6b" ], - "version": "==0.36.3" + "version": "==0.36.6" }, "swagger-ui-bundle": { "hashes": [ @@ -462,16 +503,16 @@ }, "urllib3": { "hashes": [ - "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", - "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" + "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", + "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" ], - "version": "==1.25.8" + "version": "==1.25.9" }, "validators": { "hashes": [ - "sha256:b192e6bde7d617811d59f50584ed240b580375648cd032d106edeb3164099508" + "sha256:31e8bb01b48b48940a021b8a9576b840f98fa06b91762ef921d02cb96d38727a" ], - "version": "==0.14.2" + "version": "==0.15.0" }, "webassets": { "hashes": [ @@ -482,17 +523,18 @@ }, "werkzeug": { "hashes": [ - "sha256:169ba8a33788476292d04186ab33b01d6add475033dfc07215e6d219cc077096", - "sha256:6dc65cf9091cf750012f56f2cad759fa9e879f511b5ff8685e456b4e3bf90d16" + "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", + "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" ], - "version": "==1.0.0" + "index": "pypi", + "version": "==1.0.1" }, "wtforms": { "hashes": [ - "sha256:0cdbac3e7f6878086c334aa25dc5a33869a3954e9d1e015130d65a69309b3b61", - "sha256:e3ee092c827582c50877cdbd49e9ce6d2c5c1f6561f849b3b068c1b8029626f1" + "sha256:6ff8635f4caeed9f38641d48cfe019d0d3896f41910ab04494143fc027866e1b", + "sha256:861a13b3ae521d6700dac3b2771970bd354a63ba7043ecc3a82b5288596a1972" ], - "version": "==2.2.1" + "version": "==2.3.1" }, "wtforms-alchemy": { "hashes": [ @@ -515,5 +557,14 @@ "version": "==3.1.0" } }, - "develop": {} + "develop": { + "pbr": { + "hashes": [ + "sha256:07f558fece33b05caf857474a366dfcc00562bca13dd8b47b2b3e22d9f9bf55c", + "sha256:579170e23f8e0c2f24b0de612f71f648eccb79fb1322c814ae6b3c07b5ba23e8" + ], + "index": "pypi", + "version": "==5.4.5" + } + } } diff --git a/config/default.py b/config/default.py index 0d552d9..891a9ba 100644 --- a/config/default.py +++ b/config/default.py @@ -1,3 +1,4 @@ +import re import os from os import environ @@ -5,10 +6,13 @@ basedir = os.path.abspath(os.path.dirname(__file__)) NAME = "CR Connect Protocol Builder Mock" FLASK_PORT = environ.get('PORT0') or environ.get('FLASK_PORT', default="5001") -CORS_ENABLED = False +CORS_ALLOW_ORIGINS = re.split(r',\s*', environ.get('CORS_ALLOW_ORIGINS', default="localhost:5000")) DEVELOPMENT = environ.get('DEVELOPMENT', default="true") == "true" TESTING = environ.get('TESTING', default="false") == "true" +# Add trailing slash to base path +APPLICATION_ROOT = re.sub(r'//', '/', '/%s/' % environ.get('APPLICATION_ROOT', default="/").strip('/')) + DB_HOST = environ.get('DB_HOST', default="localhost") DB_PORT = environ.get('DB_PORT', default="5432") DB_NAME = environ.get('DB_NAME', default="pb") @@ -22,5 +26,7 @@ SECRET_KEY = environ.get('SECRET_KEY', default='a really really really really lo print('=== USING DEFAULT CONFIG: ===') print('DB_HOST = ', DB_HOST) +print('CORS_ALLOW_ORIGINS = ', CORS_ALLOW_ORIGINS) print('DEVELOPMENT = ', DEVELOPMENT) print('TESTING = ', TESTING) +print('APPLICATION_ROOT = ', APPLICATION_ROOT) diff --git a/docker_run.sh b/docker_run.sh index a24b6db..8d6e338 100755 --- a/docker_run.sh +++ b/docker_run.sh @@ -1,6 +1,25 @@ #!/bin/bash # run migrations -export FLASK_APP=./app.py -pipenv run flask db upgrade -pipenv run python ./run.py +export FLASK_APP=/app/pb/__init__.py + +if [ "$DOWNGRADE_DB" = "true" ]; then + echo 'Downgrading database...' + pipenv run flask db downgrade +fi + +if [ "$UPGRADE_DB" = "true" ]; then + echo 'Upgrading database...' + pipenv run flask db upgrade +fi + +if [ "$RESET_DB" = "true" ]; then + echo 'Resetting database...' + pipenv run flask load-example-data +fi + +if [ "$APPLICATION_ROOT" = "/" ]; then + pipenv run gunicorn --bind 0.0.0.0:$PORT0 wsgi:app +else + pipenv run gunicorn -e SCRIPT_NAME="$APPLICATION_ROOT" --bind 0.0.0.0:$PORT0 wsgi:app +fi diff --git a/app.py b/pb/__init__.py similarity index 70% rename from app.py rename to pb/__init__.py index f776df0..cba7017 100644 --- a/app.py +++ b/pb/__init__.py @@ -1,16 +1,17 @@ import datetime import os +import re +import yaml from datetime import date import connexion -import yaml +from flask_cors import CORS from flask import url_for, json, redirect, render_template, request, flash from flask_assets import Environment, Bundle from flask_sqlalchemy import SQLAlchemy from flask_marshmallow import Marshmallow from flask_migrate import Migrate from sqlalchemy import func - from wtforms.ext.appengine.db import model_form PROTOCOLS = {} @@ -39,11 +40,8 @@ def get_study_details(studyid): def get_form(id, requirement_code): return - -conn = connexion.App('Protocol Builder', specification_dir='./') -conn.add_api('api.yml') - -app = conn.app +connexion_app = connexion.FlaskApp('Protocol Builder', specification_dir='pb') +app = connexion_app.app app.config.from_object('config.default') app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False @@ -55,17 +53,53 @@ else: app.config.root_path = app.instance_path app.config.from_pyfile('config.py', silent=True) +connexion_app.add_api('api.yml', base_path='/v2.0') + +# Convert list of allowed origins to list of regexes +origins_re = [r"^https?:\/\/%s(.*)" % o.replace('.', '\.') for o in app.config['CORS_ALLOW_ORIGINS']] +cors = CORS(connexion_app.app, origins=origins_re) + db = SQLAlchemy(app) migrate = Migrate(app, db) ma = Marshmallow(app) + +# Set the path of the static directory +APP_ROOT = os.path.dirname(os.path.abspath(__file__)) +APP_STATIC = os.path.join(APP_ROOT, 'static') +BASE_HREF = app.config['APPLICATION_ROOT'].strip('/') +app.static_folder = APP_STATIC +app.static_url_path = app.config['APPLICATION_ROOT'] + 'static' + +print('app.static_folder', app.static_folder) +print('app.static_url_path', app.static_url_path) + +# remove old static map +url_map = app.url_map +try: + for rule in url_map.iter_rules('static'): + url_map._rules.remove(rule) +except ValueError: + # no static view was created yet + pass + +# register new; the same view function is used +app.add_url_rule( + app.static_url_path + '/', + endpoint='static', view_func=app.send_static_file) + assets = Environment(app) +assets.init_app(app) assets.url = app.static_url_path -scss = Bundle('scss/app.scss', filters='pyscss', output='app.css') +scss = Bundle( + 'scss/app.scss', + filters='pyscss', + output='app.css' +) assets.register('app_scss', scss) # Loads all the descriptions from the API so we can display them in the editor. description_map = {} -with open(r'api.yml') as file: +with open(r'pb/api.yml') as file: api_config = yaml.load(file, Loader=yaml.FullLoader) study_detail_properties = api_config['components']['schemas']['StudyDetail']['properties'] for schema in api_config['components']['schemas']: @@ -82,14 +116,14 @@ def has_no_empty_params(rule): return len(defaults) >= len(arguments) -@app.route("/site_map") +@app.route('/site_map') def site_map(): links = [] for rule in app.url_map.iter_rules(): # Filter out rules we can't navigate to in a browser # and rules that require parameters if "GET" in rule.methods and has_no_empty_params(rule): - url = url_for(rule.endpoint, **(rule.defaults or {})) + url = app.confg['APPLICATION_ROOT'].strip('/') + url_for(rule.endpoint, **(rule.defaults or {})) links.append((url, rule.endpoint)) return json.dumps({"links": links}) @@ -97,8 +131,8 @@ def site_map(): # ************************** # WEB FORMS # ************************** -from forms import StudyForm, StudyTable, InvestigatorForm, StudyDetailsForm -from models import Study, RequiredDocument, Investigator, StudySchema, RequiredDocumentSchema, InvestigatorSchema, \ +from pb.forms import StudyForm, StudyTable, InvestigatorForm, StudyDetailsForm +from pb.models import Study, RequiredDocument, Investigator, StudySchema, RequiredDocumentSchema, InvestigatorSchema, \ StudyDetails, StudyDetailsSchema @@ -107,25 +141,33 @@ def index(): # display results studies = db.session.query(Study).order_by(Study.DATE_MODIFIED.desc()).all() table = StudyTable(studies) - return render_template('index.html', table=table) + return render_template( + 'index.html', + table=table, + base_href=BASE_HREF + ) @app.route('/new_study', methods=['GET', 'POST']) def new_study(): form = StudyForm(request.form) - action = "/new_study" + action = BASE_HREF + "/new_study" title = "New Study" if request.method == 'POST': study = Study() study.study_details = StudyDetails() _update_study(study, form) flash('Study created successfully!') - return redirect('/') + return redirect_home() - return render_template('form.html', form=form, - action=action, - title=title, - description_map=description_map) + return render_template( + 'form.html', + form=form, + action=action, + title=title, + description_map=description_map, + base_href=BASE_HREF + ) @app.route('/study/', methods=['GET', 'POST']) @@ -133,7 +175,7 @@ def edit_study(study_id): study = db.session.query(Study).filter(Study.STUDYID == study_id).first() form = StudyForm(request.form, obj=study) if request.method == 'GET': - action = "/study/" + study_id + action = BASE_HREF + "/study/" + study_id title = "Edit Study #" + study_id if study.requirements: form.requirements.data = list(map(lambda r: r.AUXDOCID, list(study.requirements))) @@ -142,17 +184,21 @@ def edit_study(study_id): if request.method == 'POST': _update_study(study, form) flash('Study updated successfully!') - return redirect('/') - return render_template('form.html', form=form, - action=action, - title=title, - description_map={}) + return redirect_home() + return render_template( + 'form.html', + form=form, + action=action, + title=title, + description_map={}, + base_href=BASE_HREF + ) @app.route('/investigator/', methods=['GET', 'POST']) def new_investigator(study_id): form = InvestigatorForm(request.form) - action = "/investigator/" + study_id + action = BASE_HREF + "/investigator/" + study_id title = "Add Investigator to Study " + study_id if request.method == 'POST': investigator = Investigator(STUDYID=study_id) @@ -161,19 +207,23 @@ def new_investigator(study_id): db.session.add(investigator) db.session.commit() flash('Investigator created successfully!') - return redirect('/') + return redirect_home() - return render_template('form.html', form=form, - action=action, - title=title, - description_map={}) + return render_template( + 'form.html', + form=form, + action=action, + title=title, + description_map={}, + base_href=BASE_HREF + ) @app.route('/del_investigator/', methods=['GET']) def del_investigator(inv_id): db.session.query(Investigator).filter(Investigator.id == inv_id).delete() db.session.commit() - return redirect('/') + return redirect_home() @app.route('/del_study/', methods=['GET']) @@ -183,7 +233,7 @@ def del_study(study_id): db.session.query(StudyDetails).filter(StudyDetails.STUDYID == study_id).delete() db.session.query(Study).filter(Study.STUDYID == study_id).delete() db.session.commit() - return redirect('/') + return redirect_home() def _update_study(study, form): @@ -218,7 +268,7 @@ def study_details(study_id): study_details = StudyDetails(STUDYID=study_id) form = StudyDetailsForm(request.form, obj=study_details) if request.method == 'GET': - action = "/study_details/" + study_id + action = BASE_HREF + "/study_details/" + study_id title = "Edit Study Details for Study #" + study_id details = "Numeric fields can be 1 for true, 0 or false, or Null if not applicable." if request.method == 'POST': @@ -226,12 +276,20 @@ def study_details(study_id): db.session.add(study_details) db.session.commit() flash('Study updated successfully!') - return redirect('/') - return render_template('form.html', form=form, - action=action, - title=title, - details=details, - description_map=description_map) + return redirect_home() + return render_template( + 'form.html', + form=form, + action=action, + title=title, + details=details, + description_map=description_map, + base_href=BASE_HREF + ) + + +def redirect_home(): + return redirect(url_for('index')) if __name__ == '__main__': diff --git a/api.yml b/pb/api.yml similarity index 98% rename from api.yml rename to pb/api.yml index 8786c3a..6893ea9 100644 --- a/api.yml +++ b/pb/api.yml @@ -10,8 +10,6 @@ info: name: Apache 2.0 url: http://www.apache.org/licenses/LICENSE-2.0.html # Added by API Auto Mocking Plugin -servers: - - url: http://localhost:5000/pb # tags are used for organizing operations tags: - name: CR-Connect @@ -22,7 +20,7 @@ paths: tags: - CR-Connect summary: A list of all studies related to a given UVA ID - operationId: app.get_user_studies + operationId: pb.get_user_studies description: "By passing in a valid UVA Id (ex: dhf8r) it will return a list of all studies that exist for that user in Protocol Builder" parameters: - in: query @@ -51,7 +49,7 @@ paths: tags: - CR-Connect summary: Required documents - operationId: app.required_docs + operationId: pb.required_docs description: A list of all documents Protocol Builder considers required, given input from the PI parameters: - in: query @@ -74,7 +72,7 @@ paths: tags: - CR-Connect summary: Personnel associated with this study. - operationId: app.investigators + operationId: pb.investigators description: A list of everyone that is associated with the study, including the PI, Study Coordinator, etc... This is currently returned on the "study" endpoint with other information. parameters: - in: query @@ -103,7 +101,7 @@ paths: get: tags: - CR-Connect - operationId: app.get_study_details + operationId: pb.get_study_details summary: Details about a specific protocol. responses: 200: diff --git a/forms.py b/pb/forms.py similarity index 90% rename from forms.py rename to pb/forms.py index 0b8e7f6..ceae06d 100644 --- a/forms.py +++ b/pb/forms.py @@ -1,12 +1,9 @@ -import sys - -from flask_table import Table, Col, DateCol, LinkCol, BoolCol, DatetimeCol, NestedTableCol +from flask_table import Table, Col, LinkCol, BoolCol, DatetimeCol, NestedTableCol from flask_wtf import FlaskForm -from wtforms import SelectMultipleField, SubmitField, StringField, IntegerField, BooleanField, DateField, widgets, \ - SelectField, validators, HiddenField +from wtforms import SelectMultipleField, StringField, BooleanField, SelectField, validators, HiddenField from wtforms_alchemy import ModelForm -from models import RequiredDocument, Investigator, StudyDetails +from pb.models import RequiredDocument, Investigator, StudyDetails class StudyForm(FlaskForm): diff --git a/models.py b/pb/models.py similarity index 99% rename from models.py rename to pb/models.py index 023ea0c..42c2b96 100644 --- a/models.py +++ b/pb/models.py @@ -1,5 +1,5 @@ from sqlalchemy import func -from app import db, ma +from pb import db, ma class Study(db.Model): diff --git a/static/favicon.ico b/pb/static/favicon.ico similarity index 100% rename from static/favicon.ico rename to pb/static/favicon.ico diff --git a/static/scss/app.scss b/pb/static/scss/app.scss similarity index 100% rename from static/scss/app.scss rename to pb/static/scss/app.scss diff --git a/run.py b/run.py index 6058c0e..9eb95ca 100644 --- a/run.py +++ b/run.py @@ -1,4 +1,4 @@ -from app import app +from pb import app if __name__ == "__main__": flask_port = app.config['FLASK_PORT'] app.run(host='0.0.0.0', port=flask_port) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..9e50b6b --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[metadata] +name = pb + +[files] +packages = pb diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..159a3d3 --- /dev/null +++ b/setup.py @@ -0,0 +1,3 @@ +from setuptools import setup + +setup(setup_requires=["pbr"], pbr=True) diff --git a/templates/form.html b/templates/form.html index d1dff9a..6cab261 100644 --- a/templates/form.html +++ b/templates/form.html @@ -3,11 +3,13 @@ Protocol Builder Mock Configuration + {% assets 'app_scss' %} - + {% endassets %} +

{{ title }}

@@ -27,7 +29,7 @@ {% endfor %} - Cancel + Cancel diff --git a/templates/index.html b/templates/index.html index 273fcd3..a5e2264 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2,12 +2,13 @@ Protocol Builder Mock + {% assets 'app_scss' %} - + {% endassets %} - +

Protocol Builder Mock

diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test_sanity.py b/tests/test_sanity.py similarity index 75% rename from test_sanity.py rename to tests/test_sanity.py index 2be2710..dba1e10 100644 --- a/test_sanity.py +++ b/tests/test_sanity.py @@ -3,9 +3,11 @@ import os os.environ["TESTING"] = "true" import unittest -from app import app, db -from forms import StudyForm -from models import Study, RequiredDocument +import random +import string +from pb import app, db +from pb.forms import StudyForm +from pb.models import Study, RequiredDocument class Sanity_Check_Test(unittest.TestCase): @@ -32,7 +34,8 @@ class Sanity_Check_Test(unittest.TestCase): def test_add_and_edit_study(self): """Add and edit a study""" - study = Study(TITLE="My Test Document", NETBADGEID="dhf8r") + 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) num_reqs = len(form.requirements.choices) self.assertGreater(num_reqs, 0) @@ -40,9 +43,9 @@ class Sanity_Check_Test(unittest.TestCase): for r in form.requirements: form.data['requirements'].append(r.data) - r = self.app.post('/new_study', data=form.data, follow_redirects=True) - assert r.status_code == 200 - added_study = Study.query.filter(Study.TITLE == "My Test Document").first() + r = self.app.post('/new_study', data=form.data, follow_redirects=False) + assert r.status_code == 302 + added_study = Study.query.filter(Study.TITLE == study_title).first() assert added_study num_studies_before = Study.query.count() @@ -50,14 +53,14 @@ class Sanity_Check_Test(unittest.TestCase): self.assertEqual(num_reqs, num_docs_before) """Edit an existing study""" - added_study.title = "New Title" + added_study.title = "New Title" + ''.join(random.choices(string.digits, k=8)) form_2 = StudyForm(formdata=None, obj=added_study) for r in form_2.requirements: form_2.data['requirements'].append(r.data) - r_2 = self.app.post('/study/%i' % added_study.STUDYID, data=form_2.data, follow_redirects=True) - assert r_2.status_code == 200 + r_2 = self.app.post('/study/%i' % added_study.STUDYID, data=form_2.data, follow_redirects=False) + assert r_2.status_code == 302 num_studies_after = Study.query.count() edited_study = Study.query.filter(Study.STUDYID == added_study.STUDYID).first() assert edited_study diff --git a/wsgi.py b/wsgi.py new file mode 100644 index 0000000..d662ed8 --- /dev/null +++ b/wsgi.py @@ -0,0 +1,23 @@ +from werkzeug.exceptions import NotFound +from werkzeug.middleware.dispatcher import DispatcherMiddleware +from werkzeug.middleware.proxy_fix import ProxyFix + +from pb import app + +if __name__ == "__main__": + def no_app(environ, start_response): + return NotFound()(environ, start_response) + + # Remove trailing slash, but add leading slash + base_url = '/' + app.config['APPLICATION_ROOT'].strip('/') + routes = {'/': app.wsgi_app} + + if base_url != '/': + routes[base_url] = app.wsgi_app + + app.wsgi_app = DispatcherMiddleware(no_app, routes) + app.wsgi_app = ProxyFix(app.wsgi_app) + + flask_port = app.config['FLASK_PORT'] + + app.run(host='0.0.0.0', port=flask_port)